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