# = Properties support # # 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 (!!!!) # # code: gmosx, drak, ekarak # # (c) 2004 Navel, all rights reserved. # $Id: properties.rb 71 2004-10-18 10:50:22Z gmosx $ require "n/utils/array" require "n/utils/hash" module N # Property Metadata # # === Design: # - add default value in the extra sql # 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 # extra sql options for the property. if set, # it must contain the sql_type for this class, the # default is overriden. attr_accessor :sql def initialize(symbol, klass, sql = nil) @symbol, @klass = symbol, klass @sql = sql @name = @symbol.to_s() end def ==(other) return @symbol == other.symbol end end end # module class Module # Properties # An array is used to enforce order. attr_accessor :__props # Metadata # A hash of module metadata. attr_accessor :__meta # Define a property (== typed attribute) # based on draks property redesign! # # ex: # prop_accessor String, :title, :body # prop_accessor String, "char(10) NOT NULL", :title, :body # def prop_accessor(*params) klass = params.shift # if the next param is a String, it is # extra sql metadata. if params.first.is_a?(String) sql = params.shift else sql = nil end # the rest of params are symbols @__props = N::SafeArray.new() unless @__props params.each { |s| add_prop(N::Property.new(s, klass, sql)) } end # to be compatible with ruby parlance alias_method :prop, :prop_accessor # Add the property # Optimize this # def add_prop(prop) 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 module_eval %{ def #{s} return @#{s} end def #{s}=(val) @#{s} = val end def __force_#{s}(val) @#{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 # Attach metadata # def meta(key, val) @__meta = N::SafeHash.new unless @__meta add_meta(key, val) end # Add the metadata # TODO: Optimize this # def add_meta(key, val) @__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 = N::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 # Override the default include method to also include the metadata. # WARNING: can cause infinite loop if overriden again! # alias_method :old_include, :include # A new version of include that includes the properties too. # def include(*modules) old_include(*modules) for m in modules inherit_meta(m) end end end