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

new(*args, &block) click to toggle source
# 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

==(other) click to toggle source

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
as_json(*args) click to toggle source
# File lib/gorillib/model/serialization.rb, line 22
def as_json(*args) to_wire(*args) ; end
attribute_set?(field_name) click to toggle source

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
attribute_values() click to toggle source

@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
attributes() click to toggle source

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
compact_attributes() click to toggle source

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
handle_extra_attributes(attrs) click to toggle source
# File lib/gorillib/model/base.rb, line 86
def handle_extra_attributes(attrs)
  @_extra_attributes ||= Hash.new
  @_extra_attributes.merge!(attrs)
end
has_default?() click to toggle source

@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
inspect() click to toggle source

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
inspect_compact() click to toggle source
# File lib/gorillib/model/base.rb, line 207
def inspect_compact
  str = "#<#{self.class.name.to_s}>"
end
read_attribute(field_name) click to toggle source

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
read_unset_attribute(field_name) click to toggle source

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
receive!(hsh={}) click to toggle source

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
to_inspectable() click to toggle source

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
to_json(options={}) click to toggle source
# File lib/gorillib/model/serialization.rb, line 24
def to_json(options={})
  MultiJson.dump(to_wire(options), options)
end
to_s() click to toggle source
# File lib/gorillib/model/base.rb, line 203
def to_s
  inspect
end
to_tsv(options={}) click to toggle source
# 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
to_wire(options={}) click to toggle source
# 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_attribute(field_name) click to toggle source

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
update_attributes(hsh) click to toggle source

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_attribute(field_name, val) click to toggle source

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

attribute_default(field) click to toggle source

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