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.
- data/.gitignore +4 -0
- data/Gemfile +12 -0
- data/README.md +21 -0
- data/Rakefile +15 -0
- data/gorillib-model.gemspec +27 -0
- data/lib/gorillib/builder.rb +239 -0
- data/lib/gorillib/core_ext/datetime.rb +23 -0
- data/lib/gorillib/core_ext/exception.rb +153 -0
- data/lib/gorillib/core_ext/module.rb +10 -0
- data/lib/gorillib/core_ext/object.rb +14 -0
- data/lib/gorillib/model/base.rb +273 -0
- data/lib/gorillib/model/collection/model_collection.rb +157 -0
- data/lib/gorillib/model/collection.rb +200 -0
- data/lib/gorillib/model/defaults.rb +115 -0
- data/lib/gorillib/model/errors.rb +24 -0
- data/lib/gorillib/model/factories.rb +555 -0
- data/lib/gorillib/model/field.rb +168 -0
- data/lib/gorillib/model/lint.rb +24 -0
- data/lib/gorillib/model/named_schema.rb +53 -0
- data/lib/gorillib/model/positional_fields.rb +35 -0
- data/lib/gorillib/model/schema_magic.rb +163 -0
- data/lib/gorillib/model/serialization/csv.rb +60 -0
- data/lib/gorillib/model/serialization/json.rb +44 -0
- data/lib/gorillib/model/serialization/lines.rb +30 -0
- data/lib/gorillib/model/serialization/to_wire.rb +54 -0
- data/lib/gorillib/model/serialization/tsv.rb +53 -0
- data/lib/gorillib/model/serialization.rb +41 -0
- data/lib/gorillib/model/type/extended.rb +83 -0
- data/lib/gorillib/model/type/ip_address.rb +153 -0
- data/lib/gorillib/model/type/url.rb +11 -0
- data/lib/gorillib/model/validate.rb +22 -0
- data/lib/gorillib/model/version.rb +5 -0
- data/lib/gorillib/model.rb +34 -0
- data/spec/builder_spec.rb +193 -0
- data/spec/core_ext/datetime_spec.rb +41 -0
- data/spec/core_ext/exception.rb +98 -0
- data/spec/core_ext/object.rb +45 -0
- data/spec/model/collection_spec.rb +290 -0
- data/spec/model/defaults_spec.rb +104 -0
- data/spec/model/factories_spec.rb +323 -0
- data/spec/model/lint_spec.rb +28 -0
- data/spec/model/serialization/csv_spec.rb +30 -0
- data/spec/model/serialization/tsv_spec.rb +28 -0
- data/spec/model/serialization_spec.rb +41 -0
- data/spec/model/type/extended_spec.rb +166 -0
- data/spec/model/type/ip_address_spec.rb +141 -0
- data/spec/model_spec.rb +261 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/capture_output.rb +28 -0
- data/spec/support/nuke_constants.rb +9 -0
- data/spec/support/shared_context_for_builders.rb +59 -0
- data/spec/support/shared_context_for_models.rb +55 -0
- data/spec/support/shared_examples_for_factories.rb +71 -0
- data/spec/support/shared_examples_for_model_fields.rb +62 -0
- data/spec/support/shared_examples_for_models.rb +87 -0
- 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
|
data/spec/model_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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,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
|