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,295 @@
1
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
+ require 'gorillib/receiver'
3
+ require 'gorillib/metaprogramming/class_attribute'
4
+ require 'gorillib/hashlike'
5
+ require 'gorillib/receiver/acts_as_hash'
6
+ require GORILLIB_ROOT_DIR('spec/support/hashlike_helper')
7
+ require GORILLIB_ROOT_DIR('spec/support/hashlike_via_delegation')
8
+ require GORILLIB_ROOT_DIR('spec/hashlike/hashlike_behavior_spec')
9
+
10
+ class SimpleReceiver
11
+ include Receiver
12
+ include Receiver::ActsAsHash
13
+ #
14
+ rcvr_accessor :a, Integer
15
+ rcvr_accessor :b, Integer
16
+ rcvr_accessor :c, Integer
17
+ rcvr_accessor :nil_val, NilClass
18
+ rcvr_accessor :false_val, Boolean
19
+ rcvr_accessor :new_key, String
20
+ end
21
+
22
+ describe Receiver do
23
+
24
+ before do
25
+ @hshlike = SimpleReceiver.new.merge(HashlikeHelper::HASH_TO_TEST_HL_V_A.dup)
26
+ @empty_hshlike = SimpleReceiver.new
27
+ @hshlike_with_array_vals = SimpleReceiver.new.merge(HashlikeHelper::BASE_HSH_WITH_ARRAY_VALS.dup)
28
+ #
29
+ @hshlike_subklass = Class.new(SimpleReceiver)
30
+ @hshlike_subklass_inst = @hshlike_subklass.new.merge(HashlikeHelper::BASE_HSH.dup)
31
+ end
32
+
33
+
34
+ # ===========================================================================
35
+ #
36
+ # Fundamental behavior
37
+
38
+ describe '#[] and #[]= and #store' do
39
+ it_should_behave_like :hashlike_store_and_retrieve
40
+ it_should_behave_like :references_string_and_symbol_keys_equivalently
41
+
42
+ it 'reject unknown keys' do
43
+ lambda{ @hshlike[:fnord] = 69 }.should raise_error NoMethodError, /undefined method `fnord=' for/
44
+ lambda{ @hshlike[:fnord] }.should raise_error NoMethodError, /undefined method `fnord' for/
45
+ @hshlike.delete(:fnord).should be_nil
46
+ end
47
+
48
+ it 'accepts defined but unset keys' do
49
+ @hshlike[:new_key].should be_nil
50
+ @hshlike[:new_key] = 69
51
+ @hshlike[:new_key].should == 69
52
+ end
53
+
54
+ it 'does not allow nil, Object, or other non-stringy keys' do
55
+ lambda{ @hshlike[300] = :i_haz_num }.should raise_error(ArgumentError, "Keys for SimpleReceiver must be symbols, strings or respond to #to_sym")
56
+ lambda{ @hshlike[nil] = :i_haz_nil }.should raise_error(ArgumentError, "Keys for SimpleReceiver must be symbols, strings or respond to #to_sym")
57
+ obj = Object.new
58
+ lambda{ @hshlike[obj] = :i_haz_obj }.should raise_error(ArgumentError, "Keys for SimpleReceiver must be symbols, strings or respond to #to_sym")
59
+ def obj.to_sym() :c ; end
60
+ @hshlike[obj] = :i_haz_obj
61
+ @hshlike[obj].should == :i_haz_obj
62
+ end
63
+
64
+ it 'defines the right accessor methods' do
65
+ @hshlike_subklass.rcvr_writer :write_only_attr, String
66
+ @hshlike_subklass.rcvr_reader :read_only_attr, String
67
+ @hshlike_subklass.rcvr :internal_attr, String
68
+ @hshlike_subklass.class_eval{ attr_accessor :not_a_receiver }
69
+ [ :receive_write_only_attr, :write_only_attr=,
70
+ :receive_read_only_attr, :read_only_attr,
71
+ :receive_internal_attr,
72
+ :not_a_receiver, :not_a_receiver= ].each{ |attr| @hshlike_subklass.should be_method_defined(attr) }
73
+ [ :write_only_attr, :read_only_attr=,
74
+ :internal_attr, :internal_attr=,
75
+ :receive_not_a_receiver ].each{ |attr| @hshlike_subklass.should_not be_method_defined(attr) }
76
+ @hshlike_subklass.receiver_attr_names.should == [:a, :b, :c, :nil_val, :false_val, :new_key, :write_only_attr, :read_only_attr, :internal_attr]
77
+ end
78
+ end
79
+
80
+ describe '#delete' do
81
+ it_should_behave_like :hashlike_delete
82
+ end
83
+
84
+ describe '#keys' do
85
+ it_should_behave_like :hashlike_keys
86
+ end
87
+
88
+ # ===========================================================================
89
+ #
90
+ # Iteration
91
+
92
+ describe '#each_pair' do
93
+ describe 'with block' do
94
+ it_should_behave_like :each_pair_on_stringlike_keys, :each_pair
95
+ end
96
+ it_should_behave_like :with_no_block_returns_enumerator, :each_pair
97
+ end
98
+
99
+ describe '#each' do
100
+ describe 'with block' do
101
+ it_should_behave_like :each_pair_on_stringlike_keys, :each
102
+ end
103
+ it_should_behave_like :with_no_block_returns_enumerator, :each
104
+ end
105
+
106
+ describe '#each_key' do
107
+ describe 'with block' do
108
+ it_should_behave_like :each_key_on_stringlike_keys
109
+ end
110
+ it_should_behave_like :with_no_block_returns_enumerator, :each_key
111
+ end
112
+
113
+ describe '#each_value' do
114
+ describe 'with block' do
115
+ it_should_behave_like :each_value_on_stringlike_keys
116
+ end
117
+ it_should_behave_like :with_no_block_returns_enumerator, :each_value
118
+ end
119
+
120
+ # ===========================================================================
121
+ #
122
+ # Retrieval and Membership
123
+
124
+ describe '#values' do
125
+ it_should_behave_like :hashlike_values
126
+ end
127
+
128
+ describe '#values_at' do
129
+ it_should_behave_like :hashlike_values_at_or_of, :values_at
130
+ end
131
+
132
+ describe '#values_of' do
133
+ it_should_behave_like :hashlike_values_at_or_of, :values_of
134
+ end
135
+
136
+ describe '#length' do
137
+ it_should_behave_like :hashlike_length, :length
138
+ end
139
+
140
+ describe '#size' do
141
+ it_should_behave_like :hashlike_length, :size
142
+ end
143
+
144
+ describe '#has_key?' do
145
+ it_should_behave_like :hashlike_has_key?, :has_key?
146
+ it_should_behave_like :hashlike_has_key_string_and_symbol_equivalent, :has_key?
147
+ end
148
+
149
+ describe '#include?' do
150
+ it_should_behave_like :hashlike_has_key?, :include?
151
+ it_should_behave_like :hashlike_has_key_string_and_symbol_equivalent, :include?
152
+ end
153
+
154
+ describe '#key?' do
155
+ it_should_behave_like :hashlike_has_key?, :key?
156
+ it_should_behave_like :hashlike_has_key_string_and_symbol_equivalent, :key?
157
+ end
158
+
159
+ describe '#member?' do
160
+ it_should_behave_like :hashlike_has_key?, :member?
161
+ it_should_behave_like :hashlike_has_key_string_and_symbol_equivalent, :member?
162
+ end
163
+
164
+ describe '#has_value?' do
165
+ it_should_behave_like :hashlike_has_value?, :has_value?
166
+ end
167
+
168
+ describe '#value?' do
169
+ it_should_behave_like :hashlike_has_value?, :value?
170
+ end
171
+
172
+ describe '#fetch' do
173
+ it_should_behave_like :hashlike_fetch
174
+ end
175
+
176
+ describe '#key' do
177
+ it_should_behave_like :hashlike_key
178
+ end
179
+
180
+ describe '#assoc' do
181
+ it_should_behave_like :hashlike_assoc
182
+ end
183
+
184
+ describe '#rassoc' do
185
+ it_should_behave_like :hashlike_rassoc
186
+ end
187
+
188
+ describe '#empty?' do
189
+ it_should_behave_like :hashlike_empty?
190
+ end
191
+
192
+ # ===========================================================================
193
+ #
194
+ # Update, merge!, merge
195
+
196
+ describe 'update' do
197
+ it_should_behave_like :merging_method, :update
198
+ it_should_behave_like :merging_method_with_normal_keys, :update
199
+ it_should_behave_like :merging_method_inplace, :update
200
+ end
201
+
202
+ describe '#merge!' do
203
+ it_should_behave_like :merging_method, :merge!
204
+ it_should_behave_like :merging_method_with_normal_keys, :merge!
205
+ it_should_behave_like :merging_method_inplace, :merge!
206
+ end
207
+
208
+ describe '#merge' do
209
+ it_should_behave_like :merging_method, :merge
210
+ it_should_behave_like :merging_method_with_normal_keys, :merge
211
+ it_should_behave_like :merging_method_returning_new, :merge
212
+ end
213
+
214
+ # ===========================================================================
215
+ #
216
+ # Filters
217
+
218
+ describe '#reject!' do
219
+ it_should_behave_like :hashlike_filter, :reject!
220
+ it_should_behave_like :rejection_filter, :reject!
221
+ it_should_behave_like :filter_modifies_self_returns_nil_if_unchanged, :reject!, false
222
+ end
223
+
224
+ describe '#select!' do
225
+ it_should_behave_like :hashlike_filter, :select!
226
+ it_should_behave_like :selection_filter, :select!
227
+ it_should_behave_like :filter_modifies_self_returns_nil_if_unchanged, :select!, true
228
+ end
229
+
230
+ describe '#delete_if' do
231
+ it_should_behave_like :hashlike_filter, :delete_if
232
+ it_should_behave_like :rejection_filter, :delete_if
233
+ it_should_behave_like :filter_modifies_self_returns_self, :delete_if, false
234
+ end
235
+
236
+ describe '#keep_if' do
237
+ it_should_behave_like :hashlike_filter, :keep_if
238
+ it_should_behave_like :selection_filter, :keep_if
239
+ it_should_behave_like :filter_modifies_self_returns_self, :keep_if, true
240
+ end
241
+
242
+ describe '#reject' do
243
+ it_should_behave_like :hashlike_filter, :select
244
+ it_should_behave_like :selection_filter, :select
245
+ it_should_behave_like :filter_does_not_modify_self_returns_same_class, :reject, false
246
+ end
247
+
248
+ describe '#select' do
249
+ it_should_behave_like :hashlike_filter, :select
250
+ it_should_behave_like :selection_filter, :select
251
+ it_should_behave_like :filter_does_not_modify_self_returns_same_class, :select, true
252
+ end
253
+
254
+ describe '#clear' do
255
+ it_should_behave_like :hashlike_clear
256
+ end
257
+
258
+ # ===========================================================================
259
+ #
260
+ # Conversion
261
+
262
+ describe '#to_hash' do
263
+ it_should_behave_like :hashlike_to_hash
264
+
265
+ it 'should recurse' do
266
+ @hshlike_subklass.rcvr_accessor :k2, SimpleReceiver
267
+ hsh = HashlikeHelper::HASH_TO_TEST_HL_V_A.merge(:k2 => {:a => 3, :new_key => 'hi' })
268
+ obj = @hshlike_subklass.receive(hsh)
269
+ obj.to_hash.should == {:a=>100, :b=>200, :c=>300, :nil_val=>nil, :false_val=>false, :k2=>{:a=>3, :new_key=>"hi"} }
270
+ end
271
+ end
272
+
273
+ describe '#invert' do
274
+ it_should_behave_like :hashlike_invert
275
+ end
276
+
277
+ describe '#flatten' do
278
+ it_should_behave_like :hashlike_flatten
279
+ end
280
+
281
+ # ===========================================================================
282
+ #
283
+ # Sanity check
284
+
285
+ it 'built test objects correctly' do
286
+ @hshlike_subklass .should < @hshlike.class
287
+ @hshlike_subklass .should_not == @hshlike.class
288
+ @hshlike_subklass_inst .should be_a(SimpleReceiver)
289
+ @hshlike_subklass_inst .should be_a(@hshlike_subklass)
290
+ @hshlike_subklass_inst .should_not be_an_instance_of(SimpleReceiver)
291
+ @hshlike .should_not be_a(@hshlike_subklass)
292
+ @hshlike .should be_an_instance_of(SimpleReceiver)
293
+ end
294
+
295
+ end
@@ -0,0 +1,551 @@
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
2
+ require 'gorillib/metaprogramming/class_attribute'
3
+ require 'gorillib/object/blank'
4
+ require 'time'
5
+ require 'date'
6
+ require 'gorillib/receiver'
7
+ require 'gorillib/array/extract_options'
8
+
9
+ class Wide
10
+ include Receiver
11
+ rcvr_accessor :my_int, Integer
12
+ end
13
+
14
+ require 'gorillib/receiver/acts_as_hash'
15
+ class StreetAddress
16
+ include Receiver
17
+ include Receiver::ActsAsHash
18
+ rcvr_accessor :num, Integer
19
+ rcvr_accessor :street, String
20
+ rcvr_accessor :city, String
21
+ rcvr_accessor :state, String
22
+ rcvr_accessor :zip_code, Integer
23
+ end
24
+
25
+ class Vcard
26
+ include Receiver
27
+ include Receiver::ActsAsHash
28
+ rcvr_accessor :name, String
29
+ rcvr_accessor :home_address, StreetAddress
30
+ rcvr_accessor :work_address, StreetAddress
31
+ rcvr_accessor :phone, String
32
+ end
33
+
34
+ class RecursiveReceiver < Wide
35
+ include Receiver
36
+ rcvr_accessor :a, Integer
37
+ rcvr_accessor :b, String
38
+ rcvr_accessor :wide_rcvr_a, Wide
39
+ rcvr_accessor :rec_field, RecursiveReceiver
40
+ rcvr_accessor :wide_rcvr_b, Wide
41
+ rcvr_accessor :c, String
42
+ end
43
+
44
+ describe Receiver do
45
+ before do
46
+ @klass = Class.new(Wide)
47
+ end
48
+
49
+ it 'tracks an order for the rcvrs' do
50
+ @klass.rcvr_accessor :a, Integer
51
+ @klass.rcvr_reader :b, Integer
52
+ @klass.rcvr_writer :c, Integer
53
+ @klass.rcvr :d, Integer
54
+ @klass.receiver_attr_names.should == [:my_int, :a, :b, :c, :d]
55
+ end
56
+
57
+ describe '.receive' do
58
+ it 'creates a new object and returns it' do
59
+ obj = @klass.new
60
+ @klass.should_receive(:new).and_return(obj)
61
+ ret = @klass.receive({})
62
+ ret.should equal(obj)
63
+ end
64
+
65
+ it 'invokes receive! on the new object' do
66
+ obj = @klass.new
67
+ @klass.should_receive(:new).and_return(obj)
68
+ obj.should_receive(:receive!).with({})
69
+ ret = @klass.receive({})
70
+ end
71
+
72
+ it 'passes extra args to the constructor' do
73
+ obj = @klass.new
74
+ @klass.should_receive(:new).with(:a, :b).and_return(obj)
75
+ obj.should_receive(:receive!).with({})
76
+ ret = @klass.receive(:a, :b, {})
77
+ end
78
+
79
+ it 'accepts an empty arg set (as if it got an empty hash)' do
80
+ obj = mock
81
+ @klass.should_receive(:new).and_return(obj)
82
+ obj.should_receive(:receive!).with({})
83
+ @klass.receive()
84
+ end
85
+
86
+ it 'accepts an empty hash' do
87
+ obj = mock
88
+ @klass.should_receive(:new).and_return(obj)
89
+ obj.should_receive(:receive!).with({})
90
+ @klass.receive({})
91
+ end
92
+
93
+ it 'uses the *last* arg as the hsh to receive' do
94
+ obj = mock
95
+ hsh_to_receive = { :a => :b }
96
+ hsh_for_constructor = { :c => :d }
97
+ @klass.should_receive(:new).with(hsh_for_constructor).and_return(obj)
98
+ obj.should_receive(:receive!).with(hsh_to_receive)
99
+ @klass.receive(hsh_for_constructor, hsh_to_receive)
100
+ end
101
+ end
102
+
103
+ ADDRESS_TUPLE = ['Homer J Simpson', 742, 'Evergreen Terr', 'Springfield', 'AZ', 12345, 100, 'Industrial Way', 'Springfield', 'WY', 98765, '800-BITE-ME']
104
+ ADDRESS_HASH = {
105
+ :name => 'Homer J Simpson',
106
+ :home_address => { :num => 742, :street => 'Evergreen Terr', :city => 'Springfield', :state => 'AZ', :zip_code => 12345 },
107
+ :work_address => { :num => 100, :street => 'Industrial Way', :city => 'Springfield', :state => 'WY', :zip_code => 98765 },
108
+ :phone => '800-BITE-ME'}
109
+
110
+ describe '.consume_tuple' do
111
+ it 'receives a tuple, assigning to rcvrs in order' do
112
+ obj = Vcard.consume_tuple(ADDRESS_TUPLE.dup)
113
+ obj.to_hash.should == ADDRESS_HASH
114
+ end
115
+
116
+ it 'allows empty tuple'
117
+
118
+ it '?breaks? on too-long tuple'
119
+ end
120
+
121
+ describe '#to_tuple' do
122
+ it 'flattens' do
123
+ obj = Vcard.receive(ADDRESS_HASH)
124
+ obj.to_tuple.should == ADDRESS_TUPLE
125
+ end
126
+ end
127
+
128
+ describe '.tuple_keys' do
129
+ it 'for a simple receiver, produces attrs in order' do
130
+ StreetAddress.tuple_keys.should == [:num, :street, :city, :state, :zip_code]
131
+ end
132
+
133
+ it 'for a complex receiver, in-order traverses the tree' do
134
+ Vcard.tuple_keys.should == [:name, :num, :street, :city, :state, :zip_code, :num, :street, :city, :state, :zip_code, :phone]
135
+ end
136
+
137
+ it 'does not recurse endlessly' do
138
+ RecursiveReceiver.tuple_keys.should == [:my_int, :a, :b, :my_int, RecursiveReceiver, :my_int, :c]
139
+ end
140
+ end
141
+
142
+ describe '.receive_foo' do
143
+ it 'injects a superclass, so I can call super() in receive_foo'
144
+ end
145
+
146
+ describe '.rcvr' do
147
+
148
+ it 'creates the receive_{whatever} method' do
149
+ @klass.rcvr_accessor :a, Integer, :foo => :bar_a
150
+ obj = @klass.new
151
+ obj.should respond_to(:receive_a)
152
+ end
153
+
154
+ it 'stores the name, type and extra info into receiver_attrs, and the name (in order) into receiver_attr_names' do
155
+ @klass.rcvr_accessor :a, Integer, :foo => :bar_a
156
+ @klass.rcvr_reader :b, Integer, :foo => :bar_b
157
+ @klass.rcvr :c, Integer, :foo => :bar_c
158
+ @klass.receiver_attr_names.should == [:my_int, :a, :b, :c]
159
+ @klass.receiver_attrs.should == {
160
+ :my_int => {:type => Integer, :name => :my_int},
161
+ :a => {:type => Integer, :name => :a, :foo => :bar_a},
162
+ :b => {:type => Integer, :name => :b, :foo => :bar_b},
163
+ :c => {:type => Integer, :name => :c, :foo => :bar_c},
164
+ }
165
+ end
166
+
167
+ it 'does not replace the class attributes in-place, so that inheritance works' do
168
+ old_receiver_attrs = @klass.receiver_attrs
169
+ old_receiver_attr_names = @klass.receiver_attr_names
170
+ @klass.rcvr_accessor :a, Integer, :foo => :bar_a
171
+ old_receiver_attrs.should_not equal(@klass.receiver_attrs)
172
+ old_receiver_attr_names.should_not equal(@klass.receiver_attr_names)
173
+ end
174
+
175
+ it 'accepts a type alias but uses the aliased class' do
176
+ @klass.rcvr_accessor :my_symbol, :symbol
177
+ @klass.rcvr_accessor :my_bytes, :bytes
178
+ @klass.receiver_attrs[:my_symbol][:type].should == Symbol
179
+ @klass.receiver_attrs[:my_bytes ][:type].should == String
180
+ end
181
+
182
+ it 'does not accept an unknown type' do
183
+ lambda{ @klass.rcvr_accessor :my_symbol, :oxnard }.should raise_error(ArgumentError, "Can\'t handle type oxnard: is it a Class or one of the TYPE_ALIASES?")
184
+ end
185
+ end
186
+
187
+ describe '.rcvr_accessor, .rcvr_reader, .rcvr_writer' do
188
+ it 'accepts rcvr, rcvr_accessor, rcvr_reader, rcvr_writer' do
189
+ @klass.rcvr_accessor :a, Integer
190
+ @klass.rcvr_reader :b, Integer
191
+ @klass.rcvr_writer :c, Integer
192
+ @klass.rcvr :d, Integer
193
+ obj = @klass.new
194
+ obj.should respond_to(:a)
195
+ obj.should respond_to(:a=)
196
+ obj.should respond_to(:b)
197
+ obj.should_not respond_to(:b=)
198
+ obj.should_not respond_to(:c)
199
+ obj.should respond_to(:c=)
200
+ obj.should_not respond_to(:d)
201
+ obj.should_not respond_to(:d=)
202
+ end
203
+
204
+ it 'delegates to rcvr' do
205
+ @klass.should_receive(:rcvr).with(:a, Integer, {:foo => :bar}).ordered
206
+ @klass.should_receive(:rcvr).with(:b, Integer, {:foo => :bar}).ordered
207
+ @klass.should_receive(:rcvr).with(:c, Integer, {:foo => :bar}).ordered
208
+ @klass.should_receive(:rcvr).with(:d, Integer, {:foo => :bar}).ordered
209
+ @klass.rcvr_accessor :a, Integer, :foo => :bar
210
+ @klass.rcvr_reader :b, Integer, :foo => :bar
211
+ @klass.rcvr_writer :c, Integer, :foo => :bar
212
+ @klass.rcvr :d, Integer, :foo => :bar
213
+ end
214
+
215
+ it 'does not modify parent class' do
216
+ @klass.rcvr_accessor :a, Integer
217
+ @klass.rcvr :d, Integer
218
+ obj = Wide.new
219
+ obj.should_not respond_to(:a)
220
+ obj.should_not respond_to(:a=)
221
+ Wide.receiver_attr_names.should == [:my_int]
222
+ Wide.receiver_attrs.should == {:my_int => {:type => Integer, :name => :my_int}}
223
+ end
224
+ end
225
+
226
+ describe 'default values' do
227
+ it 'rcvr accepts a default for an attribute' do
228
+ @klass.rcvr_reader :will_get_a_value, Integer, :default => 12
229
+ @klass.rcvr_reader :has_a_default, Integer, :default => 5
230
+ obj = @klass.receive(:my_int => 3, :will_get_a_value => 9)
231
+ obj.my_int.should == 3
232
+ obj.has_a_default.should == 5
233
+ obj.will_get_a_value.should == 9
234
+ end
235
+
236
+ it 'does not use default if value is set-but-nil' do
237
+ @klass.rcvr_reader :has_a_default, Integer, :default => 5
238
+ obj = @klass.receive(:my_int => 3, :has_a_default => nil)
239
+ obj.has_a_default.should be_nil
240
+ end
241
+ end
242
+
243
+ describe 'required attributes' do
244
+ it 'rcvr accepts required attributes'
245
+ end
246
+
247
+ describe 'typed collections' do
248
+ it 'sets a type for an array with :of => Type' do
249
+ @klass.rcvr_accessor :array_of_symbol, Array, :of => Symbol
250
+ obj = @klass.receive(:array_of_symbol => [:a, 'b', 'c'])
251
+ obj.array_of_symbol.should == [:a, :b, :c]
252
+ end
253
+
254
+ it 'accepts nil if value is nil' do
255
+ @klass.rcvr_accessor :array_of_symbol, Array, :of => Symbol
256
+ obj = @klass.receive(:array_of_symbol => nil)
257
+ obj.array_of_symbol.should == nil
258
+ end
259
+
260
+ it 'accepts complex class as :of => Type' do
261
+ class Foo ; include Receiver ; rcvr_accessor(:foo, Integer) ; end
262
+ @klass.rcvr_accessor :array_of_obj, Array, :of => Foo
263
+ obj = @klass.receive( :array_of_obj => [ {:foo => 3}, {:foo => 5} ] )
264
+ obj.array_of_obj.first.foo.should == 3
265
+ obj.array_of_obj.last.foo.should == 5
266
+ end
267
+
268
+ it 'sets a type for a hash with :of => Type' do
269
+ @klass.rcvr_accessor :hash_of_symbol, Hash, :of => Symbol
270
+ obj = @klass.receive(:hash_of_symbol => { :a => 'val_a', 'b' => :val_b , 'c' => 'val_c' })
271
+ obj.hash_of_symbol.should == { :a => :val_a, 'b' => :val_b , 'c' => :val_c }
272
+ end
273
+ end
274
+
275
+ describe '.rcvr_remaining' do
276
+ it 'creates a dummy receiver for the extra params' do
277
+ @klass.should_receive(:rcvr_reader).with(:bob, Hash, {:foo => :bar})
278
+ @klass.rcvr_remaining :bob, :foo => :bar
279
+ end
280
+ it 'does not get params that go with defined attrs (even if there is no writer)' do
281
+ @klass.rcvr_remaining :bob
282
+ hsh = {:my_int => 3, :foo => :bar, :width => 5}
283
+ obj = @klass.receive(hsh)
284
+ obj.bob.should == {:foo => :bar, :width => 5}
285
+ hsh.should == {:my_int => 3, :foo => :bar, :width => 5}
286
+ end
287
+ it 'does not get params whose key starts with "_"' do
288
+ @klass.rcvr_remaining :bob
289
+ hsh = {:my_int => 3, :foo => :bar, :width => 5, :_ignored => 9}
290
+ obj = @klass.receive(hsh)
291
+ obj.bob.should == {:foo => :bar, :width => 5}
292
+ end
293
+ it 'stores them, replacing a previous value' do
294
+ @klass.rcvr_remaining :bob
295
+ obj = @klass.new
296
+ obj.instance_variable_set("@bob", {:foo => 9, :width => 333, :extra => :will_be_gone})
297
+ obj.receive!({ :my_int => 3, :foo => :bar, :width => 5 })
298
+ obj.bob.should == {:foo => :bar, :width => 5}
299
+ end
300
+ end
301
+
302
+ describe '.after_receive, #run_after_receivers' do
303
+ it 'calls each block in order' do
304
+ @klass.after_receive{|hsh| hsh.i_am_calling_you_first }
305
+ @klass.after_receive{|hsh| hsh.i_am_calling_you_second }
306
+ @klass.after_receive{|hsh| hsh.i_am_calling_you_third }
307
+ hsh = {:my_int => 3}
308
+ hsh.should_receive(:i_am_calling_you_first).ordered
309
+ hsh.should_receive(:i_am_calling_you_second).ordered
310
+ hsh.should_receive(:i_am_calling_you_third).ordered
311
+ @klass.receive(hsh)
312
+ end
313
+
314
+ it 'calls the block with the full receive hash' do
315
+ @klass.after_receive{|hsh| hsh.i_am_calling_you }
316
+ hsh = {}
317
+ hsh.should_receive(:i_am_calling_you)
318
+ @klass.receive(hsh)
319
+ end
320
+ end
321
+
322
+ describe '#unset' do
323
+ it 'nukes any existing instance_variable' do
324
+ obj = @klass.new
325
+ obj.my_int = 3
326
+ obj.instance_variable_get('@my_int').should == 3
327
+ obj.send(:unset!, :my_int)
328
+ obj.instance_variable_get('@my_int').should be_nil
329
+ obj.instance_variables.should == []
330
+ end
331
+
332
+ it 'succeeds even if instance_variable never set' do
333
+ obj = @klass.new
334
+ obj.send(:unset!, :my_int)
335
+ obj.instance_variable_get('@my_int').should be_nil
336
+ obj.instance_variables.should == []
337
+ end
338
+
339
+ end
340
+
341
+ describe 'attr_set?' do
342
+ it 'is set if the corresponding instance_variable exists' do
343
+ obj = @klass.new
344
+ obj.attr_set?(:my_int).should == false
345
+ obj.instance_variable_set('@my_int', 3)
346
+ obj.attr_set?(:my_int).should == true
347
+ end
348
+
349
+ it 'can be set but nil or false' do
350
+ @klass.rcvr_accessor :bool_field, Boolean
351
+ @klass.rcvr_accessor :str_field, String
352
+ obj = @klass.new
353
+ obj.attr_set?(:bool_field).should == false
354
+ obj.attr_set?(:str_field).should == false
355
+ obj.instance_variable_set('@bool_field', false)
356
+ obj.instance_variable_set('@str_field', nil)
357
+ obj.attr_set?(:bool_field).should == true
358
+ obj.attr_set?(:str_field).should == true
359
+ end
360
+ end
361
+
362
+ describe '#receive!' do
363
+ before do
364
+ @obj = @klass.new
365
+ end
366
+ it 'accepts things that quack like a hash: have [] and has_key?' do
367
+ foo = mock
368
+ def foo.[](*_) 3 end ; def foo.has_key?(*_) true end
369
+ @obj.receive!(foo)
370
+ @obj.my_int.should == 3
371
+ end
372
+ it 'only accepts things that quack like a hash' do
373
+ lambda{ @obj.receive!(3) }.should raise_error ArgumentError, "Can't receive (it isn't hashlike): {3}"
374
+ end
375
+ it 'lets me call receive! with no args so I can trigger the defaults and after_receive hooks' do
376
+ @obj.should_receive(:impose_defaults!).with({}).ordered
377
+ @obj.should_receive(:run_after_receivers).with({}).ordered
378
+ @obj.receive!
379
+ end
380
+
381
+ it 'receives symbol key' do
382
+ @obj.receive! :my_int => 4
383
+ @obj.my_int.should == 4
384
+ end
385
+ it 'receives string key' do
386
+ @obj.receive! :my_int => 5
387
+ @obj.my_int.should == 5
388
+ end
389
+ it 'with symbol and string key in given hash, takes symbol key only' do
390
+ @obj.receive! :my_int => 7, 'my_int' => 6
391
+ @obj.my_int.should == 7
392
+ end
393
+ it 'only accepts things that are receivers' do
394
+ @klass.class_eval do attr_accessor :bob ; end
395
+ @obj.receive! :my_int => 7, :bob => 12
396
+ @obj.should_not_receive(:bob=)
397
+ @obj.bob.should be_nil
398
+ end
399
+ it 'ignores extra keys' do
400
+ @obj.receive! :my_int => 7, :bob => 12
401
+ @obj.instance_variable_get('@bob').should be_nil
402
+ end
403
+ it 'delegates to receive_{whatever}' do
404
+ @obj.should_receive(:receive_my_int).with(7)
405
+ @obj.receive! :my_int => 7, :bob => 12
406
+ end
407
+ it 'imposes defaults and triggers after_receivers after hash has been sucked in' do
408
+ @obj.should_receive(:receive_my_int).with(7).ordered
409
+ @obj.should_receive(:impose_defaults!).with(:my_int => 7, :bob => 12).ordered
410
+ @obj.should_receive(:run_after_receivers).with(:my_int => 7, :bob => 12).ordered
411
+ @obj.receive! :my_int => 7, :bob => 12
412
+ end
413
+ it 'returns self, to allow chaining' do
414
+ @obj.receive!(:my_int => 7, :bob => 12).should equal(@obj)
415
+ end
416
+
417
+ it 'receives even if there is no setter' do
418
+ @klass.rcvr_reader :a, Integer
419
+ @klass.rcvr :b, Integer
420
+ obj = @klass.receive :my_int => 3, :a => 5, :b => 7
421
+ obj.should_not respond_to(:a=)
422
+ obj.should_not respond_to(:b=)
423
+ obj.my_int.should == 3
424
+ obj.a.should == 5
425
+ obj.instance_variable_get("@b").should == 7
426
+ end
427
+ end
428
+
429
+ describe "receiving type" do
430
+ RECEIVABLES = [Symbol, String, Integer, Float, Time, Date, Array, Hash, Boolean, NilClass, Object]
431
+ ALIASES = [:symbol, :string, :int, :integer, :long, :time, :date, :float, :double, :hash, :map, :array, :null, :boolean, :bytes]
432
+ it "has specs for every type" do
433
+ (Receiver::RECEIVER_BODIES.keys - RECEIVABLES).should be_empty
434
+ end
435
+ it "has specs for every alias" do
436
+ (Receiver::TYPE_ALIASES.keys - ALIASES).should == []
437
+ end
438
+ RECEIVABLES.each do |receivable|
439
+ it "#{receivable} has a receiver_body" do
440
+ Receiver::RECEIVER_BODIES.should have_key(receivable)
441
+ end
442
+ end
443
+ (RECEIVABLES - [String]).each do |receivable|
444
+ it "#{receivable} accepts nil as nil" do
445
+ @klass.rcvr_accessor :nil_field, receivable
446
+ obj = @klass.receive(:nil_field => nil)
447
+ obj.nil_field.should be_nil
448
+ end
449
+ end
450
+ [String].each do |receivable|
451
+ it "#{receivable} accepts nil as empty string" do
452
+ @klass.rcvr_accessor :nil_field, receivable
453
+ obj = @klass.receive(:nil_field => nil)
454
+ obj.nil_field.should == ""
455
+ end
456
+ end
457
+
458
+ it "lets me use an anonymous class as a received type" do
459
+ @klass_2 = Class.new(Wide)
460
+ @klass_2.rcvr_accessor :maw, Integer
461
+ @klass.rcvr_accessor :k2, @klass_2
462
+ obj = @klass.receive({ :my_int => 3, :k2 => { :maw => 2 }})
463
+ obj.k2.maw.should == 2
464
+ end
465
+
466
+ it 'keeps values across a receive!' do
467
+ @klass.rcvr_accessor :repeated, Integer
468
+ @klass.rcvr_accessor :just_second, Integer
469
+ obj = @klass.receive( :my_int => 1, :repeated => 3)
470
+ [obj.my_int, obj.repeated, obj.just_second].should == [1, 3, nil]
471
+ obj.receive!(:repeated => 20, :just_second => 30)
472
+ [obj.my_int, obj.repeated, obj.just_second].should == [1, 20, 30]
473
+ end
474
+
475
+ # ---------------------------------------------------------------------------
476
+
477
+ it 'core class .receive method' do
478
+ Symbol.receive('hi').should == :hi
479
+ Integer.receive(3.4).should == 3
480
+ Float.receive("4.5").should == 4.5
481
+ String.receive(4.5).should == "4.5"
482
+ Time.receive('1985-11-05T04:03:02Z').should == Time.parse('1985-11-05T04:03:02Z')
483
+ Date.receive('1985-11-05T04:03:02Z').should == Date.parse('1985-11-05')
484
+ Array.receive('hi').should == ['hi']
485
+ Hash.receive({:hi => :there}).should == {:hi => :there}
486
+ Boolean.receive("false").should == false
487
+ NilClass.receive(nil).should == nil
488
+ Object.receive(:fnord).should == :fnord
489
+ end
490
+
491
+ # ---------------------------------------------------------------------------
492
+
493
+ def self.it_correctly_converts(type, orig, desired)
494
+ it "for #{type} converts #{orig.inspect} to #{desired.inspect}" do
495
+ field = "#{type}_field".to_sym
496
+ @klass.rcvr_accessor field, type
497
+ obj = @klass.receive( field => orig )
498
+ obj.send(field).should == desired
499
+ end
500
+ end
501
+
502
+ describe 'type coercion' do
503
+ [
504
+ [Symbol, 'foo', :foo], [Symbol, :foo, :foo], [Symbol, nil, nil], [Symbol, '', nil],
505
+ [Integer, '5', 5], [Integer, 5, 5], [Integer, nil, nil], [Integer, '', nil],
506
+ [Integer, '5', 5], [Integer, 5, 5], [Integer, nil, nil], [Integer, '', nil],
507
+ [Float, '5.2', 5.2], [Float, 5.2, 5.2], [Float, nil, nil], [Float, '', nil],
508
+ [String, 'foo', 'foo'], [String, :foo, 'foo'], [String, nil, ""], [String, '', ""],
509
+ [String, 5.2, "5.2"], [String, [1], "[1]"], [String, 1, "1"],
510
+ [Time, '1985-11-05T04:03:02Z', Time.parse('1985-11-05T04:03:02Z')],
511
+ [Time, '1985-11-05T04:03:02+06:00', Time.parse('1985-11-04T22:03:02Z')],
512
+ [Time, Time.parse('1985-11-05T04:03:02Z'), Time.parse('1985-11-05T04:03:02Z')],
513
+ [Date, Time.parse('1985-11-05T04:03:02Z'), Date.parse('1985-11-05')],
514
+ [Date, '1985-11-05T04:03:02Z', Date.parse('1985-11-05')],
515
+ [Date, '1985-11-05T04:03:02+06:00', Date.parse('1985-11-05')],
516
+ [Time, nil, nil], [Time, '', nil], [Time, 'blah', nil],
517
+ [Date, nil, nil], [Date, '', nil], [Date, 'blah', nil],
518
+ [Array, ['this', 'that', 'thother'], ['this', 'that', 'thother'] ],
519
+ [Array, ['this,that,thother'], ['this,that,thother'] ],
520
+ [Array, 'this,that,thother', ['this,that,thother'] ],
521
+ [Array, 'alone', ['alone'] ],
522
+ [Array, '', [] ],
523
+ [Array, nil, nil ],
524
+ [Hash, {:hi => 1}, {:hi => 1}], [Hash, nil, nil], [Hash, "", {}], [Hash, [], {}], [Hash, {}, {}],
525
+ [:boolean, '0', true], [:boolean, 0, true], [:boolean, '', false], [:boolean, [], true], [:boolean, nil, nil],
526
+ [:boolean, '1', true], [:boolean, 1, true], [:boolean, '5', true], [:boolean, 'true', true],
527
+ [NilClass, nil, nil],
528
+ [Object, {:foo => [1]}, {:foo => [1]} ], [Object, nil, nil], [Object, 1, 1],
529
+ ].each do |type, orig, desired|
530
+ it_correctly_converts type, orig, desired
531
+ end
532
+
533
+ describe 'controversially' do
534
+ [
535
+ [Hash, ['does no type checking'], ['does no type checking'] ],
536
+ [Hash, 'does no type checking', 'does no type checking' ],
537
+ ].each do |type, orig, desired|
538
+ it_correctly_converts type, orig, desired
539
+ end
540
+ end
541
+
542
+ describe 'NilClass' do
543
+ it 'only accepts nil' do
544
+ @klass.rcvr_accessor :nil_field, NilClass
545
+ lambda{ @klass.receive( :nil_field => 'hello' ) }.should raise_error(ArgumentError, "This field must be nil, but [hello] was given")
546
+ end
547
+ end
548
+ end
549
+
550
+ end
551
+ end