gorillib-model 0.0.1

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 (56) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +12 -0
  3. data/README.md +21 -0
  4. data/Rakefile +15 -0
  5. data/gorillib-model.gemspec +27 -0
  6. data/lib/gorillib/builder.rb +239 -0
  7. data/lib/gorillib/core_ext/datetime.rb +23 -0
  8. data/lib/gorillib/core_ext/exception.rb +153 -0
  9. data/lib/gorillib/core_ext/module.rb +10 -0
  10. data/lib/gorillib/core_ext/object.rb +14 -0
  11. data/lib/gorillib/model/base.rb +273 -0
  12. data/lib/gorillib/model/collection/model_collection.rb +157 -0
  13. data/lib/gorillib/model/collection.rb +200 -0
  14. data/lib/gorillib/model/defaults.rb +115 -0
  15. data/lib/gorillib/model/errors.rb +24 -0
  16. data/lib/gorillib/model/factories.rb +555 -0
  17. data/lib/gorillib/model/field.rb +168 -0
  18. data/lib/gorillib/model/lint.rb +24 -0
  19. data/lib/gorillib/model/named_schema.rb +53 -0
  20. data/lib/gorillib/model/positional_fields.rb +35 -0
  21. data/lib/gorillib/model/schema_magic.rb +163 -0
  22. data/lib/gorillib/model/serialization/csv.rb +60 -0
  23. data/lib/gorillib/model/serialization/json.rb +44 -0
  24. data/lib/gorillib/model/serialization/lines.rb +30 -0
  25. data/lib/gorillib/model/serialization/to_wire.rb +54 -0
  26. data/lib/gorillib/model/serialization/tsv.rb +53 -0
  27. data/lib/gorillib/model/serialization.rb +41 -0
  28. data/lib/gorillib/model/type/extended.rb +83 -0
  29. data/lib/gorillib/model/type/ip_address.rb +153 -0
  30. data/lib/gorillib/model/type/url.rb +11 -0
  31. data/lib/gorillib/model/validate.rb +22 -0
  32. data/lib/gorillib/model/version.rb +5 -0
  33. data/lib/gorillib/model.rb +34 -0
  34. data/spec/builder_spec.rb +193 -0
  35. data/spec/core_ext/datetime_spec.rb +41 -0
  36. data/spec/core_ext/exception.rb +98 -0
  37. data/spec/core_ext/object.rb +45 -0
  38. data/spec/model/collection_spec.rb +290 -0
  39. data/spec/model/defaults_spec.rb +104 -0
  40. data/spec/model/factories_spec.rb +323 -0
  41. data/spec/model/lint_spec.rb +28 -0
  42. data/spec/model/serialization/csv_spec.rb +30 -0
  43. data/spec/model/serialization/tsv_spec.rb +28 -0
  44. data/spec/model/serialization_spec.rb +41 -0
  45. data/spec/model/type/extended_spec.rb +166 -0
  46. data/spec/model/type/ip_address_spec.rb +141 -0
  47. data/spec/model_spec.rb +261 -0
  48. data/spec/spec_helper.rb +15 -0
  49. data/spec/support/capture_output.rb +28 -0
  50. data/spec/support/nuke_constants.rb +9 -0
  51. data/spec/support/shared_context_for_builders.rb +59 -0
  52. data/spec/support/shared_context_for_models.rb +55 -0
  53. data/spec/support/shared_examples_for_factories.rb +71 -0
  54. data/spec/support/shared_examples_for_model_fields.rb +62 -0
  55. data/spec/support/shared_examples_for_models.rb +87 -0
  56. metadata +193 -0
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ require 'gorillib/model/type/extended'
4
+
5
+ shared_examples_for(:ip_addresslike) do
6
+
7
+ its(:dotted){ should == '1.2.3.4' }
8
+ its(:quads){ should == [1, 2, 3, 4] }
9
+ its(:packed){ should == packed_1234 }
10
+
11
+ context '.from_packed' do
12
+ subject{ described_class.from_packed(packed_1234) }
13
+ it{ should be_a(described_class) }
14
+ its(:dotted){ should == '1.2.3.4' }
15
+ end
16
+
17
+ context '#bitness_min' do
18
+ it('is unchanged for /32'){ subject.bitness_min(32).should == subject.packed }
19
+ { 0 => 0, # '0.0.0.0'
20
+ 6 => 0, # '0.0.0.0'
21
+ 8 => 0x01000000, # '1.0.0.0',
22
+ 30 => 0x01020304, # '1.2.3.4',
23
+ }.each do |bitness, result|
24
+ it("returns #{result} for #{bitness}"){ subject.bitness_min(bitness).should == result }
25
+ end
26
+ it 'raises an error if bitness is out of range' do
27
+ expect{ subject.bitness_min(33) }.to raise_error(ArgumentError, /only 32 bits.*33/)
28
+ expect{ subject.bitness_min(-1) }.to raise_error(ArgumentError, /only 32 bits.*-1/)
29
+ expect{ subject.bitness_min('bob') }.to raise_error(ArgumentError, /only 32 bits.*bob/)
30
+ end
31
+ end
32
+
33
+ context '#bitness_max' do
34
+ it 'returns an integer' do
35
+ subject.bitness_max(24).should equal(16909311)
36
+ end
37
+ it('is unchanged for /32' ){ subject.bitness_max(32).should == subject.packed }
38
+ { 0 => 0xFFFFFFFF, # '255.255.255.255'
39
+ 6 => 0x03FFFFFF, # '3.255.255.255'
40
+ 8 => 0x01FFFFFF, # '1.255.255.255'
41
+ 31 => 0x01020305, # '1.2.3.5'
42
+ }.each do |bitness, result|
43
+ it("returns #{result} for #{bitness}"){ subject.bitness_max(bitness).should == result }
44
+ end
45
+ it 'raises an error if bitness is out of range' do
46
+ expect{ subject.bitness_max(33) }.to raise_error(ArgumentError, /only 32 bits.*33/)
47
+ expect{ subject.bitness_max(-1) }.to raise_error(ArgumentError, /only 32 bits.*-1/)
48
+ expect{ subject.bitness_max('bob') }.to raise_error(ArgumentError, /only 32 bits.*bob/)
49
+ end
50
+ end
51
+ end
52
+
53
+ describe ::IpAddress do
54
+ let(:packed_1234){ 16909060 }
55
+ let(:dotted_1234){ '1.2.3.4' }
56
+ subject{ described_class.new('1.2.3.4') }
57
+
58
+ it_should_behave_like(:ip_addresslike)
59
+
60
+ its(:to_i){ should equal(packed_1234) }
61
+ it{ expect{ subject.to_int }.to raise_error(NoMethodError, /undefined method.*\`to_int\'/) }
62
+ end
63
+
64
+ describe ::IpNumeric do
65
+ let(:packed_1234){ 16909060 }
66
+ let(:dotted_1234){ '1.2.3.4' }
67
+ subject{ described_class.new(packed_1234) }
68
+
69
+ it_should_behave_like(:ip_addresslike)
70
+
71
+ context '.from_dotted' do
72
+ subject{ described_class.from_dotted(dotted_1234) }
73
+ it('memoizes #dotted') do
74
+ val = subject.instance_variable_get('@dotted')
75
+ val.should == '1.2.3.4'
76
+ val.should be_frozen
77
+ end
78
+ it('memoizes #quads') do
79
+ val = subject.instance_variable_get('@quads')
80
+ val.should == [1,2,3,4]
81
+ val.should be_frozen
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+
88
+ describe ::IpRange do
89
+ let(:beg_1234){ 16909060 }
90
+ let(:end_1234){ 16909384 }
91
+ subject{ described_class.new(beg_1234 .. end_1234) }
92
+
93
+ its(:min){ should == beg_1234 }
94
+ its(:max){ should == end_1234 }
95
+ its(:min){ should be_a(IpNumeric) }
96
+ its(:max){ should be_a(IpNumeric) }
97
+
98
+ context 'bitness_blocks' do
99
+ it 'emits nothing if empty' do
100
+ described_class.new(50, 49).bitness_blocks(24).should == []
101
+ end
102
+ it 'emits only one block if min and max are in same bitness block' do
103
+ described_class.new(40, 80).bitness_blocks(24).should == [ [40, 80] ]
104
+ end
105
+ it 'emits only one block if min and max are the same' do
106
+ described_class.new(40, 40).bitness_blocks(24).should == [ [40, 40] ]
107
+ end
108
+ it 'emits two short blocks if min and max are in neighboring blocks' do
109
+ described_class.new(40, 260).bitness_blocks(24).should == [ [40, 255], [256, 260] ]
110
+ end
111
+ it 'emits intermediate blocks if min and max are far apart' do
112
+ described_class.new(40, 520).bitness_blocks(24).should == [ [40, 255], [256, 511], [512, 520] ]
113
+ end
114
+
115
+ {
116
+ [ '255.255.255.255', '255.255.255.255', 0 ] => [ [0xFFFFFFFF, 0xFFFFFFFF] ],
117
+ [ '255.255.255.255', '255.255.255.255', 24 ] => [ [0xFFFFFFFF, 0xFFFFFFFF] ],
118
+ [ '255.255.255.255', '255.255.255.255', 32 ] => [ [0xFFFFFFFF, 0xFFFFFFFF] ],
119
+ [ '255.255.254.1', '255.255.255.255', 0 ] => [ [0xFFFFFe01, 0xFFFFFFFF] ],
120
+ [ '255.255.254.1', '255.255.255.255', 24 ] => [ [0xFFFFFe01, 0xFFFFFeFF], [0xFFFFFF00, 0xFFFFFFFF] ],
121
+ [ '255.255.253.1', '255.255.255.7', 24 ] => [ [0xFFFFFd01, 0xFFFFFdFF], [0xFFFFFe00, 0xFFFFFeFF], [0xFFFFFF00, 0xFFFFFF07] ],
122
+ [ '0.0.0.0', '0.0.0.0', 24 ] => [ [0x0, 0x0], ],
123
+ [ '0.0.0.255', '0.0.1.0', 24 ] => [ [0xFF, 0xFF], [0x100, 0x100] ],
124
+ [ '0.0.0.7', '0.0.0.10', 31 ] => [ [0x7, 0x7], [0x8, 0x9], [0xa, 0xa], ],
125
+ [ '0.0.0.7', '0.0.0.11', 31 ] => [ [0x7, 0x7], [0x8, 0x9], [0xa, 0xb], ],
126
+ [ '0.0.0.0', '0.0.0.0', 24 ] => [ [0x0, 0x0], ],
127
+ [ '0.0.1.7', '0.0.1.255', 24 ] => [ [0x107, 0x1FF], ],
128
+ [ '0.0.1.7', '0.0.2.7', 24 ] => [ [0x107, 0x1FF], [0x200, 0x207] ],
129
+ [ '0.0.1.7', '0.0.3.7', 24 ] => [ [0x107, 0x1FF], [0x200, 0x2FF], [0x300, 0x307] ],
130
+ }.each do |(beg_ip, end_ip, bitness), blocks|
131
+ it "/#{bitness}-bit blocks of #{beg_ip}..#{end_ip} are #{blocks}" do
132
+ beg_ip = IpNumeric.from_dotted(beg_ip)
133
+ end_ip = IpNumeric.from_dotted(end_ip)
134
+ subject = IpRange.new(beg_ip .. end_ip)
135
+ subject.bitness_blocks(bitness).should == blocks
136
+ end
137
+ end
138
+
139
+ end
140
+
141
+ end
@@ -0,0 +1,261 @@
1
+ require 'spec_helper'
2
+
3
+ describe Gorillib::Model, :model_spec => true do
4
+ let(:simple_model) do
5
+ class Gorillib::Test::SimpleModel
6
+ include Gorillib::Model
7
+ field :my_field, :whatever
8
+ field :str_field, String
9
+ field :sym_field, Symbol
10
+ self
11
+ end
12
+ end
13
+ let(:subclassed_model) do
14
+ class Gorillib::Test::SubclassedModel < simple_model ; field :zyzzyva, Integer; field :acme, Integer ; end
15
+ Gorillib::Test::SubclassedModel
16
+ end
17
+ let(:nested_model) do
18
+ smurf_class = self.smurf_class
19
+ Gorillib::Test::NestedModel = Class.new(simple_model){ field :smurf, smurf_class }
20
+ Gorillib::Test::NestedModel
21
+ end
22
+
23
+ let(:described_class){ simple_model }
24
+ let(:example_inst){ described_class.receive(:my_field => 69) }
25
+
26
+ #
27
+ # IT BEHAVES LIKE A MODEL
28
+ # (maybe you wouldn't notice if it was just one little line)
29
+ #
30
+ it_behaves_like 'a model'
31
+
32
+ # --------------------------------------------------------------------------
33
+
34
+ context 'examples' do
35
+ it 'type-converts values' do
36
+ obj = simple_model.receive({
37
+ :my_field => 'accepted as-is', :str_field => :bob, :sym_field => 'converted_to_sym'
38
+ })
39
+ obj.attributes.should == { :my_field => 'accepted as-is', :str_field => 'bob', :sym_field=>:converted_to_sym }
40
+ end
41
+ it 'handles nested structures' do
42
+ deep_obj = nested_model.receive(:str_field => 'deep, man', :smurf => papa_smurf.attributes)
43
+ deep_obj.attributes.should == { :str_field => 'deep, man', :smurf => papa_smurf, :sym_field=>nil, :my_field => nil, }
44
+ end
45
+ end
46
+
47
+ context ".field" do
48
+ it "describes an attribute" do
49
+ example_inst.compact_attributes.should == { :my_field => 69 }
50
+ example_inst.write_attribute(:my_field, 3).should == 3
51
+ example_inst.compact_attributes.should == { :my_field => 3 }
52
+ example_inst.read_attribute(:my_field).should == 3
53
+ end
54
+ it 'inherits fields from its parent class, even if they are added later' do
55
+ simple_model.field_names.should == [:my_field, :str_field, :sym_field]
56
+ subclassed_model.field_names.should == [:my_field, :str_field, :sym_field, :zyzzyva, :acme]
57
+ simple_model.field :banksy, String
58
+ simple_model.field_names.should == [:my_field, :str_field, :sym_field, :banksy ]
59
+ subclassed_model.field_names.should == [:my_field, :str_field, :sym_field, :banksy, :zyzzyva, :acme]
60
+ end
61
+
62
+ it "supplies a reader method #foo to call read_attribute(:foo)" do
63
+ example_inst.should_receive(:read_attribute).with(:my_field).and_return(mock_val)
64
+ example_inst.my_field.should == mock_val
65
+ end
66
+ it "supplies a writer method #foo= to call write_attribute(:foo)" do
67
+ example_inst.should_receive(:write_attribute).with(:my_field, mock_val)
68
+ (example_inst.my_field = mock_val).should == mock_val
69
+ end
70
+ it "supplies #receive_foo, which does write_attribute(:foo) and returns the new value " do
71
+ example_inst.should_receive(:write_attribute).with(:my_field, mock_val).and_return('okey doke!')
72
+ (example_inst.receive_my_field(mock_val)).should == 'okey doke!'
73
+ end
74
+ it "sets visibility of reader with :reader => ()" do
75
+ described_class.field :test_field, Integer, :reader => :private, :writer => false
76
+ described_class.public_instance_methods.should_not include(:test_field)
77
+ described_class.private_instance_methods.should include(:test_field)
78
+ described_class.public_instance_methods.should_not include(:test_field=)
79
+ described_class.private_instance_methods.should_not include(:test_field=)
80
+ end
81
+ end
82
+
83
+ context '.field' do
84
+ subject{ described_class.new }
85
+ let(:sample_val){ 'bob' }
86
+ let(:raw_val){ :bob }
87
+ it_behaves_like 'a model field', :str_field
88
+ end
89
+
90
+ context '#attributes' do
91
+ it "maps field names to attribute values" do
92
+ example_inst = simple_model.receive({:my_field=>7, :str_field=>'yo', :sym_field=>:sup})
93
+ example_inst.attributes.should == {:my_field=>7, :str_field=>'yo', :sym_field=>:sup}
94
+ end
95
+ it "includes all field names, set and unset" do
96
+ example_inst.attributes.should == {:my_field=>69, :str_field=>nil, :sym_field=>nil}
97
+ example_inst.receive!(:my_field=>7, :str_field=>'yo')
98
+ example_inst.attributes.should == {:my_field=>7, :str_field=>'yo', :sym_field=>nil}
99
+ end
100
+ it "goes throught the #read_attribute interface" do
101
+ example_inst.should_receive(:read_attribute).with(:my_field).and_return('int')
102
+ example_inst.should_receive(:read_attribute).with(:str_field).and_return('str')
103
+ example_inst.should_receive(:read_attribute).with(:sym_field).and_return('sym')
104
+ example_inst.attributes.should == {:my_field=>'int', :str_field=>'str', :sym_field=>'sym'}
105
+ end
106
+ it "is an empty hash if there are no fields" do
107
+ model_with_no_fields = Class.new{ include Gorillib::Model }
108
+ model_with_no_fields.new.attributes.should == {}
109
+ end
110
+ end
111
+
112
+ context '#unset_attribute' do
113
+ it "unsets the attribute" do
114
+ example_inst.attribute_set?(:my_field).should be_true
115
+ example_inst.unset_attribute(:my_field)
116
+ example_inst.attribute_set?(:my_field).should be_false
117
+ end
118
+ it "if set, returns the former value" do
119
+ example_inst.unset_attribute(:my_field ).should == 69
120
+ example_inst.unset_attribute(:str_field).should == nil
121
+ end
122
+ end
123
+
124
+ context '#update_attributes' do
125
+ it "consumes a map from field names to new values" do
126
+ example_inst.attributes.should == {:my_field=>69, :str_field=>nil, :sym_field=>nil}
127
+ example_inst.update_attributes({:my_field=>7, :str_field=>'yo'})
128
+ example_inst.attributes.should == {:my_field=>7, :str_field=>'yo', :sym_field=>nil}
129
+ example_inst.update_attributes({:str_field=>'ok', :sym_field => :bye})
130
+ example_inst.attributes.should == {:my_field=>7, :str_field=>'ok', :sym_field=>:bye}
131
+ end
132
+ it "takes string or symbol keys" do
133
+ example_inst.update_attributes 'my_field'=>7, :str_field=>'yo'
134
+ example_inst.attributes.should == {:my_field=>7, :str_field=>'yo', :sym_field=>nil}
135
+ end
136
+ it "goes throught the #write_attribute interface" do
137
+ example_inst.should_receive(:write_attribute).with(:my_field, 7)
138
+ example_inst.should_receive(:write_attribute).with(:str_field, 'yo')
139
+ example_inst.update_attributes 'my_field'=>7, :str_field=>'yo'
140
+ end
141
+ end
142
+
143
+ context '#receive!' do
144
+ it "consumes a map from field names to new values" do
145
+ example_inst.attributes.should == {:my_field=>69, :str_field=>nil, :sym_field=>nil}
146
+ example_inst.receive!({:my_field=>7, :str_field=>'yo'})
147
+ example_inst.attributes.should == {:my_field=>7, :str_field=>'yo', :sym_field=>nil}
148
+ example_inst.receive!({:str_field=>'ok', :sym_field => :bye})
149
+ example_inst.attributes.should == {:my_field=>7, :str_field=>'ok', :sym_field=>:bye}
150
+ end
151
+ it "takes string or symbol keys" do
152
+ example_inst.receive! 'my_field'=>7, :str_field=>'yo'
153
+ example_inst.attributes.should == {:my_field=>7, :str_field=>'yo', :sym_field=>nil}
154
+ end
155
+ it "goes throught the #write_attribute interface" do
156
+ example_inst.should_receive(:write_attribute).with(:my_field, 7)
157
+ example_inst.should_receive(:write_attribute).with(:str_field, 'yo')
158
+ example_inst.receive! 'my_field'=>7, :str_field=>'yo'
159
+ end
160
+ it "returns nil with no block given" do
161
+ example_inst.receive!('my_field'=>7, :str_field=>'yo').should be_nil
162
+ end
163
+ end
164
+
165
+ context '#== -- two models are equal if' do
166
+ let(:subklass){ Class.new(described_class) }
167
+ let(:obj_2){ described_class.receive(:my_field => 69) }
168
+ let(:obj_3){ subklass.receive(:my_field => 69) }
169
+
170
+ it 'they have the same class' do
171
+ example_inst.attributes.should == obj_2.attributes
172
+ example_inst.attributes.should == obj_3.attributes
173
+ example_inst.should == obj_2
174
+ example_inst.should_not == obj_3
175
+ end
176
+ it 'and the same attributes' do
177
+ example_inst.attributes.should == obj_2.attributes
178
+ example_inst.should == obj_2
179
+ obj_2.my_field = 100
180
+ example_inst.should_not == obj_2
181
+ end
182
+ end
183
+
184
+ context ".fields" do
185
+ it 'is a hash of Gorillib::Model::Field objects' do
186
+ described_class.fields.keys.should == [:my_field, :str_field, :sym_field]
187
+ described_class.fields.values.each{|f| f.should be_a(Gorillib::Model::Field) }
188
+ described_class.fields.values.map(&:name).should == [:my_field, :str_field, :sym_field]
189
+ end
190
+ end
191
+
192
+ context '.has_field?' do
193
+ it 'is true if the field exists' do
194
+ simple_model.has_field?( :my_field).should be_true
195
+ subclassed_model.has_field?(:my_field).should be_true
196
+ subclassed_model.has_field?(:zyzzyva ).should be_true
197
+ end
198
+ it 'is false if it does not exist' do
199
+ simple_model.has_field?( :zyzzyva).should be_false
200
+ simple_model.has_field?( :fnord ).should be_false
201
+ subclassed_model.has_field?(:fnord ).should be_false
202
+ end
203
+ end
204
+
205
+ context '.field_names' do
206
+ it 'lists fields in order by class, then in order added' do
207
+ described_class.field_names.should == [:my_field, :str_field, :sym_field]
208
+ subclassed_model.field_names.should == [:my_field, :str_field, :sym_field, :zyzzyva, :acme]
209
+ described_class.field :banksy, String
210
+ described_class.field_names.should == [:my_field, :str_field, :sym_field, :banksy ]
211
+ subclassed_model.field_names.should == [:my_field, :str_field, :sym_field, :banksy, :zyzzyva, :acme]
212
+ end
213
+ end
214
+
215
+ context '.typename' do
216
+ it 'has a typename that matches its underscored class name' do
217
+ described_class.typename.should == 'gorillib.test.simple_model'
218
+ end
219
+ end
220
+
221
+ context '.inspect' do
222
+ it('is pretty'){ smurf_class.inspect.should == 'Gorillib::Test::Smurf[name,smurfiness,weapon]' }
223
+ it('is pretty even if class is anonymous'){ Class.new(smurf_class).inspect.should == 'anon[name,smurfiness,weapon]' }
224
+ end
225
+ context '.inspect_compact' do
226
+ it('is just the class name'){ smurf_class.inspect_compact.should == "Gorillib::Test::Smurf" }
227
+ it('is detailed if class is anonymous'){ Class.new(smurf_class).inspect_compact.should == "anon[name,smurfiness,weapon]" }
228
+ end
229
+
230
+ describe Gorillib::Model::NamedSchema do
231
+ context ".meta_module" do
232
+ let(:basic_field_names){ [ :my_field, :my_field=, :receive_my_field, :receive_str_field, :receive_sym_field, :str_field, :str_field=, :sym_field, :sym_field= ]}
233
+ let(:anon_class){ Class.new{ include Gorillib::Model ; field :my_field, :whatever } }
234
+
235
+ it "is named for the class (if the class is named)" do
236
+ described_class.send(:meta_module).should == Meta::Gorillib::Test::SimpleModelType
237
+ end
238
+ it "is anonymous if the class is anonymous" do
239
+ anon_class.name.should be_nil
240
+ anon_class.send(:meta_module).should be_a(Module)
241
+ anon_class.send(:meta_module).name.should be_nil
242
+ end
243
+ it "carries the field-specfic accessor and receive methods" do
244
+ described_class.send(:meta_module).public_instance_methods.sort.should == basic_field_names
245
+ anon_class.send(:meta_module).public_instance_methods.sort.should == [:my_field, :my_field=, :receive_my_field]
246
+ end
247
+ it "is injected right after the Gorillib::Model module" do
248
+ described_class.ancestors.first(4).should == [described_class, Meta::Gorillib::Test::SimpleModelType, Gorillib::Model, Object]
249
+ described_class.should < Meta::Gorillib::Test::SimpleModelType
250
+ end
251
+ it "retrieves an existing named module if one exists" do
252
+ Gorillib::Test.should_not be_const_defined(:TestClass)
253
+ module Meta::Gorillib::Test::SimpleModelType ; def kilroy_was_here() '23 skidoo' ; end ; end
254
+ described_class.send(:meta_module).public_instance_methods.sort.should == (basic_field_names + [:kilroy_was_here]).sort
255
+ Gorillib::Test.should be_const_defined(:SimpleModel)
256
+ described_class.send(:meta_module).should == Meta::Gorillib::Test::SimpleModelType
257
+ end
258
+ end
259
+ end
260
+
261
+ end
@@ -0,0 +1,15 @@
1
+ if ENV['GORILLIB_MODEL_COV']
2
+ require 'simplecov'
3
+ SimpleCov.start do
4
+ add_group 'Specs', 'spec/'
5
+ add_group 'Library', 'lib/'
6
+ end
7
+ end
8
+
9
+ require 'gorillib/model'
10
+
11
+ Dir[File.expand_path('../support/**/*.rb', __FILE__)].each{ |f| require f}
12
+
13
+ RSpec.configure do |config|
14
+ include Gorillib::TestHelpers
15
+ end
@@ -0,0 +1,28 @@
1
+ module Gorillib
2
+ module TestHelpers
3
+ module_function
4
+
5
+ def dummy_stdio(stdin_text=nil)
6
+ stdin = stdin_text.nil? ? $stdin : StringIO.new(stdin_text)
7
+ new_fhs = [stdin, StringIO.new('', 'w'), StringIO.new('', 'w')]
8
+ old_fhs = [$stdin, $stdout, $stderr]
9
+ begin
10
+ $stdin, $stdout, $stderr = new_fhs
11
+ yield
12
+ ensure
13
+ $stdin, $stdout, $stderr = old_fhs
14
+ end
15
+ new_fhs[1..2]
16
+ end
17
+
18
+ #
19
+ # Temporarily sets the global variables $stderr and $stdout to a capturable StringIO;
20
+ # restores them at the end, even if there is an error
21
+ #
22
+ def capture_output
23
+ dummy_stdio{ yield }
24
+ end
25
+
26
+ alias_method :quiet_output, :capture_output
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ class Module
2
+ #
3
+ # Removes all constants in the module's namespace -- this is useful when
4
+ # writing specs for metaprogramming methods
5
+ #
6
+ def nuke_constants
7
+ constants.each{|const| remove_const(const) }
8
+ end
9
+ end
@@ -0,0 +1,59 @@
1
+ shared_context 'builder', :model_spec => true, :builder_spec => true do
2
+
3
+ module Gorillib ; module Test ; end ; end
4
+ module Meta ; module Gorillib ; module Test ; end ; end ; end
5
+
6
+ let(:engine_class) do
7
+ class Gorillib::Test::Engine
8
+ include Gorillib::Builder
9
+ magic :name, Symbol, :default => ->{ "#{owner? ? owner.name : ''} engine"}
10
+ magic :carburetor, Symbol, :default => :stock
11
+ magic :volume, Integer, :doc => 'displacement volume, in in^3'
12
+ magic :cylinders, Integer
13
+ member :owner, Whatever
14
+ self
15
+ end
16
+ Gorillib::Test::Engine
17
+ end
18
+
19
+ let(:car_class) do
20
+ engine_class
21
+ class Gorillib::Test::Car
22
+ include Gorillib::Builder
23
+ magic :name, Symbol
24
+ magic :make_model, String
25
+ magic :year, Integer
26
+ magic :doors, Integer
27
+ member :engine, Gorillib::Test::Engine
28
+ self
29
+ end
30
+ Gorillib::Test::Car
31
+ end
32
+
33
+ let(:garage_class) do
34
+ car_class
35
+ class Gorillib::Test::Garage
36
+ include Gorillib::Builder
37
+ collection :cars, Gorillib::Test::Car, key_method: :name
38
+ self
39
+ end
40
+ Gorillib::Test::Garage
41
+ end
42
+
43
+ let(:wildcat) do
44
+ car_class.receive( :name => :wildcat,
45
+ :make_model => 'Buick Wildcat', :year => 1968, :doors => 2,
46
+ :engine => { :volume => 455, :cylinders => 8 } )
47
+ end
48
+ let(:ford_39) do
49
+ car_class.receive( :name => :ford_39,
50
+ :make_model => 'Ford Tudor Sedan', :year => 1939, :doors => 2, )
51
+ end
52
+ let(:garage) do
53
+ garage_class.new
54
+ end
55
+ let(:example_engine) do
56
+ engine_class.new( :name => 'Geo Metro 1.0L', :volume => 61, :cylinders => 3 )
57
+ end
58
+
59
+ end
@@ -0,0 +1,55 @@
1
+ shared_context 'model', :model_spec => true do
2
+
3
+ module Gorillib ; module Test ; end ; end
4
+ module Meta ; module Gorillib ; module Test ; end ; end ; end
5
+
6
+ after(:each){ Gorillib::Test.nuke_constants ; Meta::Gorillib::Test.nuke_constants }
7
+
8
+ let(:mock_val){ double('mock value') }
9
+
10
+ let(:smurf_class) do
11
+ class Gorillib::Test::Smurf
12
+ include Gorillib::Model
13
+ field :name, String
14
+ field :smurfiness, Integer
15
+ field :weapon, Symbol
16
+ end
17
+ Gorillib::Test::Smurf
18
+ end
19
+ let(:papa_smurf ){ smurf_class.receive(:name => 'Papa Smurf', :smurfiness => 9, :weapon => 'staff') }
20
+ let(:smurfette ){ smurf_class.receive(:name => 'Smurfette', :smurfiness => 11, :weapon => 'charm') }
21
+
22
+ let(:smurf_collection_class) do
23
+ smurf_class
24
+ class Gorillib::Test::SmurfCollection < Gorillib::ModelCollection
25
+ include Gorillib::Collection::ItemsBelongTo
26
+ self.item_type = Gorillib::Test::Smurf
27
+ self.parentage_method = :village
28
+ end
29
+ Gorillib::Test::SmurfCollection
30
+ end
31
+
32
+ let(:smurf_village_class) do
33
+ smurf_class ; smurf_collection_class
34
+ module Gorillib::Test
35
+ class SmurfVillage
36
+ include Gorillib::Model
37
+ field :name, Symbol
38
+ collection :smurfs, SmurfCollection, item_type: Smurf, key_method: :name
39
+ end
40
+ end
41
+ Gorillib::Test::SmurfVillage
42
+ end
43
+
44
+ let(:smurfhouse_class) do
45
+ module Gorillib::Test
46
+ class Smurfhouse
47
+ include Gorillib::Model
48
+ field :shape, Symbol
49
+ field :color, Symbol
50
+ end
51
+ end
52
+ Gorillib::Test::Smurfhouse
53
+ end
54
+
55
+ end
@@ -0,0 +1,71 @@
1
+ shared_examples_for :it_converts do |conversion_mapping|
2
+ non_native_ok = conversion_mapping.delete(:non_native_ok)
3
+ conversion_mapping.each do |obj, expected_result|
4
+ it "#{obj.inspect} to #{expected_result.inspect}" do
5
+ actual_result = subject.receive(obj)
6
+ actual_result.should eql(expected_result)
7
+ subject.native?( obj).should be_false
8
+ subject.blankish?(obj).should be_false
9
+ unless non_native_ok then subject.native?(actual_result).should be_true ; end
10
+ end
11
+ end
12
+ end
13
+
14
+ shared_examples_for :it_considers_native do |*native_objs|
15
+ it native_objs.inspect do
16
+ native_objs.each do |obj|
17
+ subject.native?( obj).should be_true
18
+ actual_result = subject.receive(obj)
19
+ actual_result.should equal(obj)
20
+ end
21
+ end
22
+ end
23
+
24
+ shared_examples_for :it_considers_blankish do |*blankish_objs|
25
+ it blankish_objs.inspect do
26
+ blankish_objs.each do |obj|
27
+ subject.blankish?(obj).should be_true
28
+ subject.receive(obj).should be_nil
29
+ end
30
+ end
31
+ end
32
+
33
+ shared_examples_for :it_is_a_mismatch_for do |*mismatched_objs|
34
+ it mismatched_objs.inspect do
35
+ mismatched_objs.each do |obj|
36
+ ->{ subject.receive(obj) }.should raise_error(Gorillib::Factory::FactoryMismatchError)
37
+ end
38
+ end
39
+ end
40
+
41
+ shared_examples_for :it_is_registered_as do |*keys|
42
+ it "the factory for #{keys}" do
43
+ keys.each do |key|
44
+ Gorillib::Factory(key).should be_a(described_class)
45
+ end
46
+ its_factory = Gorillib::Factory(keys.first)
47
+ Gorillib::Factory.send(:factories).to_hash.select{|key,val| val.equal?(its_factory) }.keys.should == keys
48
+ end
49
+ end
50
+
51
+ # hand it a collection with entries 1, 2, 3 please
52
+ shared_examples_for :an_enumerable_factory do
53
+ it "accepts a factory for its items" do
54
+ mock_factory = double('factory')
55
+ mock_factory.should_receive(:receive).with(1)
56
+ mock_factory.should_receive(:receive).with(2)
57
+ mock_factory.should_receive(:receive).with(3)
58
+ factory = described_class.new(:items => mock_factory)
59
+ factory.receive( collection_123 )
60
+ end
61
+ it "can generate an empty collection" do
62
+ subject.empty_product.should == empty_collection
63
+ end
64
+ it "lets you override the empty collection" do
65
+ ep = double; ep.should_receive(:try_dup).and_return 'hey'
66
+ subject = described_class.new(:empty_product => ep)
67
+ subject.empty_product.should == 'hey'
68
+ subject = described_class.new(:empty_product => ->{ 'yo' })
69
+ subject.empty_product.should == 'yo'
70
+ end
71
+ end