# code: # * George Moschovitis # design: # * Anastastios Koutoumanos # * Elias Karakoulakis # # (c) 2004 Navel, all rights reserved. # $Id: property.rb 185 2004-12-10 13:29:09Z 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: # Inject only the really needd methods into Module. # 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 declaratio, 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 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 unless self.methods.include?("__props") eval %{ # Properties # An array is used to enforce order. def __props @__props end def __props=(props) @__props = props end def __meta @__meta end def __meta=(meta) @__meta = meta end } end @__props = G::SafeArray.new() unless @__props property = G::Property.new(symbol, klass, meta) reader = meta[:reader] || true writer = writer || meta[:writer] || false __add_prop(property, reader, writer) 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 # Add the property # def __add_prop(prop, reader = true, writer = true) if idx = @__props.index(prop) # override in case of duplicates. Keep the order of the props. @__props[idx] = prop else @__props << prop end # Precompile the property read/write methods s, klass = prop.symbol, prop.klass if reader module_eval %{ def #{s} return @#{s} end } end # gmosx: __force_xxx reuses xxx= to allow for easier # overrides. if writer module_eval %{ def #{s}=(val) @#{s} = val end 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 # Attach metadata # def meta(key, val) @__meta = G::SafeHash.new unless @__meta @__meta[key] = [] unless @__meta[key] # guard against duplicates, no need to keep order. @__meta[key].delete_if { |v| val == v } @__meta[key] << val end # This method is typically called before including other # modules to preserve properties order. # def inherit_meta(mod = superclass) # concat props. if mod.__props @__props = G::SafeArray.new unless @__props mod.__props.each { |p| __add_prop(p) } end # concat metadata if mod.__meta mod.__meta.each { |k, val| val.each { |v| meta(k, v) } if val } end end end