Module: Gorillib::Model

Extended by:
Concern
Included in:
Builder, Field
Defined in:
lib/gorillib/model/base.rb,
lib/gorillib/model/lint.rb,
lib/gorillib/model/field.rb,
lib/gorillib/model/errors.rb,
lib/gorillib/model/defaults.rb,
lib/gorillib/model/validate.rb,
lib/gorillib/model/doc_string.rb,
lib/gorillib/model/named_schema.rb,
lib/gorillib/model/schema_magic.rb,
lib/gorillib/model/serialization.rb,
lib/gorillib/model/active_model_shim.rb,
lib/gorillib/model/positional_fields.rb,
lib/gorillib/model/serialization/csv.rb,
lib/gorillib/model/serialization/tsv.rb,
lib/gorillib/model/serialization/json.rb,
lib/gorillib/model/active_model_naming.rb,
lib/gorillib/model/serialization/lines.rb,
lib/gorillib/model/active_model_conversion.rb

Overview

Provides a set of class methods for defining a field schema and instance methods for reading and writing attributes.

Examples:

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">

Defined Under Namespace

Modules: ActiveModelShim, ClassMethods, Conversion, DocString, Error, Lint, LoadFromCsv, LoadFromJson, LoadFromTsv, LoadLines, NamedSchema, Naming, PositionalFields, Validate Classes: ConflictingPositionError, Field, Name, RawDataMismatchError, SimpleCollectionField, UnknownFieldError

Instance Method Summary collapse

Methods included from Concern

append_features, extended, included

Instance Method Details

#==(other) ⇒ true, false

Two models are equal if they have the same class and their attributes are equal.

Examples:

Compare for equality.

model == other

Parameters:

Returns:

  • (true, false)

    True if attributes are equal and other is instance of the same Class



187
188
189
190
# 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) ⇒ Object



18
# File 'lib/gorillib/model/serialization.rb', line 18

def as_json(*args) to_wire(*args) ; end

#attribute_set?(field_name) ⇒ true, false

True if the attribute is set.

Note that an attribute can have the value nil but be set.

Parameters:

Returns:

  • (true, false)

Raises:

  • (UnknownAttributeError)

    if the attribute is unknown



174
175
176
# File 'lib/gorillib/model/base.rb', line 174

def attribute_set?(field_name)
  instance_variable_defined?("@#{field_name}")
end

#attribute_valuesArray[Object]

Returns all the attributes, in field order, with nil where unset.

Returns:

  • (Array[Object])

    all the attributes, in field order, with nil where unset



41
42
43
# File 'lib/gorillib/model/base.rb', line 41

def attribute_values
  self.class.field_names.map{|fn| read_attribute(fn) }
end

#attributes{Symbol => Object}

Returns a Hash of all attributes

Examples:

Get attributes

person.attributes # => { :name => "Emmet Brown", :title => "Dr" }

Returns:



32
33
34
35
36
37
38
# 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{Symbol => Object}

Returns a Hash of all attributes that have been set

Examples:

Get attributes (smurfette is unarmed)

smurfette.attributes         # => { :name => "Smurfette", :weapon => nil }
smurfette.compact_attributes # => { :name => "Smurfette" }

Returns:



52
53
54
55
56
57
# 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) ⇒ Object



86
87
88
89
# File 'lib/gorillib/model/base.rb', line 86

def handle_extra_attributes(attrs)
  @_extra_attributes ||= Hash.new
  @_extra_attributes.merge!(attrs)
end

#initialize(*args, &block) ⇒ Object



21
22
23
24
# File 'lib/gorillib/model/base.rb', line 21

def initialize(*args, &block)
  attrs = self.class.attrs_hash_from_args(args)
  receive!(attrs, &block)
end

#inspectString

override to_inspectable (not this) in your descendant class

Returns:

  • (String)

    Human-readable presentation of the attributes



194
195
196
197
198
199
200
201
# 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_compactObject



207
208
209
# File 'lib/gorillib/model/base.rb', line 207

def inspect_compact
  str = "#<#{self.class.name.to_s}>"
end

#read_attribute(field_name) ⇒ Object

Read a value from the model's attributes.

Examples:

Reading an attribute

person.read_attribute(:name)

Parameters:

Returns:

  • (Object)

    The value of the attribute, or nil if it is unset

Raises:

  • (UnknownAttributeError)

    if the attribute is unknown



120
121
122
123
124
125
126
127
# 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) ⇒ Object

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.

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

Examples:

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'

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

Parameters:

Returns:



86
87
88
89
90
91
92
# 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 = {}) ⇒ nil

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.

Parameters:

  • hsh ({Symbol => Object}) (defaults to: {})

    The values to receive

Returns:

  • (nil)

    nothing



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# 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_inspectableString

assembles just the given attributes into the inspect string.

Returns:

  • (String)

    Human-readable presentation of the attributes



213
214
215
# File 'lib/gorillib/model/base.rb', line 213

def to_inspectable
  compact_attributes
end

#to_json(options = {}) ⇒ Object



20
21
22
# File 'lib/gorillib/model/serialization.rb', line 20

def to_json(options={})
  MultiJson.dump(to_wire(options), options)
end

#to_sObject



203
204
205
# File 'lib/gorillib/model/base.rb', line 203

def to_s
  inspect
end

#to_tsv(options = {}) ⇒ Object



24
25
26
27
28
# File 'lib/gorillib/model/serialization.rb', line 24

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 = {}) ⇒ Object



12
13
14
15
16
17
# File 'lib/gorillib/model/serialization.rb', line 12

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) ⇒ Object

Unset an attribute. Subsequent reads of the attribute will return nil, and attribute_set? for that field will return false.

Examples:

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 ]

Parameters:

Returns:

  • (Object)

    the former value if it was set, nil if it was unset

Raises:

  • (UnknownAttributeError)

    if the attribute is unknown



156
157
158
159
160
161
162
163
164
# 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) ⇒ Gorillib::Model

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.

Parameters:

Returns:



99
100
101
102
103
104
105
106
107
108
109
# 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) ⇒ Object

Write the value of a single attribute.

Examples:

Writing an attribute

person.write_attribute(:name, "Benjamin")

Parameters:

  • field_name (String, Symbol, #to_s)

    Name of the attribute to update.

  • val (Object)

    The value to set for the attribute.

Returns:

  • (Object)

    the attribute's value

Raises:

  • (UnknownAttributeError)

    if the attribute is unknown



139
140
141
# File 'lib/gorillib/model/base.rb', line 139

def write_attribute(field_name, val)
  instance_variable_set("@#{field_name}", val)
end