lib/glue/property.rb in glue-0.20.0 vs lib/glue/property.rb in glue-0.21.0

- old
+ new

@@ -19,415 +19,415 @@ # Perhaps a sync is needed in evals (!!!!) #++ class Property - # If set to true, perform type checking on property set. - # Useful when debugging. + # If set to true, perform type checking on property set. + # Useful when debugging. - cattr_accessor :type_checking, false + cattr_accessor :type_checking, false - # The symbol of the property. - - attr_accessor :symbol - - # The class of the property. - - attr_accessor :klass + # The symbol of the property. + + attr_accessor :symbol + + # The class of the property. + + attr_accessor :klass - # Additional metadata (like sql declaration, sql index, etc) - # Here is a list of predefined metadata: - # - # [+:reader+] - # create reader? - # - # [+:writer+] - # create writer? - # - # [+:sql_index+] - # create an sql index for the column this poperty maps to? - # - # You can use this mechanism to add your own, custom, - # metadata. - - attr_accessor :meta + # Additional metadata (like sql declaration, sql index, etc) + # Here is a list of predefined metadata: + # + # [+:reader+] + # create reader? + # + # [+:writer+] + # create writer? + # + # [+:sql_index+] + # create an sql index for the column this poperty maps to? + # + # You can use this mechanism to add your own, custom, + # metadata. + + attr_accessor :meta - def initialize(symbol, klass, meta = {}) - @symbol, @klass = symbol, klass - @meta = meta - end - - def ==(other) - return @symbol == other.symbol - end - - def to_s - return @symbol.to_s - end + def initialize(symbol, klass, meta = {}) + @symbol, @klass = symbol, klass + @meta = meta + end + + def ==(other) + return @symbol == other.symbol + end + + def to_s + return @symbol.to_s + end end # A collection of Property related utility methods. module PropertyUtils - # Add accessors to the properties to the given target - # (Module or Class). For simplicity also create the - # meta accessors. - # - # [+target+] - # The target class or module - #-- - # gmosx: Perhaps we 'll optimize this in the future. - #++ + # Add accessors to the properties to the given target + # (Module or Class). For simplicity also create the + # meta accessors. + # + # [+target+] + # The target class or module + #-- + # gmosx: Perhaps we 'll optimize this in the future. + #++ - def self.enchant(target, force = false) - unless target.instance_variables.include?('@__props') - # FIXME: should be thread safe here! - target.instance_variable_set('@__meta', Flexob.new) - target.instance_variable_set('@__props', SafeArray.new) + def self.enchant(target, force = false) + unless target.instance_variables.include?('@__props') + # FIXME: should be thread safe here! + target.instance_variable_set('@__meta', Flexob.new) + target.instance_variable_set('@__props', SafeArray.new) - # gmosx: Ruby surprises and amazes me! We are in the Metaclass - # when defining methods and attributes so @__props is really - # a class scoped variable that unlike @@__props is not shared - # through the hierarchy. - - target.module_eval %{ - def self.__props - @__props - end + # gmosx: Ruby surprises and amazes me! We are in the Metaclass + # when defining methods and attributes so @__props is really + # a class scoped variable that unlike @@__props is not shared + # through the hierarchy. + + target.module_eval %{ + def self.__props + @__props + end - def self.properties - @__props - end + def self.properties + @__props + end - def self.__props=(props) - @__props = props - end - - def self.__meta - @__meta - end + def self.__props=(props) + @__props = props + end + + def self.__meta + @__meta + end - def self.__meta=(meta) - @__meta = meta - end - - def self.metadata - @__meta - end - } + def self.__meta=(meta) + @__meta = meta + end + + def self.metadata + @__meta + end + } - if target.is_a?(Class) + if target.is_a?(Class) - # Add some extra code to append features to - # subclasses. + # Add some extra code to append features to + # subclasses. - target.module_eval %{ - def self.inherited(child) - Glue::PropertyUtils.enchant(child) - Glue::PropertyUtils.copy_props(self, child) - # gmosx: We have to define @@__props first to avoid - # reusing the hash from the module. super must stay - # at the end. - super - end - } + target.module_eval %{ + def self.inherited(child) + Glue::PropertyUtils.enchant(child) + Glue::PropertyUtils.copy_props(self, child) + # gmosx: We have to define @@__props first to avoid + # reusing the hash from the module. super must stay + # at the end. + super + end + } - else + else - # Add some extra code for modules to append - # their features to classes that include it. - - target.module_eval %{ - def self.append_features(base) - # gmosx: We have to define @@__props first to avoid - # reusing the hash from the module. super must stay - # at the end. - Glue::PropertyUtils.copy_features(self, base) - super - end - } + # Add some extra code for modules to append + # their features to classes that include it. + + target.module_eval %{ + def self.append_features(base) + # gmosx: We have to define @@__props first to avoid + # reusing the hash from the module. super must stay + # at the end. + Glue::PropertyUtils.copy_features(self, base) + super + end + } - end - end - end + end + end + end - # Copy properties from src (Module or Class) to dest. - - def self.copy_props(src, dest) - src.__props.each do |p| - add_prop(dest, p) - end - - # copy the metadata. - src.__meta.each do |k, val| - if val.is_a?(TrueClass) - dest.__meta[k] = val - else - dest.__meta[k] = val.dup - end - # val.each { |v| dest.meta(k, v) } if val - end - end + # Copy properties from src (Module or Class) to dest. + + def self.copy_props(src, dest) + src.__props.each do |p| + add_prop(dest, p) + end + + # copy the metadata. + src.__meta.each do |k, val| + if val.is_a?(TrueClass) + dest.__meta[k] = val + else + dest.__meta[k] = val.dup + end + # val.each { |v| dest.meta(k, v) } if val + end + end - # Add the property to the target (Class or Module) - - def self.add_prop(target, prop) - if idx = target.__props.index(prop) - # override in case of duplicates. Keep the order of the props. - target.__props[idx] = prop - else - target.__props << prop - end + # Add the property to the target (Class or Module) + + def self.add_prop(target, prop) + if idx = target.__props.index(prop) + # override in case of duplicates. Keep the order of the props. + target.__props[idx] = prop + else + target.__props << prop + end - # Store the property in the :props_and_relations - # metadata array. + # Store the property in the :props_and_relations + # metadata array. - target.meta :props_and_relations, prop - - # Precompile the property read/write methods + target.meta :props_and_relations, prop + + # Precompile the property read/write methods - s, klass = prop.symbol, prop.klass + s, klass = prop.symbol, prop.klass - if prop.meta[:reader] - target.module_eval %{ - def #{s} - return @#{s} - end - } - end - - # gmosx: __force_xxx reuses xxx= to allow for easier - # overrides. + if prop.meta[:reader] + target.module_eval %{ + def #{s} + return @#{s} + end + } + end + + # gmosx: __force_xxx reuses xxx= to allow for easier + # overrides. - if prop.meta[:writer] - target.module_eval %{ - #{prop_setter(prop)} - - def __force_#{s}(val) - self.#{s}=(} + case klass.name - when Fixnum.name - "val.to_i()" - when String.name - "val.to_s()" - when Float.name - "val.to_f()" - when Time.name - "Time.parse(val.to_s())" - when TrueClass.name, FalseClass.name - "val.to_i() > 0" - else - "val" - end + %{) - end - } - end - end + if prop.meta[:writer] + target.module_eval %{ + #{prop_setter(prop)} + + def __force_#{s}(val) + self.#{s}=(} + case klass.name + when Fixnum.name + "val.to_i()" + when String.name + "val.to_s()" + when Float.name + "val.to_f()" + when Time.name + "Time.parse(val.to_s())" + when TrueClass.name, FalseClass.name + "val.to_i() > 0" + else + "val" + end + %{) + end + } + end + end - # Generates the property setter code. Can be overriden - # to support extra functionality (example: markup) - - def self.prop_setter(prop) - s = prop.symbol + # Generates the property setter code. Can be overriden + # to support extra functionality (example: markup) + + def self.prop_setter(prop) + s = prop.symbol - code = %{ - def #{s}=(val) - } - - if Glue::Property.type_checking - code << %{ - unless #{prop.klass} == val.class - raise "Invalid type, expected '#{prop.klass}', is '\#\{val.class\}'." - end - } - end + code = %{ + def #{s}=(val) + } + + if Glue::Property.type_checking + code << %{ + unless #{prop.klass} == val.class + raise "Invalid type, expected '#{prop.klass}', is '\#\{val.class\}'." + end + } + end - code << %{ - @#{s} = val - end - } + code << %{ + @#{s} = val + end + } - return code - end + return code + end - # Get the property metadata for the given symbol. + # Get the property metadata for the given symbol. - def self.get_prop(klass, sym) - return klass.__props.find { |p| p.symbol == sym } - end + def self.get_prop(klass, sym) + return klass.__props.find { |p| p.symbol == sym } + end - # Include meta-language mixins - - def self.include_meta_mixins(target) - target.module_eval %{ include Glue::Validation } if defined?(Glue::Validation) - # gmosx: TODO, make Og::MetaLanguage equivalent to Validation. - # target.module_eval %{ extend Og::MetaLanguage } if defined?(Og::MetaLanguage) - target.module_eval %{ include Glue::Aspects } if defined?(Glue::Aspects) - target.send(:include, Og::EntityMixin) if defined?(Og::EntityMixin) - end + # Include meta-language mixins + + def self.include_meta_mixins(target) + target.module_eval %{ include Glue::Validation } if defined?(Glue::Validation) + # gmosx: TODO, make Og::MetaLanguage equivalent to Validation. + # target.module_eval %{ extend Og::MetaLanguage } if defined?(Og::MetaLanguage) + target.module_eval %{ include Glue::Aspects } if defined?(Glue::Aspects) + target.send(:include, Og::EntityMixin) if defined?(Og::EntityMixin) + end - def self.copy_features(this, other) - Glue::PropertyUtils.enchant(other) - Glue::PropertyUtils.copy_props(this, other) - Glue::PropertyUtils.include_meta_mixins(other) - end + def self.copy_features(this, other) + Glue::PropertyUtils.enchant(other) + Glue::PropertyUtils.copy_props(this, other) + Glue::PropertyUtils.include_meta_mixins(other) + end - # Resolves the parameters passed to the propxxx macros - # to generate the meta, klass and symbols variables. This - # way the common functionality is factored out. - # - # [+params+] - # The params to resolve. - # [+one_symbol+] - # If true, only resolves one symbol (used in prop). + # Resolves the parameters passed to the propxxx macros + # to generate the meta, klass and symbols variables. This + # way the common functionality is factored out. + # + # [+params+] + # The params to resolve. + # [+one_symbol+] + # If true, only resolves one symbol (used in prop). - def self.resolve_prop_params(*params) - meta = {} - klass = Object - symbols = [] + def self.resolve_prop_params(*params) + meta = {} + klass = Object + symbols = [] - for param in params.flatten - if param.is_a?(Class) - klass = param - elsif param.is_a?(Symbol) - symbols << param - elsif param.is_a?(TrueClass) or param.is_a?(TrueClass) - writer = param - elsif param.is_a?(Hash) - # the meta hash. - meta.update(param) { |k, a, b| [a,b].join(' ') } - else - raise 'Error when defining property!' - end - end + for param in params.flatten + if param.is_a?(Class) + klass = param + elsif param.is_a?(Symbol) + symbols << param + elsif param.is_a?(TrueClass) or param.is_a?(TrueClass) + writer = param + elsif param.is_a?(Hash) + # the meta hash. + meta.update(param) { |k, a, b| [a,b].join(' ') } + else + raise 'Error when defining property!' + end + end - raise 'No symbols provided!' if symbols.empty? + raise 'No symbols provided!' if symbols.empty? - return meta, klass, symbols - end + return meta, klass, symbols + end end end class Module - # Define a property (== typed attribute) - # This works like Ruby's standard attr method, ie creates - # only one property. - # - # Use the prop_reader, prop_writer, prop_accessor methods - # for multiple properties. - # - # === Examples - # - # prop String, :name, :sql => "char(32), :sql_index => "name(32)" - # --> creates only writer. - # prop Fixnum, :oid, writer = true, :sql => "integer PRIMARY KEY" - # --> creates reader and writer. - - def prop(*params) - meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params) - symbol = symbols.first - - Glue::PropertyUtils.enchant(self) + # Define a property (== typed attribute) + # This works like Ruby's standard attr method, ie creates + # only one property. + # + # Use the prop_reader, prop_writer, prop_accessor methods + # for multiple properties. + # + # === Examples + # + # prop String, :name, :sql => "char(32), :sql_index => "name(32)" + # --> creates only writer. + # prop Fixnum, :oid, writer = true, :sql => "integer PRIMARY KEY" + # --> creates reader and writer. + + def prop(*params) + meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params) + symbol = symbols.first + + Glue::PropertyUtils.enchant(self) - property = Glue::Property.new(symbol, klass, meta) - - reader = meta[:reader] || true - writer = writer || meta[:writer] || false - - meta[:reader] = true if meta[:reader].nil? - if defined?(writer) - meta[:writer] = writer - else - meta[:writer] = true if meta[:writer].nil? - end + property = Glue::Property.new(symbol, klass, meta) + + reader = meta[:reader] || true + writer = writer || meta[:writer] || false + + meta[:reader] = true if meta[:reader].nil? + if defined?(writer) + meta[:writer] = writer + else + meta[:writer] = true if meta[:writer].nil? + end - Glue::PropertyUtils.add_prop(self, property) + Glue::PropertyUtils.add_prop(self, property) - # gmosx: should be placed AFTER enchant! - - Glue::PropertyUtils.include_meta_mixins(self) - end + # gmosx: should be placed AFTER enchant! + + Glue::PropertyUtils.include_meta_mixins(self) + end - # Helper method. Accepts a collection of symbols and generates - # properties. Only generates reader. - # - # Example: - # prop_reader String, :name, :title, :body, :sql => "char(32)" - - def prop_reader(*params) - meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params) - - meta[:reader] = true - meta[:writer] = false - - for symbol in symbols - prop(klass, symbol, meta) - end - end + # Helper method. Accepts a collection of symbols and generates + # properties. Only generates reader. + # + # Example: + # prop_reader String, :name, :title, :body, :sql => "char(32)" + + def prop_reader(*params) + meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params) + + meta[:reader] = true + meta[:writer] = false + + for symbol in symbols + prop(klass, symbol, meta) + end + end - # Helper method. Accepts a collection of symbols and generates - # properties. Only generates writer. - # - # Example: - # prop_writer String, :name, :title, :body, :sql => "char(32)" - - def prop_writer(*params) - meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params) + # Helper method. Accepts a collection of symbols and generates + # properties. Only generates writer. + # + # Example: + # prop_writer String, :name, :title, :body, :sql => "char(32)" + + def prop_writer(*params) + meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params) - meta[:reader] = false - meta[:writer] = true - - for symbol in symbols - prop(klass, symbol, meta) - end - end + meta[:reader] = false + meta[:writer] = true + + for symbol in symbols + prop(klass, symbol, meta) + end + end - # Helper method. Accepts a collection of symbols and generates - # properties. Generates reader and writer. - # - # Example: - # prop_accessor String, :name, :title, :body, :sql => "char(32)" - - def prop_accessor(*params) - meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params) + # Helper method. Accepts a collection of symbols and generates + # properties. Generates reader and writer. + # + # Example: + # prop_accessor String, :name, :title, :body, :sql => "char(32)" + + def prop_accessor(*params) + meta, klass, symbols = Glue::PropertyUtils.resolve_prop_params(params) - meta[:reader] = true - meta[:writer] = true - - for symbol in symbols - prop(klass, symbol, meta) - end - end - alias_method :property, :prop_accessor - - # Attach metadata. - # Guard against duplicates, no need to keep order. - # This method uses closures :) - #-- - # gmosx: crappy implementation, recode. - #++ - - def meta(key, *val) - Glue::PropertyUtils.enchant(self) + meta[:reader] = true + meta[:writer] = true + + for symbol in symbols + prop(klass, symbol, meta) + end + end + alias_method :property, :prop_accessor + + # Attach metadata. + # Guard against duplicates, no need to keep order. + # This method uses closures :) + #-- + # gmosx: crappy implementation, recode. + #++ + + def meta(key, *val) + Glue::PropertyUtils.enchant(self) - if val.empty? - self.module_eval %{ - @__meta[key] = true - } - else - val = val.first if val.size == 1 - - self.module_eval %{ - @__meta[key] ||= [] - @__meta[key].delete_if { |v| val == v } - @__meta[key] << val - } - end - end + if val.empty? + self.module_eval %{ + @__meta[key] = true + } + else + val = val.first if val.size == 1 + + self.module_eval %{ + @__meta[key] ||= [] + @__meta[key].delete_if { |v| val == v } + @__meta[key] << val + } + end + end end # * George Moschovitis <gm@navel.gr>