gorillib 0.4.1pre → 0.4.2pre

Sign up to get free protection for your applications and to get access to all the features.
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