gorillib 0.0.8 → 0.1.0

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 (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
+