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,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'try_dup', :simple_spec => true do
|
4
|
+
it "returns a duplicate version on regular objects" do
|
5
|
+
obj = Object.new
|
6
|
+
oth = obj.try_dup
|
7
|
+
obj.should_not === oth
|
8
|
+
end
|
9
|
+
|
10
|
+
it "returns self on Numerics" do
|
11
|
+
obj = 12
|
12
|
+
oth = obj.try_dup
|
13
|
+
obj.should === oth
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns self on Symbols" do
|
17
|
+
obj = :test
|
18
|
+
oth = obj.try_dup
|
19
|
+
obj.should === oth
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns self on true" do
|
23
|
+
obj = true
|
24
|
+
oth = obj.try_dup
|
25
|
+
obj.should === oth
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns self on false" do
|
29
|
+
obj = false
|
30
|
+
oth = obj.try_dup
|
31
|
+
obj.should === oth
|
32
|
+
end
|
33
|
+
|
34
|
+
it "returns self on nil" do
|
35
|
+
obj = nil
|
36
|
+
oth = obj.try_dup
|
37
|
+
obj.should === oth
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns self on modules" do
|
41
|
+
obj = Module.new
|
42
|
+
oth = obj.try_dup
|
43
|
+
obj.object_id.should == oth.object_id
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,290 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'gorillib/model/collection/model_collection'
|
4
|
+
|
5
|
+
shared_examples_for 'a collection' do |options={}|
|
6
|
+
subject{ string_collection }
|
7
|
+
|
8
|
+
context '.receive' do
|
9
|
+
it 'makes a new collection, has it #receive! the cargo, returns it' do
|
10
|
+
mock_collection = double('collection')
|
11
|
+
mock_cargo = double('cargo')
|
12
|
+
mock_args = {key_method: double, item_type: double}
|
13
|
+
described_class.should_receive(:new).with(mock_args).and_return(mock_collection)
|
14
|
+
mock_collection.should_receive(:receive!).with(mock_cargo)
|
15
|
+
described_class.receive(mock_cargo, mock_args).should == mock_collection
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'empty collection' do
|
20
|
+
subject{ described_class.new }
|
21
|
+
its(:length){ should == 0 }
|
22
|
+
its(:size ){ should == 0 }
|
23
|
+
its(:empty?){ should be true }
|
24
|
+
its(:blank?){ should be true }
|
25
|
+
its(:values){ should == [] }
|
26
|
+
its(:to_a ){ should == [] }
|
27
|
+
end
|
28
|
+
context 'non-empty collection' do
|
29
|
+
subject{ string_collection }
|
30
|
+
its(:length){ should == 4 }
|
31
|
+
its(:size ){ should == 4 }
|
32
|
+
its(:empty?){ should be false }
|
33
|
+
its(:blank?){ should be false }
|
34
|
+
end
|
35
|
+
|
36
|
+
context '#values returns an array' do
|
37
|
+
its(:values){ should == %w[wocket in my pocket] }
|
38
|
+
end
|
39
|
+
context '#to_a returns same as values' do
|
40
|
+
its(:to_a ){ should == %w[wocket in my pocket] }
|
41
|
+
end
|
42
|
+
|
43
|
+
context '#each_value' do
|
44
|
+
it 'each value in order' do
|
45
|
+
result = []
|
46
|
+
ret = subject.each_value{|val| result << val.reverse }
|
47
|
+
result.should == %w[tekcow ni ym tekcop]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
unless (options[:receiving_arrays] == :skip)
|
52
|
+
context '#receive (array)', :if => :receives_arrays do
|
53
|
+
it 'adopts the contents of an array' do
|
54
|
+
string_collection.receive!(%w[horton hears a who])
|
55
|
+
string_collection.values.should == %w[wocket in my pocket horton hears a who]
|
56
|
+
end
|
57
|
+
it 'does not adopt duplicates' do
|
58
|
+
string_collection.receive!(%w[red fish blue fish])
|
59
|
+
string_collection.values.should == %w[wocket in my pocket red fish blue]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context '#receive (hash)' do
|
65
|
+
it 'adopts the values of a hash' do
|
66
|
+
string_collection.receive!({horton: "horton", hears: "hears", a: "a", who: "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!({red: 'red', fish: 'fish'})
|
71
|
+
string_collection.receive!({blue: 'blue', fish: 'fish'})
|
72
|
+
string_collection.values.should == %w[wocket in my pocket red fish blue]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
shared_examples_for 'a keyed collection' do
|
79
|
+
subject{ string_collection }
|
80
|
+
|
81
|
+
context '#[]' do
|
82
|
+
it 'retrieves stored objects' do
|
83
|
+
subject[1] = mock_val
|
84
|
+
subject[1].should equal(mock_val)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context '#fetch' do
|
89
|
+
let(:absent_key_err_re){ /(key not found: 69|index 69 outside)/ }
|
90
|
+
# ABSENT_KEY_ERROR_RE = /(key not found: 69|index 69 outside)/
|
91
|
+
it 'retrieves an object if present' do
|
92
|
+
subject[1] = mock_val
|
93
|
+
subject.fetch(1).should equal(mock_val)
|
94
|
+
end
|
95
|
+
it 'if absent and block given: calls block with label, returning its value' do
|
96
|
+
got_here = nil
|
97
|
+
subject.fetch(69){ got_here = 'yup' ; mock_val }.should equal(mock_val)
|
98
|
+
got_here.should == 'yup'
|
99
|
+
end
|
100
|
+
it 'if absent and no block given: raises an error' do
|
101
|
+
->{ subject.fetch(69) }.should raise_error IndexError, absent_key_err_re# ABSENT_KEY_ERROR_RE
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context '#delete' do
|
106
|
+
it 'retrieves an object if present' do
|
107
|
+
subject[1] = mock_val
|
108
|
+
subject.delete(1).should equal(mock_val)
|
109
|
+
subject.values.should_not include(mock_val)
|
110
|
+
end
|
111
|
+
it 'if absent and block given: calls block with label, returning its value' do
|
112
|
+
got_here = nil
|
113
|
+
subject.delete(69){ got_here = 'yup' ; mock_val }.should equal(mock_val)
|
114
|
+
got_here.should == 'yup'
|
115
|
+
end
|
116
|
+
it 'if absent and no block given: returns nil' do
|
117
|
+
subject.delete(69).should be nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
shared_examples_for 'an auto-keyed collection' do
|
123
|
+
subject{ string_collection }
|
124
|
+
|
125
|
+
it 'retrieves things by their label' do
|
126
|
+
string_collection[:pocket].should == "pocket"
|
127
|
+
string_collection['pocket'].should == nil
|
128
|
+
shouty_collection['POCKET'].should == "pocket"
|
129
|
+
shouty_collection['pocket'].should == nil
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'gets label from key if none supplied' do
|
133
|
+
string_collection[:marvin].should be nil
|
134
|
+
string_collection << 'marvin'
|
135
|
+
string_collection[:marvin].should == 'marvin'
|
136
|
+
shouty_collection << 'marvin'
|
137
|
+
shouty_collection['MARVIN'].should == 'marvin'
|
138
|
+
end
|
139
|
+
|
140
|
+
context '#receive!' do
|
141
|
+
it 'extracts labels given an array' do
|
142
|
+
subject.receive!(%w[horton hears a who])
|
143
|
+
subject[:horton].should == 'horton'
|
144
|
+
end
|
145
|
+
it 'replaces labels in-place, preserving order' do
|
146
|
+
shouty_collection.receive!(%w[in MY pocKET wocKET])
|
147
|
+
shouty_collection['WOCKET'].should == 'wocKET'
|
148
|
+
shouty_collection.values.should == %w[wocKET in MY pocKET]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context '#<<' do
|
153
|
+
it 'adds element under its natural label, at end' do
|
154
|
+
subject << 'marvin'
|
155
|
+
subject.values.last.should == 'marvin'
|
156
|
+
subject[:marvin].should == 'marvin'
|
157
|
+
end
|
158
|
+
it 'replaces duplicate values' do
|
159
|
+
val = 'wocKET'
|
160
|
+
shouty_collection['WOCKET'].should == 'wocket'
|
161
|
+
shouty_collection['WOCKET'].should_not equal(val)
|
162
|
+
shouty_collection << val
|
163
|
+
shouty_collection['WOCKET'].should == 'wocKET'
|
164
|
+
shouty_collection['WOCKET'].should equal(val)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
describe 'collections:', :model_spec => true, :collection_spec => true do
|
171
|
+
let(:symbolized_test_items){ {:wocket => 'wocket', :in => 'in', :my => 'my', :pocket => 'pocket'} }
|
172
|
+
let(:capitalized_test_items){ {'WOCKET' => 'wocket', 'IN' => 'in', 'MY' => 'my', 'POCKET' => 'pocket'} }
|
173
|
+
|
174
|
+
describe Gorillib::Collection do
|
175
|
+
context 'with no key_method (only explicit labels can be stored)' do
|
176
|
+
let(:string_collection){ described_class.receive(symbolized_test_items) }
|
177
|
+
let(:shouty_collection){ described_class.receive(capitalized_test_items) }
|
178
|
+
it_behaves_like 'a collection', :receiving_arrays => :skip
|
179
|
+
it_behaves_like 'a keyed collection'
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'with no key_method (only explicit labels can be stored)' do
|
183
|
+
let(:string_collection){ described_class.receive(symbolized_test_items, key_method: :to_sym) }
|
184
|
+
let(:shouty_collection){ described_class.receive(capitalized_test_items, key_method: :upcase) }
|
185
|
+
it_behaves_like 'a collection', :receiving_arrays => true
|
186
|
+
it_behaves_like 'an auto-keyed collection'
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe Gorillib::ModelCollection, :model_spec => true do
|
191
|
+
context do
|
192
|
+
let(:string_collection){ described_class.receive(%w[wocket in my pocket], key_method: :to_sym, item_type: String) }
|
193
|
+
let(:shouty_collection){ described_class.receive(%w[wocket in my pocket], key_method: :upcase, item_type: String) }
|
194
|
+
it_behaves_like 'a collection'
|
195
|
+
it_behaves_like 'a keyed collection'
|
196
|
+
it_behaves_like 'an auto-keyed collection'
|
197
|
+
end
|
198
|
+
|
199
|
+
subject{ smurf_collection }
|
200
|
+
let(:smurf_collection){ described_class.receive([], key_method: :name, item_type: smurf_class) }
|
201
|
+
let(:test_item){ papa_smurf }
|
202
|
+
let(:test_attrs){ test_item.attributes }
|
203
|
+
let(:mock_factory){ mf = double('factory'); mf.stub(:native? => true) ; mf }
|
204
|
+
|
205
|
+
context '#receive_item' do
|
206
|
+
before do
|
207
|
+
@test_proc = ->{ 'test' };
|
208
|
+
subject.stub(item_type: mock_factory)
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'sends it to item_type for receive, then adds it' do
|
212
|
+
mock_factory.should_receive(:receive).with(mock_val, &@test_proc).and_return(mock_val)
|
213
|
+
subject.should_receive(:add).with(mock_val, nil)
|
214
|
+
subject.receive_item(nil, mock_val, &@test_proc)
|
215
|
+
end
|
216
|
+
it 'accepts an explicit label' do
|
217
|
+
mock_factory.should_receive(:receive).with(mock_val, &@test_proc).and_return(mock_val)
|
218
|
+
subject.should_receive(:add).with(mock_val, 'truffula')
|
219
|
+
subject.receive_item('truffula', mock_val, &@test_proc)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context '#update_or_add' do
|
224
|
+
it "if absent, creates item with given attrs" do
|
225
|
+
test_proc = ->{ 'test' };
|
226
|
+
subject.should_receive(:receive_item).with('truffula', test_attrs.merge(name: 'truffula'), &test_proc).and_return(test_item)
|
227
|
+
#
|
228
|
+
subject.update_or_add('truffula', test_attrs, &test_proc)
|
229
|
+
end
|
230
|
+
it "if there's no key_method, does not it as an attr to create" do
|
231
|
+
subject.send(:remove_instance_variable, '@key_method')
|
232
|
+
subject.should_receive(:receive_item).with('truffula', test_attrs)
|
233
|
+
#
|
234
|
+
subject.update_or_add('truffula', test_attrs)
|
235
|
+
end
|
236
|
+
it "if present, updates item with attrs" do
|
237
|
+
test_proc = ->{ 'test' };
|
238
|
+
hsh = { :smurfiness => 99 }
|
239
|
+
subject['truffula'] = test_item
|
240
|
+
test_item.should_receive(:receive!).with(hsh, &test_proc)
|
241
|
+
#
|
242
|
+
subject.update_or_add('truffula', hsh, &test_proc)
|
243
|
+
end
|
244
|
+
it "returns item" do
|
245
|
+
updated_item = test_item.dup ; updated_item.name = 'truffula'
|
246
|
+
subject.update_or_add('truffula', test_attrs).should == updated_item
|
247
|
+
subject.update_or_add('truffula', test_item ).should == test_item
|
248
|
+
end
|
249
|
+
it "returns item" do
|
250
|
+
updated_item = test_item.dup ; updated_item.name = 'truffula'
|
251
|
+
subject.update_or_add('truffula', test_item ).should == updated_item
|
252
|
+
end
|
253
|
+
it "adds item to collection" do
|
254
|
+
updated_item = test_item.dup ; updated_item.name = 'truffula'
|
255
|
+
subject.update_or_add('truffula', test_attrs)
|
256
|
+
subject['truffula'].should == updated_item
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
describe Gorillib::Model do
|
263
|
+
describe '.collection' do
|
264
|
+
let(:described_class){ smurf_village_class }
|
265
|
+
subject{ described_class.new(name: :smurf_town) }
|
266
|
+
before do
|
267
|
+
smurf_collection_class ; smurf_village_class
|
268
|
+
smurf_class.field :village, smurf_village_class
|
269
|
+
end
|
270
|
+
|
271
|
+
it 'adds an eponymous field' do
|
272
|
+
described_class.should have_field(:smurfs)
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'sets a default that auto-vivifies the collection field' do
|
276
|
+
subject.smurfs.should be_a(Gorillib::ModelCollection)
|
277
|
+
subject.smurfs.belongs_to.should == subject
|
278
|
+
end
|
279
|
+
|
280
|
+
it 'receives' do
|
281
|
+
subject = smurf_village_class.receive({name: :smurf_town,
|
282
|
+
smurfs: [
|
283
|
+
{ name: 'whatever_smurf', smurfiness: 20},
|
284
|
+
]})
|
285
|
+
subject.smurfs['whatever_smurf'].village.should == subject
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Gorillib::Test ; end
|
4
|
+
module Meta::Gorillib::Test ; end
|
5
|
+
|
6
|
+
describe Gorillib::Model, :model_spec => true do
|
7
|
+
after(:each){ Gorillib::Test.nuke_constants ; Meta::Gorillib::Test.nuke_constants }
|
8
|
+
|
9
|
+
let(:car_class) do
|
10
|
+
class Gorillib::Test::Car
|
11
|
+
include Gorillib::Model
|
12
|
+
field :name, Symbol
|
13
|
+
field :make_model, String
|
14
|
+
field :year, Integer
|
15
|
+
field :style, Symbol, :default => :sedan
|
16
|
+
field :doors, Integer,
|
17
|
+
:default => ->{ [:coupe, :convertible].include?(style) ? 2 : 4 }
|
18
|
+
self
|
19
|
+
end
|
20
|
+
Gorillib::Test::Car
|
21
|
+
end
|
22
|
+
let(:wildcat) do
|
23
|
+
car_class.receive( :name => :wildcat, :make_model => 'Buick Wildcat', :year => 1968, :doors => 2 )
|
24
|
+
end
|
25
|
+
let(:ford_39) do
|
26
|
+
car_class.receive( :name => :ford_39 )
|
27
|
+
end
|
28
|
+
let(:year_field ){ car_class.fields[:year] }
|
29
|
+
let(:doors_field){ car_class.fields[:doors] }
|
30
|
+
let(:style_field){ car_class.fields[:style] }
|
31
|
+
|
32
|
+
describe 'Field#default' do
|
33
|
+
it 'is itself a field on Gorillib::Model::Field (boy that is confusing)' do
|
34
|
+
Gorillib::Model::Field.should have_field(:default)
|
35
|
+
end
|
36
|
+
context '#has_default?' do
|
37
|
+
it 'is true if the default is set' do
|
38
|
+
year_field.should_not have_default
|
39
|
+
year_field.default = '2012'
|
40
|
+
year_field.should have_default
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#attribute_default' do
|
46
|
+
before{ car_class.class_eval{ public :attribute_default } }
|
47
|
+
|
48
|
+
it "if the default does not exist, returns nil" do
|
49
|
+
ford_39.attribute_default(year_field).should be_nil
|
50
|
+
end
|
51
|
+
it "if the default is a value, returns it, dup'd if possible" do
|
52
|
+
ford_39.attribute_default(style_field).should == :sedan
|
53
|
+
year_val = double ; dupd_year_val = double
|
54
|
+
year_val.should_receive(:try_dup).and_return(dupd_year_val)
|
55
|
+
year_field.default = year_val
|
56
|
+
ford_39.attribute_default(year_field).should equal(dupd_year_val)
|
57
|
+
end
|
58
|
+
it "if the default is a proc with no args, instance_exec it" do
|
59
|
+
ford_39.style = :sedan
|
60
|
+
ford_39.attribute_default(doors_field).should == 4
|
61
|
+
ford_39.style = :coupe
|
62
|
+
ford_39.attribute_default(doors_field).should == 2
|
63
|
+
end
|
64
|
+
it "if the default is a proc with no args, instance_exec it" do
|
65
|
+
year_field.default = ->{ self }
|
66
|
+
ford_39.attribute_default(year_field).should equal(ford_39)
|
67
|
+
end
|
68
|
+
it "if the default responds_to #call, call it, passing the instance and field name" do
|
69
|
+
callable = double ; expected = double
|
70
|
+
year_field.default = callable
|
71
|
+
callable.should_receive(:respond_to?).with(:call).and_return(true)
|
72
|
+
callable.should_receive(:call).with(ford_39, :year).and_return(expected)
|
73
|
+
ford_39.stub(:read_unset_attribute).and_return('bob')
|
74
|
+
ford_39.attribute_default(year_field).should equal(expected)
|
75
|
+
end
|
76
|
+
it "if the default is a proc with args, call it in current context with the model and field name" do
|
77
|
+
this = self ; expected = double
|
78
|
+
year_field.default = ->(inst, field_name){ [self, inst, field_name, expected] }
|
79
|
+
ford_39.attribute_default(year_field).should == [this, ford_39, :year, expected]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'reading an attribute with a default' do
|
84
|
+
it "sets the default value on the field and returns it" do
|
85
|
+
ford_39.attribute_set?(:doors).should be_false
|
86
|
+
ford_39.read_attribute(:doors).should == 4
|
87
|
+
end
|
88
|
+
it "only calls a block default if the attribute is unset" do
|
89
|
+
val = 0
|
90
|
+
year_field.default = ->{ val += 1 }
|
91
|
+
ford_39.read_attribute(:year).should == 1
|
92
|
+
year_field.default.call.should == 2 # the next call to the block will return 3
|
93
|
+
ford_39.read_attribute(:year).should == 1 # repeated calls give the same value
|
94
|
+
ford_39.unset_attribute(:year)
|
95
|
+
ford_39.read_attribute(:year).should == 3 # see! since it was unset, the block was called again.
|
96
|
+
end
|
97
|
+
it "is attribute_set? after" do
|
98
|
+
ford_39.attribute_set?(:doors).should be_false
|
99
|
+
ford_39.read_attribute(:doors)
|
100
|
+
ford_39.attribute_set?(:doors).should be_true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|