# code: # * George Moschovitis # design: # * Anastastios Koutoumanos # # (c) 2004 Navel, all rights reserved. # $Id: property.rb 200 2004-12-27 11:24:41Z gmosx $ require "glue/array" require "glue/hash" module G # = Property # # Ruby attributes are typeless and generally this is good. Some times # we need extra metadata though, for example in relational mapping, # or web form population. # # Only Fixnums, Strings, Floats, Times, Booleans are converted. # # The default = methods do not force the types. A special # __force_set method should be used instead. # #-- # TODO: # Perhaps a sync is needed in evals (!!!!) #++ class Property # the symbol of the property attr_accessor :symbol # the string representation of the symbol attr_accessor :name # the class of the property attr_accessor :klass # additional metadata (like sql declaration, sql index, etc) attr_accessor :meta def initialize(symbol, klass, meta = {}) @symbol, @klass = symbol, klass @meta = meta @name = @symbol.to_s() end def ==(other) return @symbol == other.symbol end def to_s return name end end # = PropertyUtils # # 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. #-- # gmosx: Perhaps we 'll optimize this in the future. #++ def self.enchant(target) unless target.singleton_methods.include?('__props') target.module_eval <<-"end_eval", __FILE__, __LINE__ @@__meta = G::SafeHash.new @@__props = G::SafeArray.new def self.__props @@__props end def self.__props=(props) @@__props = props end def self.__meta @@__meta end def self.__meta=(meta) @@__meta = meta end end_eval 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| 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 # Precompile the property read/write methods 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[: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 %{ def #{s}=(val) @#{s} = val end } end end end # module 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 = Object for param in params if param.is_a?(Class) klass = param elsif param.is_a?(Symbol) symbol = param elsif param.is_a?(TrueClass) or param.is_a?(TrueClass) writer = param elsif param.is_a?(Hash) # the meta hash. meta = param else raise "Error when defining property!" end end G::PropertyUtils.enchant(self) if self.is_a?(Class) # Add some extra code to append features to # subclasses. self.module_eval <<-"end_eval", __FILE__, __LINE__ def self.inherited(sub) G::PropertyUtils.enchant(sub) G::PropertyUtils.copy_props(self, sub) # gmosx: We have to define @@__props first to avoid reusing # the hash from the module. super must stay at the end. super end end_eval else # Add some extra code for modules to append # their features to classes that include it. self.module_eval <<-"end_eval", __FILE__, __LINE__ def self.append_features(base) G::PropertyUtils.enchant(base) G::PropertyUtils.copy_props(self, base) # gmosx: We have to define @@__props first to avoid reusing # the hash from the module. super must stay at the end. super end end_eval end property = G::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 G::PropertyUtils.add_prop(self, property) 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 = Object symbols = [] for param in params if param.is_a?(Class) klass = param elsif param.is_a?(Symbol) symbols << param elsif param.is_a?(Hash) # the meta hash. meta = param else raise "Error when defining property!" end end 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 = Object symbols = [] for param in params if param.is_a?(Class) klass = param elsif param.is_a?(Symbol) symbols << param elsif param.is_a?(Hash) # the meta hash. meta = param else raise "Error when defining property!" 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 = Object symbols = [] for param in params if param.is_a?(Class) klass = param elsif param.is_a?(Symbol) symbols << param elsif param.is_a?(Hash) # the meta hash. meta = param else raise "Error when defining property!" end end meta[:reader] = true meta[:writer] = true for symbol in symbols prop(klass, symbol, meta) end end # Attach metadata. # Guard against duplicates, no need to keep order. # This method uses closures :) #-- # gmosx: crappy implementation, recode. #++ def meta(key, val) self.module_eval <<-"end_eval", __FILE__, __LINE__ @@__meta[key] ||= [] @@__meta[key].delete_if { |v| val == v } @@__meta[key] << val end_eval end end