gorillib 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGELOG.textile +6 -0
  2. data/README.textile +34 -11
  3. data/VERSION +1 -1
  4. data/gorillib.gemspec +63 -5
  5. data/lib/gorillib/enumerable/sum.rb +2 -2
  6. data/lib/gorillib/hash/compact.rb +2 -29
  7. data/lib/gorillib/hash/deep_compact.rb +2 -12
  8. data/lib/gorillib/hash/deep_dup.rb +4 -0
  9. data/lib/gorillib/hash/deep_merge.rb +2 -14
  10. data/lib/gorillib/hash/indifferent_access.rb +207 -0
  11. data/lib/gorillib/hash/keys.rb +2 -40
  12. data/lib/gorillib/hash/reverse_merge.rb +2 -24
  13. data/lib/gorillib/hash/slice.rb +2 -51
  14. data/lib/gorillib/hash/tree_merge.rb +4 -0
  15. data/lib/gorillib/hashlike.rb +824 -0
  16. data/lib/gorillib/hashlike/compact.rb +60 -0
  17. data/lib/gorillib/hashlike/deep_compact.rb +18 -0
  18. data/lib/gorillib/hashlike/deep_dup.rb +15 -0
  19. data/lib/gorillib/hashlike/deep_merge.rb +20 -0
  20. data/lib/gorillib/hashlike/hashlike_via_accessors.rb +169 -0
  21. data/lib/gorillib/hashlike/keys.rb +59 -0
  22. data/lib/gorillib/hashlike/reverse_merge.rb +31 -0
  23. data/lib/gorillib/hashlike/slice.rb +67 -0
  24. data/lib/gorillib/hashlike/tree_merge.rb +76 -0
  25. data/lib/gorillib/metaprogramming/mattr_accessor.rb +1 -1
  26. data/lib/gorillib/receiver.rb +315 -0
  27. data/lib/gorillib/receiver/active_model_shim.rb +19 -0
  28. data/lib/gorillib/receiver/acts_as_hash.rb +191 -0
  29. data/lib/gorillib/receiver/acts_as_loadable.rb +42 -0
  30. data/lib/gorillib/receiver/tree_diff.rb +74 -0
  31. data/lib/gorillib/receiver/validations.rb +30 -0
  32. data/lib/gorillib/struct/acts_as_hash.rb +108 -0
  33. data/lib/gorillib/struct/hashlike_iteration.rb +0 -0
  34. data/notes/fancy_hashes_and_receivers.textile +120 -0
  35. data/notes/hash_rdocs.textile +97 -0
  36. data/spec/hash/deep_merge_spec.rb +0 -2
  37. data/spec/hash/indifferent_access_spec.rb +391 -0
  38. data/spec/hash/slice_spec.rb +35 -12
  39. data/spec/hashlike/behave_same_as_hash_spec.rb +105 -0
  40. data/spec/hashlike/hashlike_behavior_spec.rb +824 -0
  41. data/spec/hashlike/hashlike_via_accessors_fuzzing_spec.rb +37 -0
  42. data/spec/hashlike/hashlike_via_accessors_spec.rb +262 -0
  43. data/spec/hashlike_spec.rb +302 -0
  44. data/spec/metaprogramming/aliasing_spec.rb +3 -0
  45. data/spec/metaprogramming/cattr_accessor_spec.rb +2 -0
  46. data/spec/metaprogramming/class_attribute_spec.rb +2 -0
  47. data/spec/metaprogramming/delegation_spec.rb +2 -0
  48. data/spec/metaprogramming/mattr_accessor_spec.rb +2 -0
  49. data/spec/metaprogramming/singleton_class_spec.rb +3 -0
  50. data/spec/receiver/acts_as_hash_spec.rb +286 -0
  51. data/spec/receiver_spec.rb +478 -0
  52. data/spec/spec_helper.rb +11 -6
  53. data/spec/string/truncate_spec.rb +1 -0
  54. data/spec/struct/acts_as_hash_fuzz_spec.rb +67 -0
  55. data/spec/struct/acts_as_hash_spec.rb +426 -0
  56. data/spec/support/hashlike_fuzzing_helper.rb +127 -0
  57. data/spec/support/hashlike_helper.rb +75 -0
  58. data/spec/support/hashlike_struct_helper.rb +37 -0
  59. data/spec/support/hashlike_via_delegation.rb +30 -0
  60. data/spec/support/matchers/be_array_eql.rb +12 -0
  61. data/spec/support/matchers/be_hash_eql.rb +14 -0
  62. data/spec/support/matchers/enumerate_method.rb +10 -0
  63. data/spec/support/matchers/evaluate_to_true.rb +5 -0
  64. metadata +62 -4
@@ -43,6 +43,9 @@ describe 'metaprogramming' do
43
43
  end
44
44
 
45
45
  describe 'alias_method_chain' do
46
+
47
+ it 'does not have an effect if already provided by another library.'
48
+
46
49
  it 'creates a with_ and without_ method that chain' do
47
50
  @instance.should respond_to(:bar)
48
51
  feature_aliases = [:bar_with_baz, :bar_without_baz]
@@ -12,6 +12,8 @@ describe 'metaprogramming' do
12
12
  @object = @class.new
13
13
  end
14
14
 
15
+ it 'does not have an effect if already provided by another library.'
16
+
15
17
  it 'uses mattr default' do
16
18
  @class.foo.should be_nil
17
19
  @object.foo.should be_nil
@@ -8,6 +8,8 @@ describe 'metaprogramming' do
8
8
  @sub = Class.new(@klass)
9
9
  end
10
10
 
11
+ it 'does not have an effect if already provided by another lib.'
12
+
11
13
  it 'defaults to nil' do
12
14
  @klass.setting.should be_nil
13
15
  @sub.setting.should be_nil
@@ -69,6 +69,8 @@ describe 'metaprogramming' do
69
69
  @david = Someone.new("David", Somewhere.new("Paulina", "Chicago"))
70
70
  end
71
71
 
72
+ it 'does not have an effect if already provided by another library.'
73
+
72
74
  it 'delegates to methods' do
73
75
  @david.street.should == "Paulina"
74
76
  @david.city.should == "Chicago"
@@ -14,6 +14,8 @@ describe Module do
14
14
  @object = @class.new
15
15
  end
16
16
 
17
+ it 'does not have an effect if already provided by another library.'
18
+
17
19
  it 'uses mattr default' do
18
20
  @module.foo.should be_nil
19
21
  @object.foo.should be_nil
@@ -6,4 +6,7 @@ describe 'Singleton Class' do
6
6
  o = Object.new
7
7
  class << o; self end.should == o.singleton_class
8
8
  end
9
+
10
+ it 'does not have an effect if already provided by another library.'
11
+
9
12
  end
@@ -0,0 +1,286 @@
1
+ require File.dirname(__FILE__)+'/../spec_helper'
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
+ include Gorillib::Hashlike
14
+ #
15
+ rcvr_accessor :a, Integer
16
+ rcvr_accessor :b, Integer
17
+ rcvr_accessor :c, Integer
18
+ # rcvr_reader :b, Integer
19
+ # rcvr_writer :c, Integer
20
+ rcvr_accessor :nil_val, NilClass
21
+ rcvr_accessor :false_val, Boolean
22
+ rcvr_accessor :new_key, String
23
+ end
24
+
25
+ describe Receiver do
26
+
27
+ before do
28
+ @hshlike = SimpleReceiver.new.merge(HashlikeHelper::HASH_TO_TEST_HL_V_A.dup)
29
+ @empty_hshlike = SimpleReceiver.new
30
+ @hshlike_with_array_vals = SimpleReceiver.new.merge(HashlikeHelper::BASE_HSH_WITH_ARRAY_VALS.dup)
31
+ #
32
+ @hshlike_subklass = Class.new(SimpleReceiver)
33
+ @hshlike_subklass_inst = @hshlike_subklass.new.merge(HashlikeHelper::BASE_HSH.dup)
34
+ end
35
+
36
+
37
+ # ===========================================================================
38
+ #
39
+ # Fundamental behavior
40
+
41
+ describe '#[] and #[]= and #store' do
42
+ it_should_behave_like :hashlike_store_and_retrieve
43
+ it_should_behave_like :references_string_and_symbol_keys_equivalently
44
+ it 'reject unknown keys' do
45
+ lambda{ @hshlike[:fnord] = 69 }.should raise_error NoMethodError, /undefined method `fnord=' for/
46
+ lambda{ @hshlike[:fnord] }.should raise_error NoMethodError, /undefined method `fnord' for/
47
+ @hshlike.delete(:fnord).should be_nil
48
+ end
49
+ it 'accepts defined but unset keys' do
50
+ @hshlike[:new_key].should be_nil
51
+ @hshlike[:new_key] = 69
52
+ @hshlike[:new_key].should == 69
53
+ end
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 'd' 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
+ raise 'finish me'
70
+ end
71
+
72
+ it 'has ' do
73
+ @hshlike_subklass.class_eval{ attr_accessor :not_a_receiver }
74
+ raise 'finish me'
75
+ end
76
+ end
77
+
78
+ describe '#delete' do
79
+ it_should_behave_like :hashlike_delete
80
+ end
81
+
82
+ describe '#keys' do
83
+ it_should_behave_like :hashlike_keys
84
+ end
85
+
86
+ # ===========================================================================
87
+ #
88
+ # Iteration
89
+
90
+ describe '#each_pair' do
91
+ describe 'with block' do
92
+ it_should_behave_like :each_pair_on_stringlike_keys, :each_pair
93
+ end
94
+ it_should_behave_like :with_no_block_returns_enumerator, :each_pair
95
+ end
96
+
97
+ describe '#each' do
98
+ describe 'with block' do
99
+ it_should_behave_like :each_pair_on_stringlike_keys, :each
100
+ end
101
+ it_should_behave_like :with_no_block_returns_enumerator, :each
102
+ end
103
+
104
+ describe '#each_key' do
105
+ describe 'with block' do
106
+ it_should_behave_like :each_key_on_stringlike_keys
107
+ end
108
+ it_should_behave_like :with_no_block_returns_enumerator, :each_key
109
+ end
110
+
111
+ describe '#each_value' do
112
+ describe 'with block' do
113
+ it_should_behave_like :each_value_on_stringlike_keys
114
+ end
115
+ it_should_behave_like :with_no_block_returns_enumerator, :each_value
116
+ end
117
+
118
+ # ===========================================================================
119
+ #
120
+ # Retrieval and Membership
121
+
122
+ describe '#values' do
123
+ it_should_behave_like :hashlike_values
124
+ end
125
+
126
+ describe '#values_at' do
127
+ it_should_behave_like :hashlike_values_at_or_of, :values_at
128
+ end
129
+
130
+ describe '#values_of' do
131
+ it_should_behave_like :hashlike_values_at_or_of, :values_of
132
+ end
133
+
134
+ describe '#length' do
135
+ it_should_behave_like :hashlike_length, :length
136
+ end
137
+
138
+ describe '#size' do
139
+ it_should_behave_like :hashlike_length, :size
140
+ end
141
+
142
+ describe '#has_key?' do
143
+ it_should_behave_like :hashlike_has_key?, :has_key?
144
+ it_should_behave_like :hashlike_has_key_string_and_symbol_equivalent, :has_key?
145
+ end
146
+
147
+ describe '#include?' do
148
+ it_should_behave_like :hashlike_has_key?, :include?
149
+ it_should_behave_like :hashlike_has_key_string_and_symbol_equivalent, :include?
150
+ end
151
+
152
+ describe '#key?' do
153
+ it_should_behave_like :hashlike_has_key?, :key?
154
+ it_should_behave_like :hashlike_has_key_string_and_symbol_equivalent, :key?
155
+ end
156
+
157
+ describe '#member?' do
158
+ it_should_behave_like :hashlike_has_key?, :member?
159
+ it_should_behave_like :hashlike_has_key_string_and_symbol_equivalent, :member?
160
+ end
161
+
162
+ describe '#has_value?' do
163
+ it_should_behave_like :hashlike_has_value?, :has_value?
164
+ end
165
+
166
+ describe '#value?' do
167
+ it_should_behave_like :hashlike_has_value?, :value?
168
+ end
169
+
170
+ describe '#fetch' do
171
+ it_should_behave_like :hashlike_fetch
172
+ end
173
+
174
+ describe '#key' do
175
+ it_should_behave_like :hashlike_key
176
+ end
177
+
178
+ describe '#assoc' do
179
+ it_should_behave_like :hashlike_assoc
180
+ end
181
+
182
+ describe '#rassoc' do
183
+ it_should_behave_like :hashlike_rassoc
184
+ end
185
+
186
+ describe '#empty?' do
187
+ it_should_behave_like :hashlike_empty?
188
+ end
189
+
190
+ # ===========================================================================
191
+ #
192
+ # Update, merge!, merge
193
+
194
+ describe 'update' do
195
+ it_should_behave_like :merging_method, :update
196
+ it_should_behave_like :merging_method_with_normal_keys, :update
197
+ it_should_behave_like :merging_method_inplace, :update
198
+ end
199
+
200
+ describe '#merge!' do
201
+ it_should_behave_like :merging_method, :merge!
202
+ it_should_behave_like :merging_method_with_normal_keys, :merge!
203
+ it_should_behave_like :merging_method_inplace, :merge!
204
+ end
205
+
206
+ describe '#merge' do
207
+ it_should_behave_like :merging_method, :merge
208
+ it_should_behave_like :merging_method_with_normal_keys, :merge
209
+ it_should_behave_like :merging_method_returning_new, :merge
210
+ end
211
+
212
+ # ===========================================================================
213
+ #
214
+ # Filters
215
+
216
+ describe '#reject!' do
217
+ it_should_behave_like :hashlike_filter, :reject!
218
+ it_should_behave_like :rejection_filter, :reject!
219
+ it_should_behave_like :filter_modifies_self_returns_nil_if_unchanged, :reject!, false
220
+ end
221
+
222
+ describe '#select!' do
223
+ it_should_behave_like :hashlike_filter, :select!
224
+ it_should_behave_like :selection_filter, :select!
225
+ it_should_behave_like :filter_modifies_self_returns_nil_if_unchanged, :select!, true
226
+ end
227
+
228
+ describe '#delete_if' do
229
+ it_should_behave_like :hashlike_filter, :delete_if
230
+ it_should_behave_like :rejection_filter, :delete_if
231
+ it_should_behave_like :filter_modifies_self_returns_self, :delete_if, false
232
+ end
233
+
234
+ describe '#keep_if' do
235
+ it_should_behave_like :hashlike_filter, :keep_if
236
+ it_should_behave_like :selection_filter, :keep_if
237
+ it_should_behave_like :filter_modifies_self_returns_self, :keep_if, true
238
+ end
239
+
240
+ describe '#reject' do
241
+ it_should_behave_like :hashlike_filter, :select
242
+ it_should_behave_like :selection_filter, :select
243
+ it_should_behave_like :filter_does_not_modify_self_returns_same_class, :reject, false
244
+ end
245
+
246
+ describe '#select' do
247
+ it_should_behave_like :hashlike_filter, :select
248
+ it_should_behave_like :selection_filter, :select
249
+ it_should_behave_like :filter_does_not_modify_self_returns_same_class, :select, true
250
+ end
251
+
252
+ describe '#clear' do
253
+ it_should_behave_like :hashlike_clear
254
+ end
255
+
256
+ # ===========================================================================
257
+ #
258
+ # Conversion
259
+
260
+ describe '#to_hash' do
261
+ it_should_behave_like :hashlike_to_hash
262
+ end
263
+
264
+ describe '#invert' do
265
+ it_should_behave_like :hashlike_invert
266
+ end
267
+
268
+ describe '#flatten' do
269
+ it_should_behave_like :hashlike_flatten
270
+ end
271
+
272
+ # ===========================================================================
273
+ #
274
+ # Sanity check
275
+
276
+ it 'built test objects correctly' do
277
+ @hshlike_subklass .should < @hshlike.class
278
+ @hshlike_subklass .should_not == @hshlike.class
279
+ @hshlike_subklass_inst .should be_a(SimpleReceiver)
280
+ @hshlike_subklass_inst .should be_a(@hshlike_subklass)
281
+ @hshlike_subklass_inst .should_not be_an_instance_of(SimpleReceiver)
282
+ @hshlike .should_not be_a(@hshlike_subklass)
283
+ @hshlike .should be_an_instance_of(SimpleReceiver)
284
+ end
285
+
286
+ end
@@ -0,0 +1,478 @@
1
+ require File.dirname(__FILE__)+'/spec_helper.rb'
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
+ describe Receiver do
15
+ before do
16
+ @klass = Class.new(Wide)
17
+ end
18
+
19
+ it 'tracks an order for the rcvrs' do
20
+ @klass.rcvr_accessor :a, Integer
21
+ @klass.rcvr_reader :b, Integer
22
+ @klass.rcvr_writer :c, Integer
23
+ @klass.rcvr :d, Integer
24
+ @klass.receiver_attr_names.should == [:my_int, :a, :b, :c, :d]
25
+ end
26
+
27
+ describe '.receive' do
28
+ it 'creates a new object and returns it' do
29
+ obj = @klass.new
30
+ @klass.should_receive(:new).and_return(obj)
31
+ ret = @klass.receive({})
32
+ ret.should equal(obj)
33
+ end
34
+
35
+ it 'invokes receive! on the new object' do
36
+ obj = @klass.new
37
+ @klass.should_receive(:new).and_return(obj)
38
+ obj.should_receive(:receive!).with({})
39
+ ret = @klass.receive({})
40
+ end
41
+
42
+ it 'passes extra args to the constructor' do
43
+ obj = @klass.new
44
+ @klass.should_receive(:new).with(:a, :b).and_return(obj)
45
+ obj.should_receive(:receive!).with({})
46
+ ret = @klass.receive(:a, :b, {})
47
+ end
48
+
49
+ it 'accepts an empty arg set (receives an empty hash)' do
50
+ obj = mock
51
+ @klass.should_receive(:new).and_return(obj)
52
+ obj.should_receive(:receive!).with({})
53
+ @klass.receive(nil)
54
+ end
55
+
56
+ it 'uses the *last* arg as the hsh to receive' do
57
+ obj = mock
58
+ hsh_to_receive = { :a => :b }
59
+ hsh_for_constructor = { :c => :d }
60
+ @klass.should_receive(:new).with(hsh_for_constructor).and_return(obj)
61
+ obj.should_receive(:receive!).with(hsh_to_receive)
62
+ @klass.receive(hsh_for_constructor, hsh_to_receive)
63
+ end
64
+ end
65
+
66
+ describe '.receive_tuple' do
67
+ it 'receives a tuple, assigning to rcvrs in order'
68
+
69
+ it 'allows empty tuple'
70
+
71
+ it '?breaks? on too-long tuple'
72
+ end
73
+
74
+
75
+ describe '.receive_foo' do
76
+ it 'injects a superclass, so I can call super() in receive_foo'
77
+ end
78
+
79
+ describe '.rcvr' do
80
+
81
+ it 'creates the receive_{whatever} method' do
82
+ @klass.rcvr_accessor :a, Integer, :foo => :bar_a
83
+ obj = @klass.new
84
+ obj.should respond_to(:receive_a)
85
+ end
86
+
87
+ it 'stores the name, type and extra info into receiver_attrs, and the name (in order) into receiver_attr_names' do
88
+ @klass.rcvr_accessor :a, Integer, :foo => :bar_a
89
+ @klass.rcvr_reader :b, Integer, :foo => :bar_b
90
+ @klass.rcvr :c, Integer, :foo => :bar_c
91
+ @klass.receiver_attr_names.should == [:my_int, :a, :b, :c]
92
+ @klass.receiver_attrs.should == {
93
+ :my_int => {:type => Integer, :name => :my_int},
94
+ :a => {:type => Integer, :name => :a, :foo => :bar_a},
95
+ :b => {:type => Integer, :name => :b, :foo => :bar_b},
96
+ :c => {:type => Integer, :name => :c, :foo => :bar_c},
97
+ }
98
+ end
99
+
100
+ it 'does not replace the class attributes in-place, so that inheritance works' do
101
+ old_receiver_attrs = @klass.receiver_attrs
102
+ old_receiver_attr_names = @klass.receiver_attr_names
103
+ @klass.rcvr_accessor :a, Integer, :foo => :bar_a
104
+ old_receiver_attrs.should_not equal(@klass.receiver_attrs)
105
+ old_receiver_attr_names.should_not equal(@klass.receiver_attr_names)
106
+ end
107
+
108
+ it 'accepts a type alias but uses the aliased class' do
109
+ @klass.rcvr_accessor :my_symbol, :symbol
110
+ @klass.rcvr_accessor :my_bytes, :bytes
111
+ @klass.receiver_attrs[:my_symbol][:type].should == Symbol
112
+ @klass.receiver_attrs[:my_bytes ][:type].should == String
113
+ end
114
+
115
+ it 'does not accept an unknown type' do
116
+ 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?")
117
+ end
118
+ end
119
+
120
+ describe '.rcvr_accessor, .rcvr_reader, .rcvr_writer' do
121
+ it 'accepts rcvr, rcvr_accessor, rcvr_reader, rcvr_writer' do
122
+ @klass.rcvr_accessor :a, Integer
123
+ @klass.rcvr_reader :b, Integer
124
+ @klass.rcvr_writer :c, Integer
125
+ @klass.rcvr :d, Integer
126
+ obj = @klass.new
127
+ obj.should respond_to(:a)
128
+ obj.should respond_to(:a=)
129
+ obj.should respond_to(:b)
130
+ obj.should_not respond_to(:b=)
131
+ obj.should_not respond_to(:c)
132
+ obj.should respond_to(:c=)
133
+ obj.should_not respond_to(:d)
134
+ obj.should_not respond_to(:d=)
135
+ end
136
+
137
+ it 'delegates to rcvr' do
138
+ @klass.should_receive(:rcvr).with(:a, Integer, {:foo => :bar}).ordered
139
+ @klass.should_receive(:rcvr).with(:b, Integer, {:foo => :bar}).ordered
140
+ @klass.should_receive(:rcvr).with(:c, Integer, {:foo => :bar}).ordered
141
+ @klass.should_receive(:rcvr).with(:d, Integer, {:foo => :bar}).ordered
142
+ @klass.rcvr_accessor :a, Integer, :foo => :bar
143
+ @klass.rcvr_reader :b, Integer, :foo => :bar
144
+ @klass.rcvr_writer :c, Integer, :foo => :bar
145
+ @klass.rcvr :d, Integer, :foo => :bar
146
+ end
147
+
148
+ it 'does not modify parent class' do
149
+ @klass.rcvr_accessor :a, Integer
150
+ @klass.rcvr :d, Integer
151
+ obj = Wide.new
152
+ obj.should_not respond_to(:a)
153
+ obj.should_not respond_to(:a=)
154
+ Wide.receiver_attr_names.should == [:my_int]
155
+ Wide.receiver_attrs.should == {:my_int => {:type => Integer, :name => :my_int}}
156
+ end
157
+ end
158
+
159
+ describe 'default values' do
160
+ it 'rcvr accepts a default for an attribute' do
161
+ @klass.rcvr_reader :will_get_a_value, Integer, :default => 12
162
+ @klass.rcvr_reader :has_a_default, Integer, :default => 5
163
+ obj = @klass.receive(:my_int => 3, :will_get_a_value => 9)
164
+ obj.my_int.should == 3
165
+ obj.has_a_default.should == 5
166
+ obj.will_get_a_value.should == 9
167
+ end
168
+
169
+ it 'does not use default if value is set-but-nil' do
170
+ @klass.rcvr_reader :has_a_default, Integer, :default => 5
171
+ obj = @klass.receive(:my_int => 3, :has_a_default => nil)
172
+ obj.has_a_default.should be_nil
173
+ end
174
+ end
175
+
176
+ describe 'required attributes' do
177
+ it 'rcvr accepts required attributes'
178
+ end
179
+
180
+ describe 'typed collections' do
181
+ it 'sets a type for an array with :of => Type' do
182
+ @klass.rcvr_accessor :array_of_symbol, Array, :of => Symbol
183
+ obj = @klass.receive(:array_of_symbol => [:a, 'b', 'c'])
184
+ obj.array_of_symbol.should == [:a, :b, :c]
185
+ end
186
+
187
+ it 'accepts nil if value is nil' do
188
+ @klass.rcvr_accessor :array_of_symbol, Array, :of => Symbol
189
+ obj = @klass.receive(:array_of_symbol => nil)
190
+ obj.array_of_symbol.should == nil
191
+ end
192
+
193
+ it 'accepts complex class as :of => Type' do
194
+ class Foo ; include Receiver ; rcvr_accessor(:foo, Integer) ; end
195
+ @klass.rcvr_accessor :array_of_obj, Array, :of => Foo
196
+ obj = @klass.receive( :array_of_obj => [ {:foo => 3}, {:foo => 5} ] )
197
+ p obj
198
+ obj.array_of_obj.first.foo.should == 3
199
+ obj.array_of_obj.last.foo.should == 5
200
+ end
201
+
202
+ it 'sets a type for a hash with :of => Type' do
203
+ @klass.rcvr_accessor :hash_of_symbol, Hash, :of => Symbol
204
+ obj = @klass.receive(:hash_of_symbol => { :a => 'val_a', 'b' => :val_b , 'c' => 'val_c' })
205
+ obj.hash_of_symbol.should == { :a => :val_a, 'b' => :val_b , 'c' => :val_c }
206
+ end
207
+ end
208
+
209
+ describe '.rcvr_remaining' do
210
+ it 'creates a dummy receiver for the extra params' do
211
+ @klass.should_receive(:rcvr_reader).with(:bob, Hash, {:foo => :bar})
212
+ @klass.rcvr_remaining :bob, :foo => :bar
213
+ end
214
+ it 'does not get params that go with defined attrs (even if there is no writer)' do
215
+ @klass.rcvr_remaining :bob
216
+ hsh = {:my_int => 3, :foo => :bar, :width => 5}
217
+ obj = @klass.receive(hsh)
218
+ obj.bob.should == {:foo => :bar, :width => 5}
219
+ hsh.should == {:my_int => 3, :foo => :bar, :width => 5}
220
+ end
221
+ it 'does not get params whose key starts with "_"' do
222
+ @klass.rcvr_remaining :bob
223
+ hsh = {:my_int => 3, :foo => :bar, :width => 5, :_ignored => 9}
224
+ obj = @klass.receive(hsh)
225
+ obj.bob.should == {:foo => :bar, :width => 5}
226
+ end
227
+ it 'stores them, replacing a previous value' do
228
+ @klass.rcvr_remaining :bob
229
+ obj = @klass.new
230
+ obj.instance_variable_set("@bob", {:foo => 9, :width => 333, :extra => :will_be_gone})
231
+ obj.receive!({ :my_int => 3, :foo => :bar, :width => 5 })
232
+ obj.bob.should == {:foo => :bar, :width => 5}
233
+ end
234
+ end
235
+
236
+ describe '.after_receive, #run_after_receivers' do
237
+ it 'calls each block in order' do
238
+ @klass.after_receive{|hsh| hsh.i_am_calling_you_first }
239
+ @klass.after_receive{|hsh| hsh.i_am_calling_you_second }
240
+ @klass.after_receive{|hsh| hsh.i_am_calling_you_third }
241
+ hsh = {:my_int => 3}
242
+ hsh.should_receive(:i_am_calling_you_first).ordered
243
+ hsh.should_receive(:i_am_calling_you_second).ordered
244
+ hsh.should_receive(:i_am_calling_you_third).ordered
245
+ @klass.receive(hsh)
246
+ end
247
+
248
+ it 'calls the block with the full receive hash' do
249
+ @klass.after_receive{|hsh| hsh.i_am_calling_you }
250
+ hsh = {}
251
+ hsh.should_receive(:i_am_calling_you)
252
+ @klass.receive(hsh)
253
+ end
254
+ end
255
+
256
+ describe '#unset' do
257
+ it 'nukes any existing instance_variable' do
258
+ obj = @klass.new
259
+ obj.my_int = 3
260
+ obj.instance_variable_get('@my_int').should == 3
261
+ obj.send(:unset!, :my_int)
262
+ obj.instance_variable_get('@my_int').should be_nil
263
+ obj.instance_variables.should == []
264
+ end
265
+
266
+ it 'succeeds even if instance_variable never set' do
267
+ obj = @klass.new
268
+ obj.send(:unset!, :my_int)
269
+ obj.instance_variable_get('@my_int').should be_nil
270
+ obj.instance_variables.should == []
271
+ end
272
+
273
+ end
274
+
275
+ describe 'attr_set?' do
276
+ it 'is set if the corresponding instance_variable exists' do
277
+ obj = @klass.new
278
+ obj.attr_set?(:my_int).should == false
279
+ obj.instance_variable_set('@my_int', 3)
280
+ obj.attr_set?(:my_int).should == true
281
+ end
282
+
283
+ it 'can be set but nil or false' do
284
+ @klass.rcvr_accessor :bool_field, Boolean
285
+ @klass.rcvr_accessor :str_field, String
286
+ obj = @klass.new
287
+ obj.attr_set?(:bool_field).should == false
288
+ obj.attr_set?(:str_field).should == false
289
+ obj.instance_variable_set('@bool_field', false)
290
+ obj.instance_variable_set('@str_field', nil)
291
+ obj.attr_set?(:bool_field).should == true
292
+ obj.attr_set?(:str_field).should == true
293
+ end
294
+ end
295
+
296
+ describe '#receive!' do
297
+ before do
298
+ @obj = @klass.new
299
+ end
300
+ it 'accepts things that quack like a hash: have [] and has_key?' do
301
+ foo = mock
302
+ def foo.[](*_) 3 end ; def foo.has_key?(*_) true end
303
+ @obj.receive!(foo)
304
+ @obj.my_int.should == 3
305
+ end
306
+ it 'only accepts things that quack like a hash' do
307
+ lambda{ @obj.receive!(3) }.should raise_error ArgumentError, "Can't receive (it isn't hashlike): {3}"
308
+ end
309
+ it 'lets me call receive! with no args so I can trigger the defaults and after_receive hooks' do
310
+ @obj.should_receive(:impose_defaults!).with({}).ordered
311
+ @obj.should_receive(:run_after_receivers).with({}).ordered
312
+ @obj.receive!
313
+ end
314
+
315
+ it 'receives symbol key' do
316
+ @obj.receive! :my_int => 4
317
+ @obj.my_int.should == 4
318
+ end
319
+ it 'receives string key' do
320
+ @obj.receive! :my_int => 5
321
+ @obj.my_int.should == 5
322
+ end
323
+ it 'with symbol and string key in given hash, takes symbol key only' do
324
+ @obj.receive! :my_int => 7, 'my_int' => 6
325
+ @obj.my_int.should == 7
326
+ end
327
+ it 'only accepts things that are receivers' do
328
+ @klass.class_eval do attr_accessor :bob ; end
329
+ @obj.receive! :my_int => 7, :bob => 12
330
+ @obj.should_not_receive(:bob=)
331
+ @obj.bob.should be_nil
332
+ end
333
+ it 'ignores extra keys' do
334
+ @obj.receive! :my_int => 7, :bob => 12
335
+ @obj.instance_variable_get('@bob').should be_nil
336
+ end
337
+ it 'delegates to receive_{whatever}' do
338
+ @obj.should_receive(:receive_my_int).with(7)
339
+ @obj.receive! :my_int => 7, :bob => 12
340
+ end
341
+ it 'imposes defaults and triggers after_receivers after hash has been sucked in' do
342
+ @obj.should_receive(:receive_my_int).with(7).ordered
343
+ @obj.should_receive(:impose_defaults!).with(:my_int => 7, :bob => 12).ordered
344
+ @obj.should_receive(:run_after_receivers).with(:my_int => 7, :bob => 12).ordered
345
+ @obj.receive! :my_int => 7, :bob => 12
346
+ end
347
+ it 'returns self, to allow chaining' do
348
+ @obj.receive!(:my_int => 7, :bob => 12).should equal(@obj)
349
+ end
350
+
351
+ it 'receives even if there is no setter' do
352
+ @klass.rcvr_reader :a, Integer
353
+ @klass.rcvr :b, Integer
354
+ obj = @klass.receive :my_int => 3, :a => 5, :b => 7
355
+ obj.should_not respond_to(:a=)
356
+ obj.should_not respond_to(:b=)
357
+ obj.my_int.should == 3
358
+ obj.a.should == 5
359
+ obj.instance_variable_get("@b").should == 7
360
+ end
361
+ end
362
+
363
+ describe "receiving type" do
364
+ RECEIVABLES = [Symbol, String, Integer, Float, Time, Date, Array, Hash, Boolean, NilClass, Object]
365
+ ALIASES = [:symbol, :string, :int, :integer, :long, :time, :date, :float, :double, :hash, :map, :array, :null, :boolean, :bytes]
366
+ it "has specs for every type" do
367
+ (Receiver::RECEIVER_BODIES.keys - RECEIVABLES).should be_empty
368
+ end
369
+ it "has specs for every alias" do
370
+ (Receiver::TYPE_ALIASES.keys - ALIASES).should == []
371
+ end
372
+ RECEIVABLES.each do |receivable|
373
+ it "#{receivable} has a receiver_body" do
374
+ Receiver::RECEIVER_BODIES.should have_key(receivable)
375
+ end
376
+ end
377
+ (RECEIVABLES - [String]).each do |receivable|
378
+ it "#{receivable} accepts nil as nil" do
379
+ @klass.rcvr_accessor :nil_field, receivable
380
+ obj = @klass.receive(:nil_field => nil)
381
+ obj.nil_field.should be_nil
382
+ end
383
+ end
384
+ [String].each do |receivable|
385
+ it "#{receivable} accepts nil as empty string" do
386
+ @klass.rcvr_accessor :nil_field, receivable
387
+ obj = @klass.receive(:nil_field => nil)
388
+ obj.nil_field.should == ""
389
+ end
390
+ end
391
+
392
+ it 'keeps values across a receive!' do
393
+ @klass.rcvr_accessor :repeated, Integer
394
+ @klass.rcvr_accessor :just_second, Integer
395
+ obj = @klass.receive( :my_int => 1, :repeated => 3)
396
+ [obj.my_int, obj.repeated, obj.just_second].should == [1, 3, nil]
397
+ obj.receive!(:repeated => 20, :just_second => 30)
398
+ [obj.my_int, obj.repeated, obj.just_second].should == [1, 20, 30]
399
+ end
400
+
401
+ # ---------------------------------------------------------------------------
402
+
403
+ describe 'core class .receive method' do
404
+ Symbol.receive('hi').should == :hi
405
+ Integer.receive(3.4).should == 3
406
+ Float.receive("4.5").should == 4.5
407
+ String.receive(4.5).should == "4.5"
408
+ Time.receive('1985-11-05T04:03:02Z').should == Time.parse('1985-11-05T04:03:02Z')
409
+ Date.receive('1985-11-05T04:03:02Z').should == Date.parse('1985-11-05')
410
+ Array.receive('hi').should == ['hi']
411
+ Hash.receive({:hi => :there}).should == {:hi => :there}
412
+ Boolean.receive("false").should == false
413
+ NilClass.receive(nil).should == nil
414
+ Object.receive(:fnord).should == :fnord
415
+ end
416
+
417
+ # ---------------------------------------------------------------------------
418
+
419
+ def self.it_correctly_converts(type, orig, desired)
420
+ it "for #{type} converts #{orig.inspect} to #{desired.inspect}" do
421
+ field = "#{type}_field".to_sym
422
+ @klass.rcvr_accessor field, type
423
+ obj = @klass.receive( field => orig )
424
+ obj.send(field).should == desired
425
+ end
426
+ end
427
+
428
+ describe 'type coercion' do
429
+ [
430
+ [Symbol, 'foo', :foo], [Symbol, :foo, :foo], [Symbol, nil, nil], [Symbol, '', nil],
431
+ [Integer, '5', 5], [Integer, 5, 5], [Integer, nil, nil], [Integer, '', nil],
432
+ [Integer, '5', 5], [Integer, 5, 5], [Integer, nil, nil], [Integer, '', nil],
433
+ [Float, '5.2', 5.2], [Float, 5.2, 5.2], [Float, nil, nil], [Float, '', nil],
434
+ [String, 'foo', 'foo'], [String, :foo, 'foo'], [String, nil, ""], [String, '', ""],
435
+ [String, 5.2, "5.2"], [String, [1], "[1]"], [String, 1, "1"],
436
+ [Time, '1985-11-05T04:03:02Z', Time.parse('1985-11-05T04:03:02Z')],
437
+ [Time, '1985-11-05T04:03:02+06:00', Time.parse('1985-11-04T22:03:02Z')],
438
+ [Time, Time.parse('1985-11-05T04:03:02Z'), Time.parse('1985-11-05T04:03:02Z')],
439
+ [Date, Time.parse('1985-11-05T04:03:02Z'), Date.parse('1985-11-05')],
440
+ [Date, '1985-11-05T04:03:02Z', Date.parse('1985-11-05')],
441
+ [Date, '1985-11-05T04:03:02+06:00', Date.parse('1985-11-05')],
442
+ [Time, nil, nil], [Time, '', nil], [Time, 'blah', nil],
443
+ [Date, nil, nil], [Date, '', nil], [Date, 'blah', nil],
444
+ [Array, ['this', 'that', 'thother'], ['this', 'that', 'thother'] ],
445
+ [Array, ['this,that,thother'], ['this,that,thother'] ],
446
+ [Array, 'this,that,thother', ['this,that,thother'] ],
447
+ [Array, 'alone', ['alone'] ],
448
+ [Array, '', [] ],
449
+ [Array, nil, nil ],
450
+ [Hash, {:hi => 1}, {:hi => 1}], [Hash, nil, nil], [Hash, "", {}], [Hash, [], {}], [Hash, {}, {}],
451
+ [:boolean, '0', true], [:boolean, 0, true], [:boolean, '', false], [:boolean, [], true], [:boolean, nil, nil],
452
+ [:boolean, '1', true], [:boolean, 1, true], [:boolean, '5', true], [:boolean, 'true', true],
453
+ [NilClass, nil, nil],
454
+ [Object, {:foo => [1]}, {:foo => [1]} ], [Object, nil, nil], [Object, 1, 1],
455
+ ].each do |type, orig, desired|
456
+ it_correctly_converts type, orig, desired
457
+ end
458
+
459
+ describe 'controversially' do
460
+ [
461
+ [Hash, ['does no type checking'], ['does no type checking'] ],
462
+ [Hash, 'does no type checking', 'does no type checking' ],
463
+ ].each do |type, orig, desired|
464
+ it_correctly_converts type, orig, desired
465
+ end
466
+ end
467
+
468
+ describe 'NilClass' do
469
+ it 'only accepts nil' do
470
+ @klass.rcvr_accessor :nil_field, NilClass
471
+ lambda{ @klass.receive( :nil_field => 'hello' ) }.should raise_error(ArgumentError, "This field must be nil, but {hello} was given")
472
+ end
473
+ end
474
+ end
475
+
476
+ end
477
+ end
478
+