lib/gorillib/model/base.rb in gorillib-0.4.1pre vs lib/gorillib/model/base.rb in gorillib-0.4.2pre

- old
+ new

@@ -17,23 +17,18 @@ # module Model extend Gorillib::Concern def initialize(*args, &block) - attrs = args.extract_options! - if args.present? - fns = self.class.field_names - ArgumentError.check_arity!(args, 0..fns.length) - attrs = attrs.merge(Hash[ fns[0..(args.length-1)].zip(args) ]) - end + attrs = self.class.attrs_hash_from_args(args) receive!(attrs, &block) end # Returns a Hash of all attributes # # @example Get attributes - # person.attributes # => { :name => "Ben Poweski" } + # person.attributes # => { :name => "Emmet Brown", :title => "Dr" } # # @return [{Symbol => Object}] The Hash of all attributes def attributes self.class.field_names.inject(Hash.new) do |hsh, fn| hsh[fn] = read_attribute(fn) @@ -67,31 +62,31 @@ # # Use `#receive!` to accept 'dirty' data -- from JSON, from a nested hash, # or some such. Use `#update_attributes` if your data is already type safe. # # @param [{Symbol => Object}] hsh The values to receive - # @return [Gorillib::Model] the object itself + # @return [nil] nothing def receive!(hsh={}) if hsh.respond_to?(:attributes) - hsh = hsh.attributes + hsh = hsh.compact_attributes else - Gorillib::Model::Validate.hashlike!(hsh){ "attributes hash for #{self.inspect}" } + Gorillib::Model::Validate.hashlike!(hsh){ "attributes for #{self.inspect}" } hsh = hsh.dup end self.class.field_names.each do |field_name| if hsh.has_key?(field_name) then val = hsh.delete(field_name) elsif hsh.has_key?(field_name.to_s) then val = hsh.delete(field_name.to_s) else next ; end self.send("receive_#{field_name}", val) end handle_extra_attributes(hsh) - self + nil end def handle_extra_attributes(attrs) - @extra_attributes ||= Hash.new - @extra_attributes.merge!(attrs) + @_extra_attributes ||= Hash.new + @_extra_attributes.merge!(attrs) end # # Accept the given attributes, adopting each value directly. # @@ -100,11 +95,11 @@ # # @param [{Symbol => Object}] hsh The values to update with # @return [Gorillib::Model] the object itself def update_attributes(hsh) if hsh.respond_to?(:attributes) then hsh = hsh.attributes ; end - Gorillib::Model::Validate.hashlike!(hsh){ "attributes hash for #{self.inspect}" } + Gorillib::Model::Validate.hashlike!(hsh){ "attributes for #{self.inspect}" } self.class.field_names.each do |field_name| if hsh.has_key?(field_name) then val = hsh[field_name] elsif hsh.has_key?(field_name.to_s) then val = hsh[field_name.to_s] else next ; end write_attribute(field_name, val) @@ -191,28 +186,31 @@ def ==(other) return false unless other.instance_of?(self.class) attributes == other.attributes end - # override inspect_helper (not this) in your descendant class + # override to_inspectable (not this) in your descendant class # @return [String] Human-readable presentation of the attributes - def inspect(detailed=true) - inspect_helper(detailed, compact_attributes) + def inspect + str = '#<' << self.class.name.to_s + attrs = to_inspectable + if attrs.present? + str << '(' << attrs.map{|attr, val| "#{attr}=#{val.respond_to?(:inspect_compact) ? val.inspect_compact : val.inspect}" }.join(", ") << ')' + end + str << '>' end + def inspect_compact + str = "#<#{self.class.name.to_s}>" + end + # assembles just the given attributes into the inspect string. # @return [String] Human-readable presentation of the attributes - def inspect_helper(detailed, attrs) - str = "#<" << self.class.name.to_s - if detailed && attrs.present? - str << " " << attrs.map do |attr, val| - "#{attr}=#{val.is_a?(Gorillib::Model) || val.is_a?(Gorillib::GenericCollection) ? val.inspect(false) : val.inspect}" - end.join(", ") - end - str << ">" + def to_inspectable + compact_attributes end - private :inspect_helper + private :to_inspectable protected module ClassMethods @@ -227,120 +225,36 @@ # Receive external data, type-converting and creating contained models as necessary # # @return [Gorillib::Model] the new object def receive(attrs={}, &block) return nil if attrs.nil? - return attrs if attrs.is_a?(self) + return attrs if native?(attrs) # Gorillib::Model::Validate.hashlike!(attrs){ "attributes for #{self.inspect}" } klass = attrs.has_key?(:_type) ? Gorillib::Factory(attrs[:_type]) : self warn "factory #{klass} is not a type of #{self} as specified in #{attrs}" unless klass <= self # klass.new(attrs, &block) end - # Defines a new field + # A `native` object does not need any transformation; it is accepted directly. + # By default, an object is native if it `is_a?` this class # - # For each field that is defined, a getter and setter will be added as - # an instance method to the model. An Field instance will be added to - # result of the fields class method. - # - # @example - # field :height, Integer - # - # @param [Symbol] field_name The field name. Must start with `[A-Za-z_]` and subsequently contain only `[A-Za-z0-9_]` - # @param [Class] type The field's type (required) - # @option options [String] doc Documentation string for the field (optional) - # @option options [Proc, Object] default Default value, or proc that instance can evaluate to find default value - # - # @return Gorillib::Model::Field - def field(field_name, type, options={}) - options = options.symbolize_keys - field_type = options.delete(:field_type){ ::Gorillib::Model::Field } - fld = field_type.new(field_name, type, self, options) - @_own_fields[fld.name] = fld - _reset_descendant_fields - fld.send(:inscribe_methods, self) - fld + # @param obj [Object] the object that will be received + # @return [true, false] true if the item does not need conversion + def native?(obj) + obj.is_a?(self) end - # @return [{Symbol => Gorillib::Model::Field}] - def fields - return @_fields if defined?(@_fields) - @_fields = ancestors.reverse.inject({}){|acc, ancestor| acc.merge!(ancestor.try(:_own_fields) || {}) } - end - - # @return [true, false] true if the field is defined on this class - def has_field?(field_name) - fields.has_key?(field_name) - end - - # @return [Array<Symbol>] The attribute names - def field_names - @_field_names ||= fields.keys - end - # @return Class name and its attributes # # @example Inspect the model's definition. # Person.inspect #=> Person[first_name, last_name] def inspect - "#{self.name || 'anon'}[#{ field_names.join(", ") }]" + "#{self.name || 'anon'}[#{ field_names.join(",") }]" end + def inspect_compact() self.name || inspect ; end - protected - - attr_reader :_own_fields - - # Ensure that classes inherit all their parents' fields, even if fields - # are added after the child class is defined. - def _reset_descendant_fields - ObjectSpace.each_object(::Class) do |klass| - klass.__send__(:remove_instance_variable, '@_fields') if (klass <= self) && klass.instance_variable_defined?('@_fields') - klass.__send__(:remove_instance_variable, '@_field_names') if (klass <= self) && klass.instance_variable_defined?('@_field_names') - end - end - - # define the reader method `#foo` for a field named `:foo` - def define_attribute_reader(field_name, field_type, visibility) - define_meta_module_method(field_name, visibility) do - begin - read_attribute(field_name) - rescue StandardError => err ; err.polish("#{self.class}.#{field_name}") rescue nil ; raise ; end - end - end - - # define the writer method `#foo=` for a field named `:foo` - def define_attribute_writer(field_name, field_type, visibility) - define_meta_module_method("#{field_name}=", visibility) do |val| - write_attribute(field_name, val) - end - end - - # define the present method `#foo?` for a field named `:foo` - def define_attribute_tester(field_name, field_type, visibility) - field = fields[field_name] - define_meta_module_method("#{field_name}?", visibility) do - attribute_set?(field_name) || field.has_default? - end - end - - def define_attribute_receiver(field_name, field_type, visibility) - define_meta_module_method("receive_#{field_name}", visibility) do |val| - begin - val = field_type.receive(val) - write_attribute(field_name, val) - self - rescue StandardError => err ; err.polish("#{self.class}.#{field_name} type #{type} on #{val}") rescue nil ; raise ; end - end - end - - def inherited(base) - base.instance_eval do - @_own_fields ||= {} - end - super - end end self.included do |base| base.instance_eval do extend Gorillib::Model::NamedSchema