gorillib 0.4.1pre → 0.4.2pre

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 (89) hide show
  1. data/.gitignore +13 -10
  2. data/.rspec +1 -1
  3. data/.yardopts +1 -0
  4. data/CHANGELOG.md +47 -0
  5. data/Gemfile +22 -19
  6. data/Guardfile +23 -9
  7. data/README.md +12 -12
  8. data/Rakefile +29 -40
  9. data/VERSION +1 -1
  10. data/examples/benchmark/factories_benchmark.rb +87 -0
  11. data/examples/builder/ironfan.rb +1 -19
  12. data/examples/hash/slicing_methods.rb +101 -0
  13. data/gorillib.gemspec +36 -35
  14. data/lib/gorillib/array/deep_compact.rb +4 -3
  15. data/lib/gorillib/array/simple_statistics.rb +76 -0
  16. data/lib/gorillib/base.rb +0 -1
  17. data/lib/gorillib/builder.rb +15 -30
  18. data/lib/gorillib/collection.rb +159 -57
  19. data/lib/gorillib/collection/model_collection.rb +136 -43
  20. data/lib/gorillib/datetime/parse.rb +4 -2
  21. data/lib/gorillib/{array → deprecated/array}/average.rb +0 -0
  22. data/lib/gorillib/{array → deprecated/array}/random.rb +2 -1
  23. data/lib/gorillib/{array → deprecated/array}/sorted_median.rb +0 -0
  24. data/lib/gorillib/{array → deprecated/array}/sorted_percentile.rb +0 -0
  25. data/lib/gorillib/deprecated/array/sorted_sample.rb +13 -0
  26. data/lib/gorillib/{metaprogramming → deprecated/metaprogramming}/aliasing.rb +0 -0
  27. data/lib/gorillib/enumerable/sum.rb +3 -3
  28. data/lib/gorillib/exception/raisers.rb +92 -22
  29. data/lib/gorillib/factories.rb +550 -0
  30. data/lib/gorillib/hash/mash.rb +15 -58
  31. data/lib/gorillib/hashlike/deep_compact.rb +2 -2
  32. data/lib/gorillib/hashlike/slice.rb +55 -40
  33. data/lib/gorillib/model.rb +5 -3
  34. data/lib/gorillib/model/base.rb +33 -119
  35. data/lib/gorillib/model/defaults.rb +58 -14
  36. data/lib/gorillib/model/errors.rb +10 -0
  37. data/lib/gorillib/model/factories.rb +1 -367
  38. data/lib/gorillib/model/field.rb +40 -18
  39. data/lib/gorillib/model/fixup.rb +16 -0
  40. data/lib/gorillib/model/positional_fields.rb +35 -0
  41. data/lib/gorillib/model/schema_magic.rb +162 -0
  42. data/lib/gorillib/model/serialization.rb +1 -2
  43. data/lib/gorillib/model/serialization/csv.rb +59 -0
  44. data/lib/gorillib/pathname.rb +19 -8
  45. data/lib/gorillib/some.rb +2 -0
  46. data/lib/gorillib/string/constantize.rb +17 -10
  47. data/lib/gorillib/string/inflector.rb +11 -7
  48. data/lib/gorillib/type/boolean.rb +40 -0
  49. data/lib/gorillib/type/extended.rb +76 -40
  50. data/lib/gorillib/type/url.rb +6 -4
  51. data/lib/gorillib/utils/console.rb +1 -18
  52. data/lib/gorillib/utils/edge_cases.rb +18 -0
  53. data/spec/examples/builder/ironfan_spec.rb +5 -10
  54. data/spec/gorillib/array/compact_blank_spec.rb +36 -21
  55. data/spec/gorillib/array/simple_statistics_spec.rb +143 -0
  56. data/spec/gorillib/builder_spec.rb +16 -20
  57. data/spec/gorillib/collection_spec.rb +131 -35
  58. data/spec/gorillib/exception/raisers_spec.rb +39 -0
  59. data/spec/gorillib/hash/deep_compact_spec.rb +3 -3
  60. data/spec/gorillib/model/{record/defaults_spec.rb → defaults_spec.rb} +5 -1
  61. data/spec/gorillib/model/factories_spec.rb +335 -0
  62. data/spec/gorillib/model/{record/overlay_spec.rb → overlay_spec.rb} +0 -0
  63. data/spec/gorillib/model/serialization_spec.rb +2 -2
  64. data/spec/gorillib/model_spec.rb +19 -18
  65. data/spec/gorillib/pathname_spec.rb +7 -7
  66. data/spec/gorillib/string/truncate_spec.rb +3 -13
  67. data/spec/gorillib/type/extended_spec.rb +50 -2
  68. data/spec/gorillib/utils/capture_output_spec.rb +1 -1
  69. data/spec/spec_helper.rb +10 -7
  70. data/spec/support/factory_test_helpers.rb +76 -0
  71. data/spec/support/gorillib_test_helpers.rb +36 -24
  72. data/spec/support/model_test_helpers.rb +39 -2
  73. metadata +86 -51
  74. data/lib/alt/kernel/call_stack.rb +0 -56
  75. data/lib/gorillib/array/sorted_sample.rb +0 -12
  76. data/lib/gorillib/builder/field.rb +0 -5
  77. data/lib/gorillib/collection/has_collection.rb +0 -31
  78. data/lib/gorillib/collection/list_collection.rb +0 -58
  79. data/lib/gorillib/exception/confidence.rb +0 -17
  80. data/lib/gorillib/io/system_helpers.rb +0 -30
  81. data/lib/gorillib/model/record_schema.rb +0 -9
  82. data/lib/gorillib/utils/stub_module.rb +0 -33
  83. data/spec/array/average_spec.rb +0 -24
  84. data/spec/array/sorted_median_spec.rb +0 -18
  85. data/spec/array/sorted_percentile_spec.rb +0 -24
  86. data/spec/array/sorted_sample_spec.rb +0 -28
  87. data/spec/gorillib/metaprogramming/aliasing_spec.rb +0 -180
  88. data/spec/gorillib/model/record/factories_spec.rb +0 -335
  89. data/spec/support/kcode_test_helper.rb +0 -16
@@ -1,27 +1,13 @@
1
1
  require 'spec_helper'
2
+ require 'support/model_test_helpers'
3
+ require 'gorillib/hash/compact'
2
4
 
3
5
  # libs under test
4
6
  require 'gorillib/builder'
5
- require 'gorillib/builder/field'
6
7
  require 'gorillib/collection/model_collection'
7
8
 
8
- # testing helpers
9
- require 'gorillib/hash/compact'
10
- require 'model_test_helpers'
11
-
12
9
  describe Gorillib::Builder, :model_spec => true, :builder_spec => true do
13
10
 
14
- let(:smurf_class) do
15
- class Gorillib::Test::Smurf
16
- include Gorillib::Builder
17
- magic :smurfiness, Integer
18
- magic :weapon, Symbol
19
- end
20
- Gorillib::Test::Smurf
21
- end
22
- let(:poppa_smurf ){ smurf_class.receive(:name => 'Poppa Smurf', :smurfiness => 9, :weapon => 'staff') }
23
- let(:smurfette ){ smurf_class.receive(:name => 'Smurfette', :smurfiness => 11, :weapon => 'charm') }
24
-
25
11
  #
26
12
  # IT BEHAVES LIKE A MODEL
27
13
  # (maybe you wouldn't notice if it was just one little line)
@@ -67,6 +53,14 @@ describe Gorillib::Builder, :model_spec => true, :builder_spec => true do
67
53
  wildcat.receive!({}){|c| expect_7 = 7 ; expect_obj = c }
68
54
  expect_7.should == 7 ; expect_obj.should == wildcat
69
55
  end
56
+ it 'with a block, returns its return value' do
57
+ val = mock_val
58
+ wildcat.receive!{ val }.should == val
59
+ wildcat.receive!{|obj| val }.should == val
60
+ end
61
+ it 'with no block, returns nil' do
62
+ wildcat.receive!.should be_nil
63
+ end
70
64
  end
71
65
 
72
66
  context ".magic" do
@@ -129,14 +123,16 @@ describe Gorillib::Builder, :model_spec => true, :builder_spec => true do
129
123
  wildcat.should respond_to(:doors)
130
124
  wildcat.should_not respond_to(:doors=)
131
125
  end
126
+
127
+
132
128
  end
133
129
 
134
130
  context 'collections' do
135
131
  subject{ garage }
136
- let(:sample_val){ Gorillib::ModelCollection.receive([wildcat], :name, car_class) }
132
+ let(:sample_val){ Gorillib::ModelCollection.receive([wildcat], key_method: :name, item_type: car_class) }
137
133
  let(:raw_val ){ [ wildcat.attributes ] }
138
134
  it_behaves_like "a model field", :cars
139
- it("#read_attribute is an empty collection if never set"){ subject.read_attribute(:cars).should == Gorillib::ModelCollection.new }
135
+ it("#read_attribute is an empty collection if never set"){ subject.read_attribute(:cars).should == Gorillib::ModelCollection.new(key_method: :to_key) }
140
136
 
141
137
  it 'a collection holds named objects' do
142
138
  garage.cars.should be_empty
@@ -157,13 +153,13 @@ describe Gorillib::Builder, :model_spec => true, :builder_spec => true do
157
153
 
158
154
  # examine the whole collection
159
155
  garage.cars.keys.should == [:cadzilla, :wildcat, :ford_39]
160
- garage.cars.should == Gorillib::ModelCollection.receive([cadzilla, wildcat, ford_39], :name, car_class)
156
+ garage.cars.should == Gorillib::ModelCollection.receive([cadzilla, wildcat, ford_39], key_method: :name, item_type: car_class)
161
157
  end
162
158
 
163
159
  it 'lazily autovivifies collection items' do
164
160
  garage.cars.should be_empty
165
161
  garage.car(:chimera).should be_a(car_class)
166
- garage.cars.should == Gorillib::ModelCollection.receive([{:name => :chimera}], :name, car_class)
162
+ garage.cars.should == Gorillib::ModelCollection.receive([{:name => :chimera}], key_method: :name, item_type: car_class)
167
163
  end
168
164
 
169
165
  context 'collection getset method' do
@@ -2,8 +2,7 @@ require 'spec_helper'
2
2
  #
3
3
  require 'gorillib/model'
4
4
  require 'gorillib/collection/model_collection'
5
- require 'gorillib/collection/list_collection'
6
- require 'model_test_helpers'
5
+ require 'support/model_test_helpers'
7
6
 
8
7
  shared_context :collection_spec do
9
8
  # a collection with the internal :clxn mocked out, and a method 'innards' to
@@ -15,17 +14,17 @@ shared_context :collection_spec do
15
14
  end
16
15
  end
17
16
 
18
- shared_examples_for 'a collection' do
17
+ shared_examples_for 'a collection' do |options={}|
19
18
  subject{ string_collection }
20
19
 
21
20
  context '.receive' do
22
21
  it 'makes a new collection, has it #receive! the cargo, returns it' do
23
22
  mock_collection = mock('collection')
24
23
  mock_cargo = mock('cargo')
25
- mock_args = [mock, mock]
26
- described_class.should_receive(:new).with(*mock_args).and_return(mock_collection)
24
+ mock_args = {key_method: mock, item_type: mock}
25
+ described_class.should_receive(:new).with(mock_args).and_return(mock_collection)
27
26
  mock_collection.should_receive(:receive!).with(mock_cargo)
28
- described_class.receive(mock_cargo, *mock_args).should == mock_collection
27
+ described_class.receive(mock_cargo, mock_args).should == mock_collection
29
28
  end
30
29
  end
31
30
 
@@ -61,16 +60,19 @@ shared_examples_for 'a collection' do
61
60
  end
62
61
  end
63
62
 
64
- # context '#receive (array)', :if => :receives_arrays do
65
- # it 'adopts the contents of an array' do
66
- # string_collection.receive!(%w[horton hears a who])
67
- # string_collection.values.should == %w[wocket in my pocket horton hears a who]
68
- # end
69
- # it 'does not adopt duplicates' do
70
- # string_collection.receive!(%w[red fish blue fish])
71
- # string_collection.values.should == %w[wocket in my pocket red fish blue]
72
- # end
73
- # end
63
+ unless (options[:receiving_arrays] == :skip)
64
+ context '#receive (array)', :if => :receives_arrays do
65
+ it 'adopts the contents of an array' do
66
+ string_collection.receive!(%w[horton hears a who])
67
+ string_collection.values.should == %w[wocket in my pocket horton hears a who]
68
+ end
69
+ it 'does not adopt duplicates' do
70
+ string_collection.receive!(%w[red fish blue fish])
71
+ string_collection.values.should == %w[wocket in my pocket red fish blue]
72
+ end
73
+ end
74
+ end
75
+
74
76
  context '#receive (hash)' do
75
77
  it 'adopts the values of a hash' do
76
78
  string_collection.receive!({horton: "horton", hears: "hears", a: "a", who: "who" })
@@ -96,6 +98,7 @@ shared_examples_for 'a keyed collection' do
96
98
  end
97
99
 
98
100
  context '#fetch' do
101
+ ABSENT_KEY_ERROR_RE = /(key not found: 69|index 69 outside)/
99
102
  it 'retrieves an object if present' do
100
103
  subject[1] = mock_val
101
104
  subject.fetch(1).should equal(mock_val)
@@ -106,7 +109,7 @@ shared_examples_for 'a keyed collection' do
106
109
  got_here.should == 'yup'
107
110
  end
108
111
  it 'if absent and no block given: raises an error' do
109
- ->{ subject.fetch(69) }.should raise_error IndexError, /(key not found: 69|index 69 outside)/
112
+ ->{ subject.fetch(69) }.should raise_error IndexError, ABSENT_KEY_ERROR_RE
110
113
  end
111
114
  end
112
115
 
@@ -176,30 +179,123 @@ shared_examples_for 'an auto-keyed collection' do
176
179
  end
177
180
 
178
181
  describe 'collections:', :model_spec, :collection_spec do
182
+ let(:symbolized_test_items){ {:wocket => 'wocket', :in => 'in', :my => 'my', :pocket => 'pocket'} }
183
+ let(:capitalized_test_items){ {'WOCKET' => 'wocket', 'IN' => 'in', 'MY' => 'my', 'POCKET' => 'pocket'} }
184
+
185
+ describe Gorillib::Collection do
186
+ context 'with no key_method (only explicit labels can be stored)' do
187
+ let(:string_collection){ described_class.receive(symbolized_test_items) }
188
+ let(:shouty_collection){ described_class.receive(capitalized_test_items) }
189
+ it_behaves_like 'a collection', :receiving_arrays => :skip
190
+ it_behaves_like 'a keyed collection'
191
+ end
179
192
 
180
- describe Gorillib::ListCollection do
181
- let(:string_collection){ described_class.receive(%w[wocket in my pocket]) }
182
- it_behaves_like 'a collection'
183
- # it_behaves_like 'a keyed collection'
193
+ context 'with no key_method (only explicit labels can be stored)' do
194
+ let(:string_collection){ described_class.receive(symbolized_test_items, key_method: :to_sym) }
195
+ let(:shouty_collection){ described_class.receive(capitalized_test_items, key_method: :upcase) }
196
+ it_behaves_like 'a collection', :receiving_arrays => true
197
+ it_behaves_like 'an auto-keyed collection'
198
+ end
184
199
  end
185
200
 
186
- describe Gorillib::Collection, :receives_arrays => false do
187
- let(:string_collection){ described_class.receive({:wocket => 'wocket', :in => 'in', :my => 'my', :pocket => 'pocket'}) }
188
- let(:shouty_collection){ described_class.receive({'WOCKET' => 'wocket', 'IN' => 'in', 'MY' => 'my', 'POCKET' => 'pocket'}) }
189
- it_behaves_like 'a collection', :receives_arrays => false
190
- it_behaves_like 'a keyed collection'
201
+ describe Gorillib::ModelCollection, :model_spec do
202
+ context do
203
+ let(:string_collection){ described_class.receive(%w[wocket in my pocket], key_method: :to_sym, item_type: String) }
204
+ let(:shouty_collection){ described_class.receive(%w[wocket in my pocket], key_method: :upcase, item_type: String) }
205
+ it_behaves_like 'a collection'
206
+ it_behaves_like 'a keyed collection'
207
+ it_behaves_like 'an auto-keyed collection'
208
+ end
209
+
210
+ subject{ smurf_collection }
211
+ let(:smurf_collection){ described_class.receive([], key_method: :name, item_type: smurf_class) }
212
+ let(:test_item){ papa_smurf }
213
+ let(:test_attrs){ test_item.attributes }
214
+ let(:mock_factory){ mf = mock('factory'); mf.stub!(:native? => true) ; mf }
191
215
 
192
- # I only want the 'adopts the contents of an array' to run when the
193
- # it_behaves_like group says it should be part of the specs.
194
- it "needs travis's rspec help on the 'adopts the contents of an array' spec"
216
+ context '#receive_item' do
217
+ before do
218
+ @test_proc = ->{ 'test' };
219
+ subject.stub(item_type: mock_factory)
220
+ end
195
221
 
222
+ it 'sends it to item_type for receive, then adds it' do
223
+ mock_factory.should_receive(:receive).with(mock_val, &@test_proc).and_return(mock_val)
224
+ subject.should_receive(:add).with(mock_val, nil)
225
+ subject.receive_item(nil, mock_val, &@test_proc)
226
+ end
227
+ it 'accepts an explicit label' do
228
+ mock_factory.should_receive(:receive).with(mock_val, &@test_proc).and_return(mock_val)
229
+ subject.should_receive(:add).with(mock_val, 'truffula')
230
+ subject.receive_item('truffula', mock_val, &@test_proc)
231
+ end
232
+ end
233
+
234
+ context '#update_or_add' do
235
+ it "if absent, creates item with given attrs" do
236
+ test_proc = ->{ 'test' };
237
+ subject.should_receive(:receive_item).with('truffula', test_attrs.merge(name: 'truffula'), &test_proc).and_return(test_item)
238
+ #
239
+ subject.update_or_add('truffula', test_attrs, &test_proc)
240
+ end
241
+ it "if there's no key_method, does not it as an attr to create" do
242
+ subject.send(:remove_instance_variable, '@key_method')
243
+ subject.should_receive(:receive_item).with('truffula', test_attrs)
244
+ #
245
+ subject.update_or_add('truffula', test_attrs)
246
+ end
247
+ it "if present, updates item with attrs" do
248
+ test_proc = ->{ 'test' };
249
+ hsh = { :smurfiness => 99 }
250
+ subject['truffula'] = test_item
251
+ test_item.should_receive(:receive!).with(hsh, &test_proc)
252
+ #
253
+ subject.update_or_add('truffula', hsh, &test_proc)
254
+ end
255
+ it "returns item" do
256
+ updated_item = test_item.dup ; updated_item.name = 'truffula'
257
+ subject.update_or_add('truffula', test_attrs).should == updated_item
258
+ subject.update_or_add('truffula', test_item ).should == test_item
259
+ end
260
+ it 'FIXME: does not behave right on existing bojects' do
261
+ updated_item = test_item.dup ; updated_item.name = 'truffula'
262
+ subject.update_or_add('truffula', test_item ).should == updated_item
263
+ end
264
+ it "adds item to collection" do
265
+ updated_item = test_item.dup ; updated_item.name = 'truffula'
266
+ subject.update_or_add('truffula', test_attrs)
267
+ subject['truffula'].should == updated_item
268
+ end
269
+ end
196
270
  end
197
271
 
198
- describe Gorillib::ModelCollection do
199
- let(:string_collection){ described_class.receive(%w[wocket in my pocket], :to_sym, String) }
200
- let(:shouty_collection){ described_class.receive(%w[wocket in my pocket], :upcase, String) }
201
- it_behaves_like 'a collection'
202
- it_behaves_like 'a keyed collection'
203
- it_behaves_like 'an auto-keyed collection'
272
+
273
+ describe Gorillib::Model do
274
+ describe '.collection' do
275
+ let(:described_class){ smurf_village_class }
276
+ subject{ described_class.new(name: :smurf_town) }
277
+ before do
278
+ smurf_collection_class ; smurf_village_class
279
+ smurf_class.field :village, smurf_village_class
280
+ end
281
+
282
+ it 'adds an eponymous field' do
283
+ described_class.should have_field(:smurfs)
284
+ end
285
+
286
+ it 'sets a default that auto-vivifies the collection field' do
287
+ subject.smurfs.should be_a(Gorillib::ModelCollection)
288
+ subject.smurfs.belongs_to.should == subject
289
+ end
290
+
291
+ it 'receives' do
292
+ subject = smurf_village_class.receive({name: :smurf_town,
293
+ smurfs: [
294
+ { name: 'whatever_smurf', smurfiness: 20},
295
+ ]})
296
+ subject.smurfs['whatever_smurf'].village.should == subject
297
+ end
298
+ end
299
+
204
300
  end
205
301
  end
@@ -9,6 +9,7 @@ describe 'raisers' do
9
9
  def should_return_true
10
10
  yield.should be_true
11
11
  end
12
+ # different rubies have different error messages ARRGH.
12
13
  def capture_error
13
14
  message = 'should have raised, did not'
14
15
  begin
@@ -42,6 +43,11 @@ describe 'raisers' do
42
43
  should_raise_my_error(capture_error{ [].fill() }){ described_class.check_arity!([], 1..3) }
43
44
  should_raise_my_error(capture_error{ [].to_s(1) }){ described_class.check_arity!([1], 0) }
44
45
  end
46
+ it 'appends result of block (if given) to message' do
47
+ str = "esiar no delave ylno"
48
+ ->{ described_class.check_arity!([], 1..3){ str.reverse! } }.should raise_error(/only evaled on raise/)
49
+ str.should == "only evaled on raise"
50
+ end
45
51
  end
46
52
 
47
53
  context '.arity_at_least!' do
@@ -57,4 +63,37 @@ describe 'raisers' do
57
63
  end
58
64
  end
59
65
  end
66
+
67
+ describe TypeMismatchError do
68
+ context '.mismatched!' do
69
+ let(:error_message){ /.+ has mismatched type/ }
70
+ it 'raises an error' do
71
+ should_raise_my_error{ described_class.mismatched!("string", Integer) }
72
+ should_raise_my_error{ described_class.mismatched!(Object.new) }
73
+ end
74
+ end
75
+
76
+ context '.check_type!' do
77
+ let(:error_message){ /.+ has mismatched type; expected .+/ }
78
+ it 'raises true if any type matches' do
79
+ should_return_true{ described_class.check_type!("string", [Integer, String]) }
80
+ end
81
+ it 'raises an error if nothing matches' do
82
+ should_raise_my_error{ described_class.check_type!("string", [Integer, Float]) }
83
+ should_raise_my_error{ described_class.check_type!("string", [Integer]) }
84
+ should_raise_my_error{ described_class.check_type!("string", Integer) }
85
+ end
86
+ it 'checks is_a? given a class' do
87
+ should_return_true{ described_class.check_type!("string", [Integer, String]) }
88
+ should_return_true{ described_class.check_type!(7, [Integer, String]) }
89
+ should_raise_my_error{ described_class.check_type!(:symbol, [Integer, String]) }
90
+ end
91
+ it 'checks responds_to? given a symbol' do
92
+ should_return_true{ described_class.check_type!("string", [:to_str, :to_int]) }
93
+ should_return_true{ described_class.check_type!(7, [:to_str, :to_int]) }
94
+ should_raise_my_error{ described_class.check_type!(:symbol, [:to_str, :to_int]) }
95
+ end
96
+ end
97
+ end
98
+
60
99
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
  require 'gorillib/hash/deep_compact'
3
3
  require 'gorillib/array/deep_compact'
4
4
 
5
- describe Hash, :hashlike_spec => true do
5
+ describe Hash, :hashlike_spec do
6
6
  describe 'array/deep_compact and hash/deep_compact' do
7
7
  it "should respond to the method deep_compact!" do
8
8
  { }.should respond_to :deep_compact!
@@ -13,13 +13,13 @@ describe Hash, :hashlike_spec => true do
13
13
  end
14
14
 
15
15
  it "should return a hash with all blank values removed recursively" do
16
- @test_hash = {:e=>["", nil, [], {}, "foo", { :a=> [nil, {}, { :c=> ["","",[]] } ], :b => nil }]}
16
+ @test_hash = {:e => ["", nil, [], {}, "foo", { :a => [nil, {}, { :c => ["","",[]] } ], :b => nil }]}
17
17
  @test_hash.deep_compact!.should == {:e=>["foo"]}
18
18
  end
19
19
  end
20
20
  end
21
21
 
22
- describe Array, :hashlike_spec => true do
22
+ describe Array, :hashlike_spec do
23
23
  describe 'array/deep_compact and hash/deep_compact' do
24
24
  it "should respond to the method deep_compact!" do
25
25
  [ ].should respond_to :deep_compact!
@@ -1,6 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
- require 'gorillib/model'
3
+ require 'gorillib/metaprogramming/concern'
4
+ require 'gorillib/metaprogramming/class_attribute'
5
+ require 'gorillib/model/named_schema'
6
+ require 'gorillib/model/base'
7
+ require 'gorillib/model/schema_magic'
4
8
  require 'gorillib/model/field'
5
9
  require 'gorillib/model/defaults'
6
10
 
@@ -0,0 +1,335 @@
1
+ require 'spec_helper'
2
+ # require 'support/factory_test_helpers'
3
+
4
+ require 'gorillib/object/blank'
5
+ require 'gorillib/object/try_dup'
6
+ require 'gorillib/hash/slice'
7
+ require 'gorillib/metaprogramming/class_attribute'
8
+ require 'gorillib/string/inflector'
9
+
10
+ require 'gorillib/collection'
11
+ require 'gorillib/model/factories'
12
+
13
+ require 'factory_test_helpers'
14
+
15
+ describe '', :model_spec, :factory_spec, :only do
16
+
17
+ describe Gorillib::Factory do
18
+ describe 'Factory()' do
19
+ it 'turns a proc into an ApplyProcFactory' do
20
+ ff = Gorillib::Factory( ->(obj){ "bob says #{obj}" } )
21
+ ff.receive(3).should == "bob says 3"
22
+ end
23
+ it 'returns anything that responds to #receive directly' do
24
+ ff = Object.new ; ff.define_singleton_method(:receive){}
25
+ Gorillib::Factory(ff).should equal(ff)
26
+ end
27
+ it 'returns a factory directly' do
28
+ ff = Gorillib::Factory::SymbolFactory.new
29
+ Gorillib::Factory(ff).should equal(ff)
30
+ end
31
+ it 'does not look up factory **classes**' do
32
+ ->{ Gorillib::Factory(Gorillib::Factory::SymbolFactory) }.should raise_error(ArgumentError, /Don\'t know which factory makes/)
33
+ end
34
+ it 'looks up factories by typename' do
35
+ Gorillib::Factory(:symbol ).should be_a(Gorillib::Factory::SymbolFactory)
36
+ Gorillib::Factory(:identical).should == (::Whatever)
37
+ end
38
+ it 'looks up factories by class' do
39
+ Gorillib::Factory(Symbol).should be_a(Gorillib::Factory::SymbolFactory)
40
+ Gorillib::Factory(String).should be_a(Gorillib::Factory::StringFactory)
41
+ end
42
+ it 'calls Gorillib::Factory.lookup' do
43
+ x = mock
44
+ Gorillib::Factory.should_receive(:find).with(x)
45
+ Gorillib::Factory(x)
46
+ end
47
+ end
48
+ end
49
+
50
+ # __________________________________________________________________________
51
+ #
52
+ #
53
+ #
54
+ describe Gorillib::Factory::StringFactory do
55
+ it_behaves_like :it_considers_native, 'foo', ''
56
+ it_behaves_like :it_converts, :foo => 'foo', 3 => '3', false => "false", [] => "[]"
57
+ it_behaves_like :it_considers_blankish, nil
58
+ it_behaves_like :it_is_registered_as, :string, String
59
+ its(:typename){ should == :string }
60
+ end
61
+
62
+ describe Gorillib::Factory::SymbolFactory do
63
+ it_behaves_like :it_considers_native, :foo, :"symbol :with weird chars"
64
+ it_behaves_like :it_converts, 'foo' => :foo
65
+ it_behaves_like :it_considers_blankish, nil, ""
66
+ it_behaves_like :it_is_a_mismatch_for, 3, false, []
67
+ it_behaves_like :it_is_registered_as, :symbol, Symbol
68
+ its(:typename){ should == :symbol }
69
+ end
70
+
71
+ describe Gorillib::Factory::RegexpFactory do
72
+ it_behaves_like :it_considers_native, /foo/, //
73
+ it_behaves_like :it_converts, 'foo' => /foo/, ".*" => /.*/
74
+ it_behaves_like :it_considers_blankish, nil, ""
75
+ it_behaves_like :it_is_a_mismatch_for, :foo, 3, false, []
76
+ it_behaves_like :it_is_registered_as, :regexp, Regexp
77
+ its(:typename){ should == :regexp }
78
+ end
79
+
80
+ describe Gorillib::Factory::IntegerFactory do
81
+ it_behaves_like :it_considers_native, 1, -1, 123_456_789_123_456_789_123_456_789_123_456_789
82
+ #
83
+ it_behaves_like :it_converts, '1' => 1, '0' => 0, '1234567' => 1234567
84
+ it_behaves_like :it_converts, '123456789123456789123456789123456789' => 123_456_789_123_456_789_123_456_789_123_456_789
85
+ it_behaves_like :it_converts, '1_234_567' => 1234567
86
+ it_behaves_like :it_converts, 1.0 => 1, 1.99 => 1, -0.9 => 0
87
+ it_behaves_like :it_converts, Complex(1,0) => 1, Complex(1.0,0) => 1, Rational(5,2) => 2
88
+ #
89
+ it_behaves_like :it_considers_blankish, nil, ''
90
+ it_behaves_like :it_is_a_mismatch_for, Complex(1,0.0), Complex(0,3), :foo, false, []
91
+ it_behaves_like :it_is_a_mismatch_for, '8_', 'one', '3blindmice', '_8_'
92
+ it_behaves_like :it_is_a_mismatch_for, Float::INFINITY, Float::NAN
93
+ # Does not accept floating-point-ish
94
+ it_behaves_like :it_is_a_mismatch_for, '1.0', '0.0', '1234567.0'
95
+ it_behaves_like :it_is_a_mismatch_for, '1e5', '1_234_567e-3', '1_234_567.0', '+11.123E+12'
96
+ # Handles hex
97
+ it_behaves_like :it_converts, '011' => 9, '0x10' => 16
98
+ # Intolerant of cruft, unlike GraciousIntegerFactory
99
+ it_behaves_like :it_is_a_mismatch_for, '0L', '1_234_567L', '1_234_567e-3f', '1,234,567'
100
+ #
101
+ it_behaves_like :it_is_registered_as, :int, :integer, Integer
102
+ its(:typename){ should == :integer }
103
+ end
104
+
105
+ describe Gorillib::Factory::GraciousIntegerFactory do
106
+ it_behaves_like :it_considers_native, 1, -1, 123_456_789_123_456_789_123_456_789_123_456_789
107
+ #
108
+ it_behaves_like :it_converts, '1' => 1, '0' => 0, '1234567' => 1234567
109
+ it_behaves_like :it_converts, '123456789123456789123456789123456789' => 123_456_789_123_456_789_123_456_789_123_456_789
110
+ it_behaves_like :it_converts, '1_234_567' => 1234567
111
+ it_behaves_like :it_converts, 1.0 => 1, 1.99 => 1, -0.9 => 0
112
+ it_behaves_like :it_converts, Complex(1,0) => 1, Complex(1.0,0) => 1, Rational(5,2) => 2
113
+ #
114
+ it_behaves_like :it_considers_blankish, nil, ''
115
+ it_behaves_like :it_is_a_mismatch_for, Complex(1,0.0), Complex(0,3), :foo, false, []
116
+ it_behaves_like :it_is_a_mismatch_for, '8_', 'one', '3blindmice', '_8_'
117
+ it_behaves_like :it_is_a_mismatch_for, Float::INFINITY, Float::NAN
118
+ # Handles floating-point well
119
+ it_behaves_like :it_converts, '1.0' => 1, '0.0' => 0, '1234567.0' => 1234567
120
+ it_behaves_like :it_converts, '1e5' => 100000, '1_234_567e-3' => 1234, '1_234.5e3' => 1_234_500, '+11.123E+12' => 11_123_000_000_000
121
+ # Silently confuses hex and octal
122
+ it_behaves_like :it_converts, '011' => 9, '0x10' => 16, '0x1.999999999999ap4' => 25
123
+ # Tolerates some cruft, unlike IntegerFactory
124
+ it_behaves_like :it_converts, '0L' => 0, '1_234_567L' => 1234567, '1234.5e3f' => 1_234_500, '1234567e-3f' => 1_234
125
+ it_behaves_like :it_converts, '1,234,567' => 1234567, ',,,,1,23,45.67e,-1,' => 1234
126
+ #
127
+ it_behaves_like :it_is_registered_as, :gracious_int
128
+ its(:typename){ should == :integer }
129
+ end
130
+
131
+ describe Gorillib::Factory::FloatFactory do
132
+ it_behaves_like :it_considers_native, 1.0, 1.234567e6, 123_456_789_123_456_789_123_456_789_123_456_789.0
133
+ it_behaves_like :it_considers_native, Float::INFINITY, Float::NAN
134
+ it_behaves_like :it_converts, '1' => 1.0, '0' => 0.0, '1234567' => 1234567.0
135
+ it_behaves_like :it_converts, '1.0' => 1.0, '0.0' => 0.0, '1234567.0' => 1234567.0
136
+ it_behaves_like :it_converts, '123456789123456789123456789123456789' => 123_456_789_123_456_789_123_456_789_123_456_789.0
137
+ it_behaves_like :it_converts, '1_234_567' => 1234567.0
138
+ it_behaves_like :it_converts, 1 => 1.0
139
+ it_behaves_like :it_converts, Complex(1,0) => 1.0, Complex(1.0,0) => 1.0, Rational(5,2) => 2.5
140
+ it_behaves_like :it_converts, '1e5' => 1e5, '1_234_567e-3' => 1234.567, '1_234_567.0' => 1234567.0, '+11.123E+700' => 11.123e700
141
+ #
142
+ it_behaves_like :it_considers_blankish, nil, ''
143
+ it_behaves_like :it_is_a_mismatch_for, Complex(1,0.0), Complex(0,3), :foo, false, []
144
+ #
145
+ # Different from #to_f
146
+ it_behaves_like :it_converts, '011' => 11.0, '0x10' => 16.0, '0x1.999999999999ap4' => 25.6
147
+ it_behaves_like :it_is_a_mismatch_for, '0L', '1_234_567L', '1_234_567e-3f'
148
+ it_behaves_like :it_is_a_mismatch_for, '_8_', 'one', '3blindmice', '8_'
149
+ #
150
+ it_behaves_like :it_is_registered_as, :float, Float
151
+ its(:typename){ should == :float }
152
+ end
153
+
154
+ describe Gorillib::Factory::GraciousFloatFactory do
155
+ it_behaves_like :it_considers_native, 1.0, 1.234567e6, 123_456_789_123_456_789_123_456_789_123_456_789.0
156
+ it_behaves_like :it_converts, '1' => 1.0, '0' => 0.0, '1234567' => 1234567.0
157
+ it_behaves_like :it_converts, '1.0' => 1.0, '0.0' => 0.0, '1234567.0' => 1234567.0
158
+ it_behaves_like :it_converts, '123456789123456789123456789123456789' => 123_456_789_123_456_789_123_456_789_123_456_789.0
159
+ it_behaves_like :it_converts, '1_234_567' => 1234567.0
160
+ it_behaves_like :it_converts, 1 => 1.0
161
+ it_behaves_like :it_converts, Complex(1,0) => 1.0, Complex(1.0,0) => 1.0, Rational(5,2) => 2.5
162
+ it_behaves_like :it_converts, '1e5' => 1e5, '1_234_567e-3' => 1234.567, '1_234_567.0' => 1234567.0, '+11.123E+700' => 11.123e700
163
+ #
164
+ it_behaves_like :it_considers_blankish, nil, ''
165
+ it_behaves_like :it_is_a_mismatch_for, Complex(1,0.0), Complex(0,3), :foo, false, []
166
+ #
167
+ # Different from stricter FloatFactory or laxer #to_f
168
+ it_behaves_like :it_converts, '011' => 11.0, '0x10' => 16.0, '0x1.999999999999ap4' => 25.6
169
+ it_behaves_like :it_converts, '0L' => 0.0, '1_234_567L' => 1234567.0, '1_234_567e-3f' => 1234.567
170
+ it_behaves_like :it_is_a_mismatch_for, '_8_', 'one', '3blindmice', '8_'
171
+ it_behaves_like :it_converts, '1,234,567e-3' => 1234.567, ',,,,1,23,45.67e,-1,' => 1234.567, '+11.123E+700' => 11.123e700
172
+ #
173
+ it_behaves_like :it_is_registered_as, :gracious_float
174
+ its(:typename){ should == :float }
175
+ end
176
+
177
+ describe Gorillib::Factory::ComplexFactory do
178
+ cplx0 = Complex(0) ; cplx1 = Complex(1) ; cplx1234500 = Complex(1_234_500,0) ; cplx1234500f = Complex(1_234_500.0,0) ;
179
+ it_behaves_like :it_considers_native, Complex(1,3), Complex(1,0)
180
+ it_behaves_like :it_converts, '1234.5e3' => cplx1234500f, '1234500' => cplx1234500, '0' => cplx0
181
+ it_behaves_like :it_converts, 1234.5e3 => cplx1234500f, 1 => cplx1, -1 => Complex(-1), Rational(3,2) => Complex(Rational(3,2),0)
182
+ it_behaves_like :it_converts, [1,0] => Complex(1), [1,2] => Complex(1,2)
183
+ it_behaves_like :it_converts, ['1234.5e3','98.6'] => Complex(1234.5e3, 98.6)
184
+ it_behaves_like :it_considers_blankish, nil, ''
185
+ it_behaves_like :it_is_a_mismatch_for, 'one', '3blindmice', '0x10', '1234.5e3f', '1234L', :foo, false, []
186
+ it_behaves_like :it_is_registered_as, :complex, Complex
187
+ its(:typename){ should == :complex }
188
+ end
189
+
190
+ describe Gorillib::Factory::RationalFactory do
191
+ rat_0 = Rational(0) ; rat1 = Rational(1) ; rat3_2 = Rational(3, 2) ;
192
+ it_behaves_like :it_considers_native, Rational(1, 3), Rational(1, 7)
193
+ it_behaves_like :it_converts, '1.5' => rat3_2, '1' => rat1, '0' => rat_0, '1_234_567' => Rational(1234567), '1_234_567.0' => Rational(1234567)
194
+ it_behaves_like :it_converts, 1.5 => rat3_2, 1 => rat1, -1 => Rational(-1), Complex(1.5) => rat3_2
195
+ it_behaves_like :it_considers_blankish, nil, ''
196
+ it_behaves_like :it_is_a_mismatch_for, 'one', '3blindmice', '0x10', '1234.5e3f', '1234L', :foo, false, [], Complex(1.5,3)
197
+ it_behaves_like :it_is_registered_as, :rational, Rational
198
+ its(:typename){ should == :rational }
199
+ end
200
+
201
+ describe Gorillib::Factory::TimeFactory do
202
+ fuck_wit_dre_day = Time.new(1993, 2, 18, 8, 8, 0, '-08:00') # and Everybody's Celebratin'
203
+ ice_cubes_good_day = Time.utc(1992, 1, 20, 0, 0, 0)
204
+ it_behaves_like :it_considers_native, Time.now.utc, ice_cubes_good_day
205
+ it_behaves_like :it_converts, fuck_wit_dre_day => fuck_wit_dre_day.getutc
206
+ it_behaves_like :it_converts, '19930218160800' => fuck_wit_dre_day.getutc, '19920120000000Z' => ice_cubes_good_day
207
+ it_behaves_like :it_converts, Date.new(1992, 1, 20) => ice_cubes_good_day
208
+ before('behaves like it_converts "an unparseable time" to nil'){ subject.stub(:warn) }
209
+ it_behaves_like :it_converts, "an unparseable time" => nil, :non_native_ok => true
210
+ it_behaves_like :it_considers_blankish, nil, ''
211
+ it_behaves_like :it_is_a_mismatch_for, :foo, false, []
212
+ it_behaves_like :it_is_registered_as, :time, Time
213
+ it('always returns a UTC timezoned time') do
214
+ subject.convert(fuck_wit_dre_day).utc_offset.should == 0
215
+ fuck_wit_dre_day.utc_offset.should == (-8 * 3600)
216
+ end
217
+ its(:typename){ should == :time }
218
+ end
219
+
220
+ describe Gorillib::Factory::BooleanFactory do
221
+ it_behaves_like :it_considers_native, true, false
222
+ it_behaves_like :it_considers_blankish, nil
223
+ it_behaves_like :it_converts, 'false' => false, :false => false
224
+ it_behaves_like :it_converts, 'true' => true, :true => true, '0' => true, 0 => true, [] => true, :foo => true, [] => true, Complex(1.5,3) => true, Object.new => true
225
+ it_behaves_like :it_is_registered_as, :boolean
226
+ its(:typename){ should == :boolean }
227
+ end
228
+
229
+ describe ::Whatever do
230
+ it_behaves_like :it_considers_native, true, false, nil, 3, '', 'a string', :a_symbol, [], {}, ->(){ 'a proc' }, Module.new, Complex(1,3), Object.new
231
+ it 'it is itself the factory for :identical and :whatever' do
232
+ keys = [Whatever, :identical, :whatever]
233
+ keys.each do |key|
234
+ Gorillib::Factory(key).should equal(described_class)
235
+ end
236
+ its_factory = ::Whatever
237
+ Gorillib::Factory.send(:factories).to_hash.select{|key,val| val.equal?(its_factory) }.keys.should == keys
238
+ end
239
+ end
240
+ describe Gorillib::Factory::IdenticalFactory do
241
+ it{ described_class.should equal(Whatever) }
242
+ end
243
+
244
+ describe Gorillib::Factory::ModuleFactory do
245
+ it_behaves_like :it_considers_blankish, nil
246
+ it_behaves_like :it_considers_native, Module, Module.new, Class, Class.new, Object, String, BasicObject
247
+ it_behaves_like :it_is_a_mismatch_for, true, false, 3, '', 'a string', :a_symbol, [], {}, ->(){ 'a proc' }, Complex(1,3), Object.new
248
+ it_behaves_like :it_is_registered_as, :module, Module
249
+ its(:typename){ should == :module }
250
+ end
251
+
252
+ describe Gorillib::Factory::ClassFactory do
253
+ it_behaves_like :it_considers_blankish, nil
254
+ it_behaves_like :it_considers_native, Module, Class, Class.new, Object, String, BasicObject
255
+ it_behaves_like :it_is_a_mismatch_for, true, false, 3, '', 'a string', :a_symbol, [], {}, ->(){ 'a proc' }, Module.new, Complex(1,3), Object.new
256
+ it_behaves_like :it_is_registered_as, :class, Class
257
+ its(:typename){ should == :class }
258
+ end
259
+
260
+ describe Gorillib::Factory::NilFactory do
261
+ it_behaves_like :it_considers_native, nil
262
+ it_behaves_like :it_is_a_mismatch_for, true, false, 3, '', 'a string', :a_symbol, [], {}, ->(){ 'a proc' }, Module.new, Complex(1,3), Object.new
263
+ it_behaves_like :it_is_registered_as, :nil, NilClass
264
+ its(:typename){ should == :nil_class }
265
+ end
266
+
267
+ describe Gorillib::Factory::TrueFactory do
268
+ it_behaves_like :it_considers_native, true
269
+ it_behaves_like :it_is_a_mismatch_for, false, 3, '', 'a string', :a_symbol, [], {}, ->(){ 'a proc' }, Module.new, Complex(1,3), Object.new
270
+ it_behaves_like :it_is_registered_as, :true, TrueClass
271
+ end
272
+
273
+ describe Gorillib::Factory::FalseFactory do
274
+ it_behaves_like :it_considers_native, false
275
+ it_behaves_like :it_is_a_mismatch_for, true, 3, '', 'a string', :a_symbol, [], {}, ->(){ 'a proc' }, Module.new, Complex(1,3), Object.new
276
+ it_behaves_like :it_is_registered_as, :false, FalseClass
277
+ end
278
+
279
+ describe Gorillib::Factory::RangeFactory do
280
+ it_behaves_like :it_considers_blankish, nil, []
281
+ it_behaves_like :it_considers_native, (1..2), ('a'..'z')
282
+ it_behaves_like :it_is_a_mismatch_for, true, 3, '', 'a string', :a_symbol, [1,2], {}, ->(){ 'a proc' }, Module.new, Complex(1,3), Object.new
283
+ it_behaves_like :it_is_registered_as, :range, Range
284
+ its(:typename){ should == :range }
285
+ end
286
+
287
+ # __________________________________________________________________________
288
+
289
+ describe Gorillib::Factory::HashFactory do
290
+ let(:collection_123){ { 'a' => 1, :b => 2, 'c' => 3 } }
291
+ let(:empty_collection){ {} }
292
+
293
+ it_behaves_like :it_considers_blankish, nil
294
+ it_behaves_like :it_converts, { {} => {}, { "a" => 2 } => { 'a' => 2 }, { :a => 2 } => { :a => 2 }, :non_native_ok => true }
295
+ it_behaves_like :it_is_a_mismatch_for, [1,2,3]
296
+ it_behaves_like :an_enumerable_factory
297
+
298
+ it 'follows examples' do
299
+ described_class.new.receive(collection_123).should == { 'a' => 1, :b => 2, 'c' => 3}
300
+ described_class.new(:keys => :symbol).receive({'a' => 'x', :b => 'y', 'c' => :z}).should == {:a => 'x', :b => 'y', :c => :z}
301
+ described_class.new(:items => :symbol).receive({'a' => 'x', :b => 'y', 'c' => :z}).should == {'a' => :x, :b => :y, 'c' => :z}
302
+ autov_factory = described_class.new(:empty_product => Hash.new{|h,k| h[k] = {} })
303
+ result = autov_factory.receive({:a => :b}) ; result.should == { :a => :b }
304
+ result[:flanger][:modacity] = 11 ; result.should == { :a => :b, :flanger => { :modacity => 11 }}
305
+ result2 = autov_factory.receive({:x => :y}) ; result2.should == { :x => :y } ; result.should == { :a => :b, :flanger => { :modacity => 11 }}
306
+ end
307
+
308
+ it "accepts a factory for the keys" do
309
+ mock_factory = mock('factory')
310
+ mock_factory.should_receive(:receive).with(3).and_return("converted!")
311
+ factory = described_class.new(:keys => mock_factory)
312
+ factory.receive( { 3 => 4 } ).should == { 'converted!' => 4 }
313
+ end
314
+ it_behaves_like :it_is_registered_as, :hash, Hash
315
+ its(:typename){ should == :hash }
316
+ end
317
+
318
+ describe Gorillib::Factory::ArrayFactory do
319
+ let(:collection_123){ [1,2,3] }
320
+ let(:empty_collection){ [] }
321
+
322
+ it 'follows examples' do
323
+ described_class.new.receive([1,2,3]).should == [1,2,3]
324
+ described_class.new(:items => :symbol).receive(['a', 'b', :c]).should == [:a, :b, :c]
325
+ described_class.new(:empty_product => [1,2,3]).receive([:a, :b, :c]).should == [1, 2, 3, :a, :b, :c]
326
+ end
327
+
328
+ it_behaves_like :it_considers_blankish, nil
329
+ it_behaves_like :it_converts, { [] => [], {} => [], [1,2,3] => [1,2,3], {:a => :b} => [[:a, :b]], [:a] => [:a], [[]] => [[]], :non_native_ok => true }
330
+ it_behaves_like :an_enumerable_factory
331
+ it_behaves_like :it_is_registered_as, :array, Array
332
+ its(:typename){ should == :array }
333
+ end
334
+
335
+ end