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|