gorillib 0.5.0 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
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