gorillib 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. data/.gitignore +3 -0
  2. data/.gitmodules +3 -0
  3. data/.travis.yml +11 -0
  4. data/Gemfile +2 -16
  5. data/Rakefile +2 -74
  6. data/away/aliasing_spec.rb +180 -0
  7. data/away/confidence.rb +17 -0
  8. data/away/stub_module.rb +33 -0
  9. data/gorillib.gemspec +31 -246
  10. data/lib/gorillib/collection/model_collection.rb +1 -0
  11. data/lib/gorillib/data_munging.rb +0 -1
  12. data/lib/gorillib/hashlike/slice.rb +2 -0
  13. data/lib/gorillib/model/field.rb +2 -2
  14. data/lib/gorillib/model/serialization.rb +9 -4
  15. data/lib/gorillib/model/serialization/csv.rb +1 -0
  16. data/lib/gorillib/model/serialization/lines.rb +2 -0
  17. data/lib/gorillib/model/serialization/tsv.rb +1 -0
  18. data/lib/gorillib/pathname.rb +1 -1
  19. data/lib/gorillib/pathname/utils.rb +6 -0
  20. data/lib/gorillib/string/inflector.rb +1 -1
  21. data/lib/gorillib/system.rb +1 -0
  22. data/lib/gorillib/system/runner.rb +36 -0
  23. data/lib/gorillib/type/ip_address.rb +2 -2
  24. data/lib/gorillib/version.rb +3 -0
  25. data/old/lib/gorillib/hash/indifferent_access.rb +207 -0
  26. data/old/lib/gorillib/hash/tree_merge.rb +4 -0
  27. data/old/lib/gorillib/hashlike/tree_merge.rb +49 -0
  28. data/old/lib/gorillib/metaprogramming/cattr_accessor.rb +79 -0
  29. data/old/lib/gorillib/metaprogramming/mattr_accessor.rb +61 -0
  30. data/old/lib/gorillib/receiver.rb +402 -0
  31. data/old/lib/gorillib/receiver/active_model_shim.rb +32 -0
  32. data/old/lib/gorillib/receiver/acts_as_hash.rb +195 -0
  33. data/old/lib/gorillib/receiver/acts_as_loadable.rb +42 -0
  34. data/old/lib/gorillib/receiver/locale/en.yml +27 -0
  35. data/old/lib/gorillib/receiver/tree_diff.rb +74 -0
  36. data/old/lib/gorillib/receiver/validations.rb +30 -0
  37. data/old/lib/gorillib/receiver_model.rb +21 -0
  38. data/old/lib/gorillib/struct/acts_as_hash.rb +108 -0
  39. data/old/lib/gorillib/struct/hashlike_iteration.rb +0 -0
  40. data/old/spec/gorillib/hash/indifferent_access_spec.rb +391 -0
  41. data/old/spec/gorillib/metaprogramming/cattr_accessor_spec.rb +43 -0
  42. data/old/spec/gorillib/metaprogramming/mattr_accessor_spec.rb +45 -0
  43. data/old/spec/gorillib/receiver/receiver/acts_as_hash_spec.rb +295 -0
  44. data/old/spec/gorillib/receiver_spec.rb +551 -0
  45. data/old/spec/gorillib/struct/acts_as_hash_fuzz_spec.rb +71 -0
  46. data/old/spec/gorillib/struct/acts_as_hash_spec.rb +422 -0
  47. data/spec/gorillib/array/compact_blank_spec.rb +2 -2
  48. data/spec/gorillib/collection_spec.rb +6 -6
  49. data/spec/gorillib/factories_spec.rb +2 -2
  50. data/spec/gorillib/hashlike_spec.rb +2 -1
  51. data/spec/gorillib/model/defaults_spec.rb +3 -3
  52. data/spec/gorillib/model/serialization/csv_spec.rb +35 -0
  53. data/spec/gorillib/model/serialization/tsv_spec.rb +20 -4
  54. data/spec/gorillib/model/serialization_spec.rb +3 -3
  55. data/spec/spec_helper.rb +6 -1
  56. data/spec/support/factory_test_helpers.rb +2 -2
  57. data/spec/support/gorillib_test_helpers.rb +4 -4
  58. data/spec/support/hashlike_fuzzing_helper.rb +1 -15
  59. data/spec/support/hashlike_helper.rb +5 -1
  60. data/spec/support/model_test_helpers.rb +12 -1
  61. metadata +192 -168
  62. data/notes/HOWTO.md +0 -22
  63. data/notes/bucket.md +0 -155
  64. data/notes/builder.md +0 -170
  65. data/notes/collection.md +0 -81
  66. data/notes/factories.md +0 -86
  67. data/notes/model-overlay.md +0 -209
  68. data/notes/model.md +0 -135
  69. data/notes/structured-data-classes.md +0 -127
@@ -0,0 +1,79 @@
1
+ require 'gorillib/array/extract_options'
2
+
3
+ # Extends the class object with class and instance accessors for class attributes,
4
+ # just like the native attr* accessors for instance attributes.
5
+ #
6
+ # Note that unlike +class_attribute+, if a subclass changes the value then that would
7
+ # also change the value for parent class. Similarly if parent class changes the value
8
+ # then that would change the value of subclasses too.
9
+ #
10
+ # class Person
11
+ # cattr_accessor :hair_colors
12
+ # end
13
+ #
14
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
15
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
16
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
17
+ #
18
+ # To opt out of the instance writer method, pass :instance_writer => false.
19
+ # To opt out of the instance reader method, pass :instance_reader => false.
20
+ #
21
+ # class Person
22
+ # cattr_accessor :hair_colors, :instance_writer => false, :instance_reader => false
23
+ # end
24
+ #
25
+ # Person.new.hair_colors = [:brown] # => NoMethodError
26
+ # Person.new.hair_colors # => NoMethodError
27
+ class Class
28
+ def cattr_reader(*syms)
29
+ options = syms.extract_options!
30
+ syms.each do |sym|
31
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
32
+ unless defined? @@#{sym}
33
+ @@#{sym} = nil
34
+ end
35
+
36
+ def self.#{sym}
37
+ @@#{sym}
38
+ end
39
+ EOS
40
+
41
+ unless options[:instance_reader] == false
42
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
43
+ def #{sym}
44
+ @@#{sym}
45
+ end
46
+ EOS
47
+ end
48
+ end
49
+ end unless method_defined?(:cattr_reader)
50
+
51
+ def cattr_writer(*syms)
52
+ options = syms.extract_options!
53
+ syms.each do |sym|
54
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
55
+ unless defined? @@#{sym}
56
+ @@#{sym} = nil
57
+ end
58
+
59
+ def self.#{sym}=(obj)
60
+ @@#{sym} = obj
61
+ end
62
+ EOS
63
+
64
+ unless options[:instance_writer] == false
65
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
66
+ def #{sym}=(obj)
67
+ @@#{sym} = obj
68
+ end
69
+ EOS
70
+ end
71
+ self.send("#{sym}=", yield) if block_given?
72
+ end
73
+ end unless method_defined?(:cattr_writer)
74
+
75
+ def cattr_accessor(*syms, &blk)
76
+ cattr_reader(*syms)
77
+ cattr_writer(*syms, &blk)
78
+ end unless method_defined?(:cattr_accessor)
79
+ end
@@ -0,0 +1,61 @@
1
+ require 'gorillib/array/extract_options'
2
+
3
+ class Module
4
+ def mattr_reader(*syms)
5
+ options = syms.extract_options!
6
+ syms.each do |sym|
7
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
8
+ @@#{sym} = nil unless defined? @@#{sym}
9
+
10
+ def self.#{sym}
11
+ @@#{sym}
12
+ end
13
+ EOS
14
+
15
+ unless options[:instance_reader] == false
16
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
17
+ def #{sym}
18
+ @@#{sym}
19
+ end
20
+ EOS
21
+ end
22
+ end
23
+ end unless method_defined?(:mattr_reader)
24
+
25
+ def mattr_writer(*syms)
26
+ options = syms.extract_options!
27
+ syms.each do |sym|
28
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
29
+ def self.#{sym}=(obj)
30
+ @@#{sym} = obj
31
+ end
32
+ EOS
33
+
34
+ unless options[:instance_writer] == false
35
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
36
+ def #{sym}=(obj)
37
+ @@#{sym} = obj
38
+ end
39
+ EOS
40
+ end
41
+ end
42
+ end unless method_defined?(:mattr_writer)
43
+
44
+ # Extends the module object with module and instance accessors for class attributes,
45
+ # just like the native attr* accessors for instance attributes.
46
+ #
47
+ # module AppConfiguration
48
+ # mattr_accessor :google_api_key
49
+ # self.google_api_key = "123456789"
50
+ #
51
+ # mattr_accessor :paypal_url
52
+ # self.paypal_url = "www.sandbox.paypal.com"
53
+ # end
54
+ #
55
+ # AppConfiguration.google_api_key = "overriding the api key!"
56
+ def mattr_accessor(*syms)
57
+ mattr_reader(*syms)
58
+ mattr_writer(*syms)
59
+ end unless method_defined?(:mattr_accessor)
60
+
61
+ end
@@ -0,0 +1,402 @@
1
+ require 'gorillib/object/blank'
2
+ require 'gorillib/object/try'
3
+ require 'gorillib/object/try_dup'
4
+ require 'gorillib/array/extract_options'
5
+ require 'gorillib/metaprogramming/class_attribute'
6
+
7
+ # dummy type for receiving True or False
8
+ class Boolean ; end unless defined?(Boolean)
9
+
10
+ # Receiver lets you describe complex (even recursive!) actively-typed data models that
11
+ # * are creatable or assignable from static data structures
12
+ # * perform efficient type conversion when assigning from a data structure,
13
+ # * but with nothing in the way of normal assignment or instantiation
14
+ # * and no requirements on the initializer
15
+ #
16
+ # class Tweet
17
+ # include Receiver
18
+ # rcvr_accessor :id, Integer
19
+ # rcvr_accessor :user_id, Integer
20
+ # rcvr_accessor :created_at, Time
21
+ # end
22
+ # p Tweet.receive(:id => "7", :user_id => 9, :created_at => "20101231010203" )
23
+ # # => #<Tweet @id=7, @user_id=9, @created_at=2010-12-31 07:02:03 UTC>
24
+ #
25
+ # You can override receive behavior in a straightforward and predictable way:
26
+ #
27
+ # class TwitterUser
28
+ # include Receiver
29
+ # rcvr_accessor :id, Integer
30
+ # rcvr_accessor :screen_name, String
31
+ # rcvr_accessor :follower_ids, Array, :of => Integer
32
+ # # accumulate unique follower ids
33
+ # def receive_follower_ids(arr)
34
+ # @follower_ids = (@follower_ids||[]) + arr.map(&:to_i)
35
+ # @follower_ids.uniq!
36
+ # end
37
+ # end
38
+ #
39
+ # The receiver pattern works naturally with inheritance:
40
+ #
41
+ # class TweetWithUser < Tweet
42
+ # rcvr_accessor :user, TwitterUser
43
+ # after_receive do |hsh|
44
+ # self.user_id = self.user.id if self.user
45
+ # end
46
+ # end
47
+ # p TweetWithUser.receive(:id => 8675309, :created_at => "20101231010203", :user => { :id => 24601, :screen_name => 'bob', :follower_ids => [1, 8, 3, 4] })
48
+ # => #<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>
49
+ #
50
+ # TweetWithUser was able to add another receiver, applicable only to itself and its subclasses.
51
+ #
52
+ # The receive method works well with sparse data -- you can accumulate
53
+ # attributes without trampling formerly set values:
54
+ #
55
+ # tw = Tweet.receive(:id => "7", :user_id => 9 )
56
+ # p tw
57
+ # # => #<Tweet @id=7, @user_id=9>
58
+ #
59
+ # tw.receive!(:created_at => "20101231010203" )
60
+ # p tw
61
+ # # => #<Tweet @id=7, @user_id=9, @created_at=2010-12-31 07:02:03 UTC>
62
+ #
63
+ # Note the distinction between an explicit nil field and a missing field:
64
+ #
65
+ # tw.receive!(:user_id => nil, :created_at => "20090506070809" )
66
+ # p tw
67
+ # # => #<Tweet @id=7, @user_id=nil, @created_at=2009-05-06 12:08:09 UTC>
68
+ #
69
+ # There are helpers for default and required attributes:
70
+ #
71
+ # class Foo
72
+ # include Receiver
73
+ # rcvr_accessor :is_reqd, String, :required => true
74
+ # rcvr_accessor :also_reqd, String, :required => true
75
+ # rcvr_accessor :has_default, String, :default => 'hello'
76
+ # end
77
+ # foo_obj = Foo.receive(:is_reqd => "hi")
78
+ # # => #<Foo:0x00000100bd9740 @is_reqd="hi" @has_default="hello">
79
+ # foo_obj.missing_attrs
80
+ # # => [:also_reqd]
81
+ #
82
+ module Receiver
83
+
84
+ RECEIVER_BODIES = {} unless defined?(RECEIVER_BODIES)
85
+ RECEIVER_BODIES[Symbol] = %q{ v.blank? ? nil : v.to_sym }
86
+ RECEIVER_BODIES[Integer] = %q{ v.blank? ? nil : v.to_i }
87
+ RECEIVER_BODIES[Float] = %q{ v.blank? ? nil : v.to_f }
88
+ RECEIVER_BODIES[String] = %q{ v.to_s }
89
+ RECEIVER_BODIES[Time] = %q{ v.nil? ? nil : Time.parse(v.to_s).utc rescue nil }
90
+ RECEIVER_BODIES[Date] = %q{ v.nil? ? nil : Date.parse(v.to_s) rescue nil }
91
+ RECEIVER_BODIES[Array] = %q{ case when v.nil? then nil when v.blank? then [] else Array(v) end }
92
+ RECEIVER_BODIES[Hash] = %q{ case when v.nil? then nil when v.blank? then {} else v end }
93
+ RECEIVER_BODIES[Boolean] = %q{ case when v.nil? then nil when v.to_s.strip.blank? then false else v.to_s.strip != "false" end }
94
+ RECEIVER_BODIES[NilClass] = %q{ raise ArgumentError, "This field must be nil, but [#{v}] was given" unless (v.nil?) ; nil }
95
+ RECEIVER_BODIES[Object] = %q{ v } # accept and love the object just as it is
96
+
97
+ #
98
+ # Give each base class a receive method
99
+ #
100
+ RECEIVER_BODIES.each do |k,b|
101
+ if k.is_a?(Class) && b.is_a?(String)
102
+ k.class_eval <<-STR, __FILE__, __LINE__ + 1
103
+ def self.receive(v)
104
+ #{b}
105
+ end
106
+ STR
107
+ elsif k.is_a?(Class)
108
+ k.class_eval do
109
+ define_singleton_method(:receive, &b)
110
+ end
111
+ end
112
+ end
113
+
114
+ TYPE_ALIASES = {
115
+ :null => NilClass,
116
+ :boolean => Boolean,
117
+ :string => String, :bytes => String,
118
+ :symbol => Symbol,
119
+ :int => Integer, :integer => Integer, :long => Integer,
120
+ :time => Time, :date => Date,
121
+ :float => Float, :double => Float,
122
+ :hash => Hash, :map => Hash,
123
+ :array => Array,
124
+ } unless defined?(TYPE_ALIASES)
125
+
126
+ #
127
+ # modify object in place with new typecast values.
128
+ #
129
+ def receive! hsh={}
130
+ raise ArgumentError, "Can't receive (it isn't hashlike): {#{hsh.inspect}}" unless hsh.respond_to?(:[]) && hsh.respond_to?(:has_key?)
131
+ _receiver_fields.each do |attr|
132
+ if hsh.has_key?(attr.to_sym) then val = hsh[attr.to_sym]
133
+ elsif hsh.has_key?(attr.to_s) then val = hsh[attr.to_s]
134
+ else next ; end
135
+ _receive_attr attr, val
136
+ end
137
+ impose_defaults!(hsh)
138
+ replace_options!(hsh)
139
+ run_after_receivers(hsh)
140
+ self
141
+ end
142
+
143
+ # true if the attr is a receiver variable and it has been set
144
+ def attr_set?(attr)
145
+ receiver_attrs.has_key?(attr) && self.instance_variable_defined?("@#{attr}")
146
+ end
147
+
148
+ protected
149
+
150
+ def unset!(attr)
151
+ self.send(:remove_instance_variable, "@#{attr}") if self.instance_variable_defined?("@#{attr}")
152
+ end
153
+
154
+ def _receive_attr attr, val
155
+ self.send("receive_#{attr}", val)
156
+ end
157
+
158
+ def _receiver_fields
159
+ self.class.receiver_attr_names
160
+ end
161
+
162
+ def _receiver_defaults
163
+ self.class.receiver_defaults
164
+ end
165
+
166
+ def _after_receivers
167
+ self.class.after_receivers
168
+ end
169
+
170
+ def impose_defaults!(hsh)
171
+ _receiver_defaults.each do |attr, val|
172
+ next if attr_set?(attr)
173
+ self.instance_variable_set "@#{attr}", val.try_dup
174
+ end
175
+ end
176
+
177
+ # class Foo
178
+ # include Receiver
179
+ # include Receiver::ActsAsHash
180
+ # rcvr_accessor :attribute, String, :default => 'okay' :replace => { 'bad' => 'good' }
181
+ # end
182
+ #
183
+ # f = Foo.receive({:attribute => 'bad'})
184
+ # => #<Foo:0x10156c820 @attribute="good">
185
+ #
186
+ def replace_options!(hsh)
187
+ self.receiver_attrs.each do |attr, info|
188
+ val = self.instance_variable_get("@#{attr}")
189
+ if info[:replace] and info[:replace].has_key? val
190
+ self.instance_variable_set "@#{attr}", info[:replace][val]
191
+ end
192
+ end
193
+ end
194
+
195
+ def run_after_receivers(hsh)
196
+ _after_receivers.each do |after_receiver|
197
+ self.instance_exec(hsh, &after_receiver)
198
+ end
199
+ end
200
+
201
+ public
202
+
203
+ module ClassMethods
204
+
205
+ #
206
+ # Returns a new instance with the given hash used to set all rcvrs.
207
+ #
208
+ # All args up to the last one are passed to the initializer.
209
+ # The last arg must be a hash -- its attributes are set on the newly-created object
210
+ #
211
+ # @param hsh [Hash] attr-value pairs to set on the newly created object.
212
+ # @param *args [Array] arguments to pass to the constructor
213
+ # @return [Object] a new instance
214
+ def receive *args
215
+ hsh = args.pop || {}
216
+ raise ArgumentError, "Can't receive (it isn't hashlike): {#{hsh.inspect}} -- the hsh should be the *last* arg" unless hsh.respond_to?(:[]) && hsh.respond_to?(:has_key?)
217
+ obj = self.new(*args)
218
+ obj.receive!(hsh)
219
+ end
220
+
221
+ #
222
+ # define a receiver attribute.
223
+ # automatically generates an attr_accessor on the class if none exists
224
+ #
225
+ # @option [Boolean] :required - Adds an error on validation if the attribute is never set
226
+ # @option [Object] :default - After any receive! operation, attribute is set to this value unless attr_set? is true
227
+ # @option [Class] :of - For collections (Array, Hash, etc), the type of the collection's items
228
+ #
229
+ def rcvr name, type, info={}
230
+ name = name.to_sym
231
+ type = type_to_klass(type)
232
+ body = receiver_body_for(type, info)
233
+ if body.is_a?(String)
234
+ class_eval(%Q{
235
+ def receive_#{name}(v)
236
+ self.instance_variable_set("@#{name}", (#{body}))
237
+ end}, __FILE__, __LINE__ + 1)
238
+ else
239
+ define_method("receive_#{name}") do |*args|
240
+ v = body.call(*args)
241
+ self.instance_variable_set("@#{name}", v)
242
+ v
243
+ end
244
+ end
245
+ # careful here: don't modify parent's class_attribute in-place
246
+ self.receiver_attrs = self.receiver_attrs.dup
247
+ self.receiver_attr_names += [name] unless receiver_attr_names.include?(name)
248
+ self.receiver_attrs[name] = info.merge({ :name => name, :type => type })
249
+ end
250
+
251
+ # make a block to run after each time .receive! is invoked
252
+ def after_receive &block
253
+ self.after_receivers += [block]
254
+ end
255
+
256
+ # defines a receiver attribute, an attr_reader and an attr_writer
257
+ # attr_reader is skipped if the getter method is already defined;
258
+ # attr_writer is skipped if the setter method is already defined;
259
+ def rcvr_accessor name, type, info={}
260
+ attr_reader(name) unless method_defined?(name)
261
+ attr_writer(name) unless method_defined?("#{name}=")
262
+ rcvr name, type, info
263
+ end
264
+ # defines a receiver attribute and an attr_reader
265
+ # attr_reader is skipped if the getter method is already defined.
266
+ def rcvr_reader name, type, info={}
267
+ attr_reader(name) unless method_defined?(name)
268
+ rcvr name, type, info
269
+ end
270
+ # defines a receiver attribute and an attr_writer
271
+ # attr_writer is skipped if the setter method is already defined.
272
+ def rcvr_writer name, type, info={}
273
+ attr_writer(name) unless method_defined?("#{name}=")
274
+ rcvr name, type, info
275
+ end
276
+
277
+ #
278
+ # Defines a receiver for attributes sent to receive! that are
279
+ # * not defined as receivers
280
+ # * attribute name does not start with '_'
281
+ #
282
+ # @example
283
+ # class Foo ; include Receiver
284
+ # rcvr_accessor :bob, String
285
+ # rcvr_remaining :other_params
286
+ # end
287
+ # foo_obj = Foo.receive(:bob => 'hi, bob", :joe => 'hi, joe')
288
+ # # => <Foo @bob='hi, bob' @other_params={ :joe => 'hi, joe' }>
289
+ def rcvr_remaining name, info={}
290
+ rcvr_reader name, Hash, info
291
+ after_receive do |hsh|
292
+ remaining_vals_hsh = hsh.reject{|k,v| (receiver_attrs.include?(k)) || (k.to_s =~ /^_/) }
293
+ self._receive_attr name, remaining_vals_hsh
294
+ end
295
+ end
296
+
297
+ # a hash from attribute names to their default values if given
298
+ def receiver_defaults
299
+ defs = {}
300
+ receiver_attrs.each do |name, info|
301
+ defs[name] = info[:default] if info.has_key?(:default)
302
+ end
303
+ defs
304
+ end
305
+
306
+ # returns an in-order traversal of the
307
+ #
308
+ def tuple_keys
309
+ return @tuple_keys if @tuple_keys
310
+ @tuple_keys = self
311
+ @tuple_keys = receiver_attrs.map do |attr, info|
312
+ info[:type].try(:tuple_keys) || attr
313
+ end.flatten
314
+ end
315
+
316
+ def consume_tuple(tuple)
317
+ obj = self.new
318
+ receiver_attrs.each do |attr, info|
319
+ if info[:type].respond_to?(:consume_tuple)
320
+ val = info[:type].consume_tuple(tuple)
321
+ else
322
+ val = tuple.shift
323
+ end
324
+ # obj.send("receive_#{attr}", val)
325
+ obj.send("#{attr}=", val)
326
+ end
327
+ obj
328
+ end
329
+
330
+ protected
331
+ def receiver_body_for type, info
332
+ type = type_to_klass(type)
333
+ # Note that Array and Hash only need (and only get) special treatment when
334
+ # they have an :of => SomeType option.
335
+ case
336
+ when info[:of] && (type == Array)
337
+ receiver_type = info[:of]
338
+ lambda{|v| v.nil? ? nil : v.map{|el| receiver_type.receive(el) } }
339
+ when info[:of] && (type == Hash)
340
+ receiver_type = info[:of]
341
+ lambda{|v| v.nil? ? nil : v.inject({}){|h, (el,val)| h[el] = receiver_type.receive(val); h } }
342
+ when Receiver::RECEIVER_BODIES.include?(type)
343
+ Receiver::RECEIVER_BODIES[type]
344
+ when type.is_a?(Class)
345
+ lambda{|v| v.blank? ? nil : type.receive(v) }
346
+ else
347
+ raise("Can't receive #{type} #{info}")
348
+ end
349
+ end
350
+
351
+ def type_to_klass(type)
352
+ case
353
+ when type.is_a?(Class) then return type
354
+ when TYPE_ALIASES.has_key?(type) then TYPE_ALIASES[type]
355
+ # when (type.is_a?(Symbol) && type.to_s =~ /^[A-Z]/) then type.to_s.constantize
356
+ else raise ArgumentError, "Can\'t handle type #{type}: is it a Class or one of the TYPE_ALIASES?"
357
+ end
358
+ end
359
+ end
360
+
361
+ def to_tuple
362
+ tuple = []
363
+ self.each_value do |val|
364
+ if val.respond_to?(:to_tuple)
365
+ tuple += val.to_tuple
366
+ else
367
+ tuple << val
368
+ end
369
+ end
370
+ tuple
371
+ end
372
+
373
+ module ClassMethods
374
+ # By default, the hashlike methods iterate over the receiver attributes.
375
+ # If you want to filter our add to the keys list, override this method
376
+ #
377
+ # @example
378
+ # def self.members
379
+ # super + [:firstname, :lastname] - [:fullname]
380
+ # end
381
+ #
382
+ def members
383
+ receiver_attr_names
384
+ end
385
+ end
386
+
387
+ # set up receiver attributes, and bring in methods from the ClassMethods module at class-level
388
+ def self.included base
389
+ base.class_eval do
390
+ unless method_defined?(:receiver_attrs)
391
+ class_attribute :receiver_attrs
392
+ class_attribute :receiver_attr_names
393
+ class_attribute :after_receivers
394
+ self.receiver_attrs = {} # info about the attr
395
+ self.receiver_attr_names = [] # ordered set of attr names
396
+ self.after_receivers = [] # blocks to execute following receive!
397
+ extend ClassMethods
398
+ end
399
+ end
400
+ end
401
+
402
+ end