module Gorillib::Model
Provides a set of class methods for defining a field schema and instance methods for reading and writing attributes.
@example Usage
class Person include Gorillib::Model field :name, String, :doc => 'Full name of person' field :height, Float, :doc => 'Height in meters' end person = Person.new person.name = "Bob Dobbs, Jr" puts person #=> #<Person name="Bob Dobbs, Jr">
Constants
- VERSION
Public Class Methods
# File lib/gorillib/model/base.rb, line 21 def initialize(*args, &block) attrs = self.class.attrs_hash_from_args(args) receive!(attrs, &block) end
Public Instance Methods
Two models are equal if they have the same class and their attributes are equal.
@example Compare for equality.
model == other
@param [Gorillib::Model, Object] other The other model to compare
@return [true, false] True if attributes are equal and other is instance of the same Class
# File lib/gorillib/model/base.rb, line 187 def ==(other) return false unless other.instance_of?(self.class) attributes == other.attributes end
# File lib/gorillib/model/serialization.rb, line 22 def as_json(*args) to_wire(*args) ; end
True if the attribute is set.
Note that an attribute can have the value nil but be set.
@param [String, Symbol, to_s
] field_name Name of the attribute to check.
@raise [UnknownAttributeError] if the attribute is unknown @return [true, false]
# File lib/gorillib/model/base.rb, line 174 def attribute_set?(field_name) instance_variable_defined?("@#{field_name}") end
@return [Array] all the attributes, in field order, with ‘nil` where unset
# File lib/gorillib/model/base.rb, line 41 def attribute_values self.class.field_names.map{|fn| read_attribute(fn) } end
Returns a Hash
of all attributes
@example Get attributes
person.attributes # => { :name => "Emmet Brown", :title => "Dr" }
@return [{Symbol => Object}] The Hash
of all attributes
# File lib/gorillib/model/base.rb, line 32 def attributes self.class.field_names.inject(Hash.new) do |hsh, fn| # hsh[fn] = attribute_set?(fn) ? read_attribute(fn) : nil hsh[fn] = read_attribute(fn) hsh end 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" }
@return [{Symbol => Object}] The Hash
of all set attributes
# File lib/gorillib/model/base.rb, line 52 def compact_attributes self.class.field_names.inject(Hash.new) do |hsh, fn| hsh[fn] = read_attribute(fn) if attribute_set?(fn) hsh end end
# File lib/gorillib/model/base.rb, line 86 def handle_extra_attributes(attrs) @_extra_attributes ||= Hash.new @_extra_attributes.merge!(attrs) end
@return [true, false] true if the field has a default value/proc set
# File lib/gorillib/model/defaults.rb, line 8 def has_default? attribute_set?(:default) end
override to_inspectable
(not this) in your descendant class @return [String] Human-readable presentation of the attributes
# File lib/gorillib/model/base.rb, line 194 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
# File lib/gorillib/model/base.rb, line 207 def inspect_compact str = "#<#{self.class.name.to_s}>" end
Read a value from the model’s attributes.
@example Reading an attribute
person.read_attribute(:name)
@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
# File lib/gorillib/model/base.rb, line 120 def read_attribute(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
This is called by ‘read_attribute` if an attribute is unset; you should not call this directly. You might use this to provide defaults, or lazy access, or layered resolution.
Once a non-nil default value has been read, it is **fixed on the field**; this method will not be called again, and ‘attribute_set?(…)` will return `true`.
@example values are fixed on read
class Defaultable include Gorillib::Model field :timestamp, Integer, default: ->{ Time.now } end dd = Defaultable.new dd.attribute_set?(:timestamp) # => false dd.timestamp # => '2012-01-02 12:34:56 CST' dd.attribute_set?(:timestamp) # => true # The block is *not* re-run -- the time is the same dd.timestamp # => '2012-01-02 12:34:56 CST'
@example If the default is a literal nil it is set as normal:
Defaultable.field :might_be_nil, String, default: nil dd.attribute_set?(:might_be_nil) # => false dd.might_be_nil # => nil dd.attribute_set?(:might_be_nil) # => true
If the default is generated from a block (or anything but a literal nil), no default is set:
Defaultable.field :might_be_nil, String, default: ->{ puts 'ran!'; some_other_value ? some_other_value.reverse : nil } Defaultable.field :some_other_value, String dd = Defaultable.new dd.attribute_set?(:might_be_nil) # => false dd.might_be_nil # => nil 'ran!' # block was run dd.might_be_nil # => nil 'ran!' # block was run again dd.some_other_val = 'hello' dd.might_be_nil # => 'olleh' 'ran!' # block was run again, and set a value this time dd.some_other_val = 'goodbye' dd.might_be_nil # => 'olleh' # block was not run again
@param [String, Symbol, to_s
] field_name Name of the attribute to unset. @return [Object] The new value
# File lib/gorillib/model/defaults.rb, line 86 def read_unset_attribute(field_name) field = self.class.fields[field_name] or return nil return unless field.has_default? val = attribute_default(field) return nil if val.nil? && (not field.default.nil?) # don't write nil unless intent is clearly to have default nil write_attribute(field.name, val) end
Accept the given attributes, converting each value to the appropriate type, constructing included models and collections, and other triggers as defined.
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 [nil] nothing
# File lib/gorillib/model/base.rb, line 69 def receive!(hsh={}) if hsh.respond_to?(:attributes) hsh = hsh.compact_attributes else 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) nil end
assembles just the given attributes into the inspect string. @return [String] Human-readable presentation of the attributes
# File lib/gorillib/model/base.rb, line 213 def to_inspectable compact_attributes end
# File lib/gorillib/model/serialization.rb, line 24 def to_json(options={}) MultiJson.dump(to_wire(options), options) end
# File lib/gorillib/model/base.rb, line 203 def to_s inspect end
# File lib/gorillib/model/serialization.rb, line 28 def to_tsv(options={}) attributes.map do |key, attr| attr.respond_to?(:to_wire) ? attr.to_wire(options) : attr end.join("\t") end
# File lib/gorillib/model/serialization.rb, line 16 def to_wire(options={}) compact_attributes.merge(:_type => self.class.typename).inject({}) do |acc, (key,attr)| acc[key] = attr.respond_to?(:to_wire) ? attr.to_wire(options) : attr acc end end
Unset an attribute. Subsequent reads of the attribute will return ‘nil`, and `attribute_set?` for that field will return false.
@example Unsetting an attribute
obj.write_attribute(:foo, nil) [ obj.read_attribute(:foo), obj.attribute_set?(:foo) ] # => [ nil, true ] person.unset_attribute(:height) [ obj.read_attribute(:foo), obj.attribute_set?(:foo) ] # => [ nil, false ]
@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
# File lib/gorillib/model/base.rb, line 156 def unset_attribute(field_name) if instance_variable_defined?("@#{field_name}") val = instance_variable_get("@#{field_name}") remove_instance_variable("@#{field_name}") return val else return nil end end
Accept the given attributes, adopting each value directly.
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 update with @return [Gorillib::Model] the object itself
# File lib/gorillib/model/base.rb, line 99 def update_attributes(hsh) if hsh.respond_to?(:attributes) then hsh = hsh.attributes ; end 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) end self end
Write the value of a single attribute.
@example Writing an attribute
person.write_attribute(:name, "Benjamin")
@param [String, Symbol, to_s
] field_name Name of the attribute to update. @param [Object] val The value to set for the attribute.
@raise [UnknownAttributeError] if the attribute is unknown @return [Object] the attribute’s value
# File lib/gorillib/model/base.rb, line 139 def write_attribute(field_name, val) instance_variable_set("@#{field_name}", val) end
Protected Instance Methods
the actual default value to assign to the attribute
# File lib/gorillib/model/defaults.rb, line 98 def attribute_default(field) return unless field.has_default? val = field.default case when val.is_a?(Proc) && (val.arity == 0) self.instance_exec(&val) when val.is_a?(UnboundMethod) && (val.arity == 0) val.bind(self).call when val.respond_to?(:call) val.call(self, field.name) else val.try_dup end end