Module: Receiver

Defined in:
lib/gorillib/receiver.rb,
lib/gorillib/receiver/tree_diff.rb,
lib/gorillib/receiver/validations.rb,
lib/gorillib/receiver/acts_as_hash.rb,
lib/gorillib/receiver/acts_as_loadable.rb,
lib/gorillib/receiver/active_model_shim.rb

Overview

Receiver lets you describe complex (even recursive!) actively-typed data models that

  • are creatable or assignable from static data structures

  • perform efficient type conversion when assigning from a data structure,

  • but with nothing in the way of normal assignment or instantiation

  • and no requirements on the initializer

    class Tweet
      include Receiver
      rcvr_accessor :id,           Integer
      rcvr_accessor :user_id,      Integer
      rcvr_accessor :created_at,   Time
    end
    p Tweet.receive(:id => "7", :user_id => 9, :created_at => "20101231010203" )
     # => #<Tweet @id=7, @user_id=9, @created_at=2010-12-31 07:02:03 UTC>
    

You can override receive behavior in a straightforward and predictable way:

class TwitterUser
  include Receiver
  rcvr_accessor :id,           Integer
  rcvr_accessor :screen_name,  String
  rcvr_accessor :follower_ids, Array, :of => Integer
  # accumulate unique follower ids
  def receive_follower_ids(arr)
    @follower_ids = (@follower_ids||[]) + arr.map(&:to_i)
    @follower_ids.uniq!
  end
end

The receiver pattern works naturally with inheritance:

class TweetWithUser < Tweet
  rcvr_accessor :user, TwitterUser
  after_receive do |hsh|
    self.user_id = self.user.id if self.user
  end
end
p TweetWithUser.receive(:id => 8675309, :created_at => "20101231010203", :user => { :id => 24601, :screen_name => 'bob', :follower_ids => [1, 8, 3, 4] })
 => #<TweetWithUser @id=8675309, @created_at=2010-12-31 07:02:03 UTC, @user=#<TwitterUser @id=24601, @screen_name="bob", @follower_ids=[1, 8, 3, 4]>, @user_id=24601>

TweetWithUser was able to add another receiver, applicable only to itself and its subclasses.

The receive method works well with sparse data – you can accumulate attributes without trampling formerly set values:

tw = Tweet.receive(:id => "7", :user_id => 9 )
p tw
# => #<Tweet @id=7, @user_id=9>

tw.receive!(:created_at => "20101231010203" )
p tw
# => #<Tweet @id=7, @user_id=9, @created_at=2010-12-31 07:02:03 UTC>

Note the distinction between an explicit nil field and a missing field:

tw.receive!(:user_id => nil, :created_at => "20090506070809" )
p tw
# => #<Tweet @id=7, @user_id=nil, @created_at=2009-05-06 12:08:09 UTC>

There are helpers for default and required attributes:

class Foo
  include Receiver
  rcvr_accessor :is_reqd,     String, :required => true
  rcvr_accessor :also_reqd,   String, :required => true
  rcvr_accessor :has_default, String, :default => 'hello'
end
foo_obj = Foo.receive(:is_reqd => "hi")
# => #<Foo:0x00000100bd9740 @is_reqd="hi" @has_default="hello">
foo_obj.missing_attrs
# => [:also_reqd]

Defined Under Namespace

Modules: ActiveModelShim, ActsAsHash, ActsAsLoadable, ClassMethods

Constant Summary collapse

RECEIVER_BODIES =
{}
TYPE_ALIASES =
{
  :null    => NilClass,
  :boolean => Boolean,
  :string  => String,  :bytes   => String,
  :symbol  => Symbol,
  :int     => Integer, :integer => Integer,  :long    => Integer,
  :time    => Time,    :date    => Date,
  :float   => Float,   :double  => Float,
  :hash    => Hash,    :map     => Hash,
  :array   => Array,
}

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object

set up receiver attributes, and bring in methods from the ClassMethods module at class-level



388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/gorillib/receiver.rb', line 388

def self.included base
  base.class_eval do
    unless method_defined?(:receiver_attrs)
      class_attribute :receiver_attrs
      class_attribute :receiver_attr_names
      class_attribute :after_receivers
      self.receiver_attrs      = {} # info about the attr
      self.receiver_attr_names = [] # ordered set of attr names
      self.after_receivers     = [] # blocks to execute following receive!
      extend ClassMethods
    end
  end
end

Instance Method Details

#attr_set?(attr) ⇒ Boolean

true if the attr is a receiver variable and it has been set

Returns:



144
145
146
# File 'lib/gorillib/receiver.rb', line 144

def attr_set?(attr)
  receiver_attrs.has_key?(attr) && self.instance_variable_defined?("@#{attr}")
end

#missing_attrsObject

returns a list of required but missing attributes



13
14
15
16
17
18
19
# File 'lib/gorillib/receiver/validations.rb', line 13

def missing_attrs
  missing = []
  self.class.required_rcvrs.each do |name, info|
    missing << name if (not attr_set?(name))
  end
  missing
end

#receive!(hsh = {}) ⇒ Object

modify object in place with new typecast values.

Raises:

  • (ArgumentError)


129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/gorillib/receiver.rb', line 129

def receive! hsh={}
  raise ArgumentError, "Can't receive (it isn't hashlike): {#{hsh.inspect}}" unless hsh.respond_to?(:[]) && hsh.respond_to?(:has_key?)
  _receiver_fields.each do |attr|
    if    hsh.has_key?(attr.to_sym) then val = hsh[attr.to_sym]
    elsif hsh.has_key?(attr.to_s)   then val = hsh[attr.to_s]
    else  next ; end
    _receive_attr attr, val
  end
  impose_defaults!(hsh)
  replace_options!(hsh)
  run_after_receivers(hsh)
  self
end

#to_tupleObject



361
362
363
364
365
366
367
368
369
370
371
# File 'lib/gorillib/receiver.rb', line 361

def to_tuple
  tuple = []
  self.each_value do |val|
    if val.respond_to?(:to_tuple)
      tuple += val.to_tuple
    else
      tuple << val
    end
  end
  tuple
end

#tree_diff(other) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/gorillib/receiver/tree_diff.rb', line 2

def tree_diff(other)
  diff_hsh = {}
  other = other.symbolize_keys if other.respond_to?(:symbolize_keys)
  each do |k, v|
    case
    when v.is_a?(Array) && other[k].is_a?(Array)
      val = v.tree_diff(other[k])
      diff_hsh[k] = val unless val.blank?
    when v.respond_to?(:tree_diff) && other[k].respond_to?(:to_hash)
      val = v.tree_diff(other[k])
      diff_hsh[k] = val unless val.blank?
    else
      diff_hsh[k] = v unless v == other[k]
    end
  end
  other_hsh = other.dup.delete_if{|k, v| has_key?(k) }
  diff_hsh.merge!(other_hsh)
end

#validation_errorsObject

An array of strings describing any ways this fails validation



4
5
6
7
8
9
10
# File 'lib/gorillib/receiver/validations.rb', line 4

def validation_errors
  errors = []
  if (ma = missing_attrs).present?
    errors << "Missing values for {#{ma.join(",")}}"
  end
  errors
end