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

- old
+ new

@@ -1,6 +1,5 @@ - module Gorillib # Provides a set of class methods for defining a field schema and instance # methods for reading and writing attributes. # @@ -17,10 +16,20 @@ # puts person #=> #<Person name="Bob Dobbs, Jr"> # 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 + receive!(attrs, &block) + end + # Returns a Hash of all attributes # # @example Get attributes # person.attributes # => { :name => "Ben Poweski" } # @@ -30,10 +39,15 @@ hsh[fn] = read_attribute(fn) hsh end end + # @return [Array[Object]] all the attributes, in field order, with `nil` where unset + def attribute_values + self.class.field_names.map{|fn| read_attribute(fn) } + end + # Returns a Hash of all attributes *that have been set* # # @example Get attributes (smurfette is unarmed) # smurfette.attributes # => { :name => "Smurfette", :weapon => nil } # smurfette.compact_attributes # => { :name => "Smurfette" } @@ -55,18 +69,23 @@ # 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 def receive!(hsh={}) - if hsh.respond_to?(:attributes) then hsh = hsh.attributes ; end - Gorillib::Model::Validate.hashlike!("attributes hash for #{self.inspect}", hsh) - hsh = hsh.symbolize_keys - self.class.fields.each do |field_name, field| - next unless hsh.has_key?(field_name) - self.public_send(:"receive_#{field_name}", hsh[field_name]) + if hsh.respond_to?(:attributes) + hsh = hsh.attributes + else + Gorillib::Model::Validate.hashlike!(hsh){ "attributes hash for #{self.inspect}" } + hsh = hsh.dup end - handle_extra_attributes( hsh.reject{|field_name,val| self.class.has_field?(field_name) } ) + 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 end def handle_extra_attributes(attrs) @extra_attributes ||= Hash.new @@ -81,16 +100,16 @@ # # @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!("attributes hash", hsh) - self.class.fields.each do |attr, field| - if hsh.has_key?(attr) then val = hsh[attr] - elsif hsh.has_key?(attr.to_s) then val = hsh[attr.to_s] + Gorillib::Model::Validate.hashlike!(hsh){ "attributes hash 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(attr, val) + write_attribute(field_name, val) end self end # Read a value from the model's attributes. @@ -101,13 +120,13 @@ # @param [String, Symbol, #to_s] field_name Name of the attribute to get. # # @raise [UnknownAttributeError] if the attribute is unknown # @return [Object] The value of the attribute, or nil if it is unset def read_attribute(field_name) - check_field(field_name) - if instance_variable_defined?("@#{field_name}") - instance_variable_get("@#{field_name}") + attr_name = "@#{field_name}" + if instance_variable_defined?(attr_name) + instance_variable_get(attr_name) else read_unset_attribute(field_name) end end @@ -120,11 +139,10 @@ # @param [Object] val The value to set for the attribute. # # @raise [UnknownAttributeError] if the attribute is unknown # @return [Object] the attribute's value def write_attribute(field_name, val) - check_field(field_name) instance_variable_set("@#{field_name}", val) end # Unset an attribute. Subsequent reads of the attribute will return `nil`, # and `attribute_set?` for that field will return false. @@ -138,11 +156,10 @@ # @param [String, Symbol, #to_s] field_name Name of the attribute to unset. # # @raise [UnknownAttributeError] if the attribute is unknown # @return [Object] the former value if it was set, nil if it was unset def unset_attribute(field_name) - check_field(field_name) if instance_variable_defined?("@#{field_name}") val = instance_variable_get("@#{field_name}") remove_instance_variable("@#{field_name}") return val else @@ -157,11 +174,10 @@ # @param [String, Symbol, #to_s] field_name Name of the attribute to check. # # @raise [UnknownAttributeError] if the attribute is unknown # @return [true, false] def attribute_set?(field_name) - check_field(field_name) instance_variable_defined?("@#{field_name}") end # Two models are equal if they have the same class and their attributes # are equal. @@ -186,47 +202,42 @@ # 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 << " " - str << attrs.map do |attr, val| - "#{attr}=#{val.is_a?(Gorillib::Model) || val.is_a?(Gorillib::Collection) ? val.inspect(false) : val.inspect}" + 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 << ">" end private :inspect_helper protected - # @return [true] if the field exists - # @raise [UnknownFieldError] if the field is missing - def check_field(field_name) - return true if self.class.has_field?(field_name) - raise UnknownFieldError, "unknown field: #{field_name} for #{self}" - end - module ClassMethods + # + # A readable handle for this field + # def typename - Gorillib::Inflector.underscore(self.name).gsub(%r{/}, '.') + @typename ||= Gorillib::Inflector.underscore(self.name||'anon').gsub(%r{/}, '.') end # # 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) - Gorillib::Model::Validate.hashlike!("attributes for #{self}", attrs) + # + Gorillib::Model::Validate.hashlike!(attrs){ "attributes for #{self.inspect}" } klass = attrs.has_key?(:_type) ? Gorillib::Factory(attrs[:_type]) : self - warn "factory #{self} doesn't match type specified in #{attrs}" unless klass <= self - obj = klass.new - obj.receive!(attrs, &block) - obj + 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 # # For each field that is defined, a getter and setter will be added as @@ -258,16 +269,16 @@ @_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.to_sym) + fields.has_key?(field_name) end # @return [Array<Symbol>] The attribute names def field_names - fields.keys + @_field_names ||= fields.keys end # @return Class name and its attributes # # @example Inspect the model's definition. @@ -282,11 +293,12 @@ # 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, '@_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) @@ -304,11 +316,12 @@ 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) + 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|