lib/glue/property.rb in glue-0.23.0 vs lib/glue/property.rb in glue-0.24.0
- old
+ new
@@ -1,457 +1,250 @@
-require 'mega/synchash'
-require 'mega/syncarray'
+require 'mega/annotation'
+require 'mega/inheritor'
+require 'mega/ohash'
-require 'nano/time/change'
+require 'glue/on_included'
+require 'og/entity'
+require 'glue/validation'
+require 'og/relation/all'
+require 'glue/aspects'
-require 'glue/attribute'
-require 'glue/flexob'
-
-module Glue
-
-# 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.
+# A convienience structure that holds property
+# metadata.
#--
-# TODO:
-# Perhaps a sync is needed in evals (!!!!)
+# TODO: reimplement type checking.
#++
class Property
-
- # If set to true, perform type checking on property set.
- # Useful when debugging.
-
- cattr_accessor :type_checking, false
-
- # The symbol of the property.
+ attr_accessor :hash
- attr_accessor :symbol
+ def initialize(hash)
+ @hash = hash
+ @hash = @hash.to_h unless @hash.is_a?(Hash)
+ end
- # The class of the property.
+ def [](key)
+ @hash[key]
+ end
+ alias_method :method_missing, :[]
- 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 :metadata
-
- # Support legacy code, will be deprecated.
-
- alias_method :meta, :metadata
- alias_method :meta=, :metadata=
-
- def initialize(symbol, klass, metadata = {})
- @symbol, @klass = symbol, klass
- @metadata = metadata
+ def []=(key, val)
+ @hash[key] = val
end
- def ==(other)
- return @symbol == other.symbol
+ def <=>(other)
+ @hash[:symbol] <=> other.hash[:symbol]
end
def to_s
- return @symbol.to_s
- end
+ @hash[:symbol].to_s
+ end
end
-# A collection of Property related utility methods.
+# Property related utils and helpers.
-module PropertyUtils
-
- # Add accessors to the properties to the given target
- # (Module or Class). For simplicity also create the
- # meta accessors.
+class Property
+ class << self
+
+ # Populate an object from a hash of values.
+ # This is a truly dangerous method.
#
- # [+target+]
- # The target class or module
- #--
- # gmosx: Perhaps we 'll optimize this in the future.
- #++
+ # === Options:
+ # * name
+ # * force_boolean
+
+ def populate_object(obj, values, options = {})
+ # If a class is passed create an instance.
+ obj = obj.new if obj.is_a?(Class)
- 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', SyncArray.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.
+ for prop in obj.class.properties.values
+ unless options[:all]
+ next if :oid == prop.symbol or prop.editor == :none # TODO check for real key.
+ end
+ prop_name = prop.symbol.to_s
- target.module_eval %{
- def self.__props
- @__props
- end
-
- def self.properties
- @__props
- end
-
- def self.__props=(props)
- @__props = props
- end
+ # See if there is an incoming request param for this prop.
+
+ if values.has_key?(prop_name)
- def self.__meta
- @__meta
- end
-
- def self.__meta=(meta)
- @__meta = meta
- end
+ # if incoming file then read contents into a string.
- def self.metadata
- @__meta
- end
- }
-
- if target.is_a?(Class)
-
- # 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
- }
-
+ prop_value = values[prop_name]
+ prop_value.read if prop_value.respond_to?(:read)
+ prop_value = prop_value.to_s unless prop_value.is_a?(Hash) or prop_value.is_a?(Array)
+
+ # If property is a Blob dont overwrite current property's data if "".
+
+ break if prop.klass == Og::Blob and prop_value.empty?
+
+ # Pass to property force.
+
+ obj.send("__force_#{prop_name}", prop_value)
+ elsif options[:force_boolean] and (prop.klass == TrueClass or prop.klass == FalseClass)
+ # Set a boolean property to false if it is not in the request.
+ # Only enabled if force_boolean == true.
+
+ obj.send("__force_#{prop_name}", 0)
else
-
- # Add some extra code for modules to append
- # their features to classes that include it.
+ # check if there is a hashed version of the property (in the form values[symbol.part])
- 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
+ property_parts = Hash.new
+ values.each do |k,v|
+ if k =~ /^#{prop_name}\./
+ part_key = k.sub("#{prop_name}.", "")
+ property_parts[part_key.to_sym] = values[k].to_s
end
- }
-
+ end
+
+ # pass the hashed version to __force_hash_ for processing.
+
+ obj.send("__force_hash_#{prop_name}", property_parts) unless property_parts.empty?
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
-
- # 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.
-
- target.meta :props_and_relations, prop
-
- # Precompile the property read/write methods
-
- s, klass = prop.symbol, prop.klass
-
- if prop.meta[:reader]
- target.module_eval %{
- def #{s}
- return @#{s}
+ if options[:assign_relations]
+ for rel in obj.class.relations
+ unless options[:all]
+ next if rel.options[:editor] == :none
end
- }
- end
-
- # gmosx: __force_xxx reuses xxx= to allow for easier
- # overrides.
-
- if prop.meta[:writer]
- code = %{
- #{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
+ rel_name = rel.name.to_s
- def __force_hash_#{s}(hash)
- }
+ # Renew the relations from values
+ # TODO, farms: custom callbacks for processing relations?
- case klass.name
- when Time.name
- code << %{
- ihash = {}
- hash = hash.map { |k, v| ihash[k.intern] = v.to_i }
- @#{s} = @#{s}.change(ihash)
- }
- end
+ if rel.kind_of? Og::RefersTo
+ if foreign_oid = values[rel_name]
+ foreign_oid = nil if foreign_oid == 'nil' or foreign_oid == 'none'
+ end
+ obj.send("__force_#{rel.foreign_key}", foreign_oid)
+ elsif rel.kind_of? Og::JoinsMany or rel.kind_of? Og::HasMany
+ # Empty current relationships.
- code << %{
- end
- }
-
- target.module_eval(code)
+ collection = obj.send(rel_name)
+ collection.remove_all
+
+ # Set new.
+
+ if values.has_key?(rel_name)
+ values[rel_name].each do |v|
+ child = rel.target_class[v.to_s.to_i]
+ collection << child
+ end
+ end
+ end
+ end
end
- end
- # Generates the property setter code. Can be overriden
- # to support extra functionality (example: markup)
+ return obj
+ end
- def self.prop_setter(prop)
- s = prop.symbol
-
+ def eval_helpers(writer, m, sym, klass)
+ return unless writer
+
code = %{
- def #{s}=(val)
+ def __force_#{sym}(val)
+ self.#{sym}=(} + case klass.name
+ when 'Fixnum': 'val.nil? ? nil : val.to_i'
+ when 'String': 'val.to_s'
+ when 'Float': 'val.nil? ? nil : val.to_f'
+ when 'Time': 'Time.parse(val.to_s)'
+ when 'TrueClass', 'FalseClass': '(val == "on" or val == "true") ? true : (val.to_i > 0)'
+ when 'Og::Blob': 'val'
+ else
+ 'val'
+ end + %{)
+ end
+
+ def __force_hash_#{sym}(hash)
}
-
- if Glue::Property.type_checking
- code << %{
- unless #{prop.klass} == val.class
- raise "Invalid type, expected '#{prop.klass}', is '\#\{val.class\}'."
- end
- }
+
+ case klass.name
+ when Time.name
+ code << %{
+ self.#{sym} = Time.local(hash[:year],hash[:month],hash[:day],hash[:hour],hash[:min])
+ }
end
code << %{
- @#{s} = val
- end
+ end
}
- return code
- end
+ m.module_eval(code)
+ end
- # Get the property metadata for the given symbol.
-
- 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
-
- 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).
-
- 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
-
- raise 'No symbols provided!' if symbols.empty?
-
- return meta, klass, symbols
- end
-
end
-end
+#--
+# Extend the default Module.
+#
+# The properties hash, keeps the property metadata as a hash to avoid
+# the null issue.
+#++
class Module
+ [ nil, :_reader, :_writer, :_accessor].each do |m|
+ writer = (m != :reader)
+ code = %{
+ def prop#{m} (*args)
+ inheritor(:properties, {}, :merge) unless @properties
- # 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)
+ args = args.flatten
+ harg = {}
+ while args.last.is_a?(Hash)
+ harg.update(args.pop)
+ 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
+ harg[:klass] = args.pop if args.last.is_a?(Class)
+ harg[:klass] ||= String
+
+ raise if args.empty? and harg.empty?
- Glue::PropertyUtils.add_prop(self, property)
-
- # gmosx: should be placed AFTER enchant!
+ if !args.empty?
+ undef_keys = args.select{ |a| !method_defined?(a) }
+ unless undef_keys.empty?
+ attr#{m} *undef_keys
+ end
+ args.each { |a|
+ a = a.to_sym
+ an = define_annotation(a, harg)
+ ah = an.to_h.merge(:symbol => a)
+ # gmosx: allow for duplicate declarations.
+ properties![a] = Property.new(ah)
+ Property.eval_helpers(#{writer}, self, a, harg[:klass])
+ }
+ else
+ undef_keys = harg.keys.select{ |a| !method_defined?(a) }
+ attribute#{m} *undef_keys
+ harg.each { |a,h|
+ a = a.to_sym
+ an = define_annotation(a, h)
+ ah = an.to_h.merge(:symbol => a)
+ # gmosx: allow for property redefinitions.
+ properties![a] = Property.new(ah)
+ Property.eval_helpers(#{writer}, self, a, harg[:klass])
+ }
+ end
+
+ Module.__add_prop_hook__(self)
+ end
+ }
- Glue::PropertyUtils.include_meta_mixins(self)
+ module_eval(code)
end
+ alias :property :prop_accessor
+
+ # NITRO specific!! leave blank in nano/mega.
+ # TODO: factor in eval_helpers into the hook!
- # 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
+ def self.__add_prop_hook__(m)
+ m.send(:include, Og::EntityMixin) unless m.ancestors.include?(Og::EntityMixin)
+ m.send(:include, Glue::Validation) unless m.ancestors.include?(Glue::Validation)
+ m.send(:include, Glue::Aspects) unless m.ancestors.include?(Glue::Aspects)
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)
-
- 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)
-
- 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
-
end
# * George Moschovitis <gm@navel.gr>
+# * Tom Sawyer <transfire@gmail.com>
+# * Chris Farmiloe <chris.farmiloe@farmiloe.com>