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