modelish 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module Modelish
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "modelish/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "modelish"
7
+ s.version = Modelish::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Maeve Revels"]
10
+ s.email = ["maeve.revels@g5platform.com"]
11
+ s.homepage = "http://github.com/maeve/#{s.name}"
12
+ s.summary = %q{A lightweight pseudo-modeling not-quite-framework}
13
+ s.description = %q{Sometimes you need something just a little modelish.}
14
+
15
+ s.rubyforge_project = s.name
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency('hashie', '~> 1.0')
23
+
24
+ s.add_development_dependency('rspec','~> 2.5')
25
+ s.add_development_dependency('yard', '~> 0.6')
26
+ s.add_development_dependency('bluecloth','~> 2.0.11')
27
+ s.add_development_dependency('autotest')
28
+
29
+ s.has_rdoc = true
30
+ end
@@ -0,0 +1,285 @@
1
+ require 'spec_helper'
2
+
3
+ describe Modelish::Base do
4
+ subject { model_class }
5
+ let(:model_class) { Class.new(Modelish::Base) }
6
+
7
+ let(:model) { model_class.new(init_options) }
8
+ let(:init_options) { nil }
9
+ let(:default_value) { nil }
10
+
11
+ it { should respond_to(:property) }
12
+
13
+ context "with simple property" do
14
+ before { model_class.property(property_name) }
15
+
16
+ let(:property_name) { :simple_property }
17
+ let(:property_value) { 'simple string value' }
18
+
19
+ it_should_behave_like 'a modelish property'
20
+ it_should_behave_like 'a valid model'
21
+ end
22
+
23
+ context "with property default value" do
24
+ before { model_class.property(property_name, :default => default_value) }
25
+
26
+ let(:property_name) { :default_property }
27
+ let(:property_value) { 'non-default value' }
28
+ let(:default_value) { 42 }
29
+
30
+ it_should_behave_like 'a modelish property'
31
+ it_should_behave_like 'a valid model'
32
+ end
33
+
34
+ context "with translated property" do
35
+ before { model_class.property(property_name, :from => from_name) }
36
+
37
+ subject { model }
38
+
39
+ let(:property_name) { :translated_property }
40
+ let(:from_name) { 'OldPropertyNAME' }
41
+ let(:property_value) { 'new value' }
42
+
43
+ it_should_behave_like 'a modelish property'
44
+
45
+ it { should_not respond_to(from_name) }
46
+ it { should respond_to("#{from_name}=") }
47
+
48
+ describe "original setter" do
49
+ subject { model.send("#{from_name}=", property_value) }
50
+
51
+ it "should change the property value" do
52
+ expect { subject }.to change{model.send(property_name)}.from(nil).to(property_value)
53
+ end
54
+ end
55
+
56
+ it_should_behave_like 'a valid model'
57
+ end
58
+
59
+ context "with typed property" do
60
+ before { model_class.property(property_name, options) }
61
+
62
+ subject { model }
63
+
64
+ let(:property_name) { :my_int_property }
65
+ let(:property_type) { Integer }
66
+
67
+ let(:valid_string) { '42' }
68
+ let(:valid_typed_value) { 42 }
69
+ let(:invalid_value) { '42.0' }
70
+
71
+ context "without default value" do
72
+ let(:options) { {:type => property_type} }
73
+ let(:default_value) { nil }
74
+
75
+ context "without init options" do
76
+ it_should_behave_like 'a typed property', :my_int_property, Integer
77
+ it_should_behave_like 'a valid model'
78
+ end
79
+
80
+ context "with init options" do
81
+ let(:model) { model_class.new(property_name => valid_string) }
82
+
83
+ its(:my_int_property) { should == valid_typed_value }
84
+ its(:raw_my_int_property) { should == valid_string }
85
+
86
+ it_should_behave_like 'a valid model'
87
+ end
88
+ end
89
+
90
+ context "with default value" do
91
+ let(:options) { {:type => property_type, :default => default_value} }
92
+ let(:default_value) { 0 }
93
+
94
+ it_should_behave_like 'a typed property', :my_int_property, Integer
95
+
96
+ it_should_behave_like 'a valid model'
97
+ end
98
+ end
99
+
100
+ context "with required property" do
101
+ before { model_class.property(property_name, :required => true) }
102
+
103
+ let(:property_name) { :my_required_property }
104
+ let(:property_value) { 'a valid string' }
105
+
106
+ subject { model }
107
+
108
+ let(:init_options) { {property_name => property_value} }
109
+
110
+ it_should_behave_like 'a modelish property'
111
+
112
+ context "when property value is not nil" do
113
+ it_should_behave_like 'a valid model'
114
+ end
115
+
116
+ context "when property value is nil" do
117
+ let(:property_value) { nil }
118
+
119
+ it_should_behave_like 'a model with an invalid property' do
120
+ let(:error_count) { 1 }
121
+ end
122
+ end
123
+
124
+ context "when property value is an empty string" do
125
+ let(:property_value) { ' ' }
126
+
127
+ it_should_behave_like 'a model with an invalid property' do
128
+ let(:error_count) { 1 }
129
+ end
130
+ end
131
+ end
132
+
133
+ context "with length-restricted property" do
134
+ before { model_class.property(property_name, :required => false, :max_length => max_length) }
135
+
136
+ let(:property_name) { :my_required_property }
137
+ let(:property_value) { 'a' * (max_length - 1) }
138
+ let(:max_length) { 10 }
139
+
140
+ subject { model }
141
+
142
+ let(:init_options) { {property_name => property_value} }
143
+
144
+ it_should_behave_like 'a modelish property'
145
+
146
+ context "when property value is nil" do
147
+ let(:property_value) { nil }
148
+
149
+ it_should_behave_like 'a valid model'
150
+ end
151
+
152
+ context "when property value is a valid string" do
153
+ it_should_behave_like 'a valid model'
154
+ end
155
+
156
+ context "when property value is too long" do
157
+ let(:property_value) { 'a' * (max_length + 1) }
158
+
159
+ it_should_behave_like 'a model with an invalid property' do
160
+ let(:error_count) { 1 }
161
+ end
162
+ end
163
+ end
164
+
165
+ context "with property that has validator block" do
166
+ before { model_class.property(property_name, :validator => validator_block) }
167
+
168
+ let(:property_name) { :validated_property }
169
+ let(:validator_block) do
170
+ lambda { |val| "#{property_name} must support to_hash" unless val.respond_to?(:to_hash) }
171
+ end
172
+ let(:property_value) { Hash.new }
173
+
174
+ subject { model }
175
+
176
+ let(:init_options) { {property_name => property_value} }
177
+
178
+ it_should_behave_like 'a modelish property'
179
+
180
+ context "when value is valid" do
181
+ it_should_behave_like 'a valid model'
182
+ end
183
+
184
+ context "when value is invalid" do
185
+ let(:property_value) { Array.new }
186
+
187
+ it_should_behave_like 'a model with an invalid property' do
188
+ let(:error_count) { 1 }
189
+ end
190
+ end
191
+ end
192
+
193
+ context "with type-validated property" do
194
+ before { model_class.property(property_name, prop_options) }
195
+
196
+ let(:prop_options) { {:type => Integer, :validate_type => true} }
197
+ let(:property_name) { :strict_typed_property }
198
+ let(:property_value) { 42 }
199
+
200
+ subject { model }
201
+
202
+ let(:init_options) { {property_name => property_value} }
203
+
204
+ it_should_behave_like 'a modelish property'
205
+
206
+ context "when value is nil" do
207
+ let(:property_value) { nil }
208
+
209
+ it_should_behave_like 'a valid model'
210
+ end
211
+
212
+ context "when value is valid" do
213
+ let(:property_value) { '42' }
214
+
215
+ it_should_behave_like 'a valid model'
216
+ end
217
+
218
+ context "when value is invalid" do
219
+ let(:property_value) { 'forty-two' }
220
+
221
+ it_should_behave_like 'a model with an invalid property' do
222
+ let(:error_count) { 1 }
223
+ end
224
+ end
225
+
226
+ context "with no property type" do
227
+ let(:prop_options) { {:validate_type => true} }
228
+
229
+ context "when value is nil" do
230
+ let(:property_value) { nil }
231
+
232
+ it_should_behave_like 'a valid model'
233
+ end
234
+
235
+ context "when value is not nil" do
236
+ let(:property_value) { Object.new }
237
+
238
+ it_should_behave_like 'a valid model'
239
+ end
240
+ end
241
+ end
242
+
243
+ context "with multiple validations" do
244
+ before do
245
+ model_class.property(property_name, :required => true,
246
+ :max_length => 3,
247
+ :type => Symbol,
248
+ :validate_type => true)
249
+ end
250
+
251
+ let(:init_options) { {property_name => property_value} }
252
+
253
+ let(:property_name) { :prop_with_many_validations }
254
+
255
+ context "when value is valid" do
256
+ let(:property_value) { :foo }
257
+
258
+ it_should_behave_like 'a valid model'
259
+ end
260
+
261
+ context "when value is nil" do
262
+ let(:property_value) { nil }
263
+
264
+ it_should_behave_like 'a model with an invalid property' do
265
+ let(:error_count) { 1 }
266
+ end
267
+ end
268
+
269
+ context "when value is too long" do
270
+ let(:property_value) { :crazy_long_value }
271
+
272
+ it_should_behave_like 'a model with an invalid property' do
273
+ let(:error_count) { 1 }
274
+ end
275
+ end
276
+
277
+ context "when value is not a symbol" do
278
+ let(:property_value) { Object.new }
279
+
280
+ it_should_behave_like 'a model with an invalid property' do
281
+ let(:error_count) { 1 }
282
+ end
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ describe Modelish::PropertyTypes do
4
+ let(:model_class) { Class.new { include Modelish::PropertyTypes } }
5
+ let(:model) { model_class.new }
6
+
7
+ let(:property_name) { :my_property }
8
+
9
+ subject { model_class }
10
+
11
+ context "when included" do
12
+ it { should respond_to(:add_property_type) }
13
+ it { should respond_to(:property_types) }
14
+
15
+ describe "property_types" do
16
+ subject { model_class.property_types }
17
+
18
+ it { should be_a Hash }
19
+ it { should be_empty }
20
+ end
21
+ end
22
+
23
+ describe "#add_property_type" do
24
+ before { model_class.add_property_type(property_name, property_type) }
25
+
26
+ let(:default_value) { nil }
27
+
28
+ context "when property_type is Integer" do
29
+ let(:property_type) { Integer }
30
+
31
+ it_should_behave_like 'a typed property', :my_property, Integer do
32
+ let(:valid_string) { '42' }
33
+ let(:valid_typed_value) { 42 }
34
+ let(:invalid_value) { 'forty-two' }
35
+ end
36
+ end
37
+
38
+ context "when property_type is Float" do
39
+ let(:property_type) { Float }
40
+
41
+ it_should_behave_like 'a typed property', :my_property, Float do
42
+ let(:valid_string) { '42.5' }
43
+ let(:valid_typed_value) { 42.5 }
44
+ let(:invalid_value) { 'forty-two point five' }
45
+ end
46
+ end
47
+
48
+ context "when property_type is Date" do
49
+ let(:property_type) { Date }
50
+
51
+ it_should_behave_like 'a typed property', :my_property, Date do
52
+ let(:valid_string) { '2011-03-10' }
53
+ let(:valid_typed_value) { Date.civil(2011, 03, 10) }
54
+ let(:invalid_value) { 'foo' }
55
+ end
56
+ end
57
+
58
+ context "when property_type is String" do
59
+ let(:property_type) { String }
60
+
61
+ it_should_behave_like 'a typed property', :my_property, String do
62
+ let(:valid_string) { "\tmy_string \n" }
63
+ let(:valid_typed_value) { "my_string" }
64
+ end
65
+ end
66
+
67
+ context "when property_type is Symbol" do
68
+ let(:property_type) { Symbol }
69
+
70
+ it_should_behave_like 'a typed property', :my_property, Symbol do
71
+ let(:valid_string) { 'MyCrazy String' }
72
+ let(:valid_typed_value) { :my_crazy_string }
73
+ end
74
+ end
75
+
76
+ context "when property_type is Array" do
77
+ let(:property_type) { Array }
78
+
79
+ it_should_behave_like 'a typed property', :my_property, Array do
80
+ let(:valid_string) { 'my valid string' }
81
+ let(:valid_typed_value) { ['my valid string'] }
82
+ end
83
+ end
84
+
85
+ context "when type is an arbitrary class" do
86
+ class CustomType
87
+ attr_accessor :foo
88
+
89
+ def initialize(raw_val)
90
+ if raw_val.respond_to?(:foo)
91
+ self.foo = raw_val.foo
92
+ elsif raw_val.is_a?(String)
93
+ self.foo = raw_val
94
+ else
95
+ raise TypeError.new
96
+ end
97
+ end
98
+
99
+ def ==(other)
100
+ other.respond_to?(:foo) && other.foo == self.foo
101
+ end
102
+ end
103
+
104
+ let(:property_type) { CustomType }
105
+
106
+ it_should_behave_like 'a typed property', :my_property, CustomType do
107
+ let(:valid_string) { 'bar' }
108
+ let(:valid_typed_value) { CustomType.new(valid_string) }
109
+ let(:invalid_value) { Object.new }
110
+ end
111
+ end
112
+
113
+ context "when type is a closure" do
114
+ let(:property_type) do
115
+ lambda do |val|
116
+ value = val.respond_to?(:split) ? val.split(',') : val
117
+ value.collect { |n| n.to_i }
118
+ end
119
+ end
120
+
121
+ it_should_behave_like 'a typed property', :my_property do
122
+ let(:valid_string) { '1,2,3' }
123
+ let(:valid_typed_value) { [1,2,3] }
124
+ let(:invalid_value) { Object.new }
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,695 @@
1
+ require 'spec_helper'
2
+
3
+ describe Modelish::Validations do
4
+ let(:model_class) { Class.new { include Modelish::Validations } }
5
+
6
+ let(:property_name) { :validated_property }
7
+
8
+ let(:model) { model_class.new }
9
+
10
+ subject { model_class }
11
+
12
+ it { should respond_to(:validate_required?) }
13
+
14
+ describe ".validate_required?" do
15
+ subject { model_class.validate_required?(property_name => value) }
16
+
17
+ context "when value is nil" do
18
+ let(:value) { nil }
19
+
20
+ it { should be_false }
21
+ end
22
+
23
+ context "when value is blank" do
24
+ let(:value) { ' ' }
25
+
26
+ it { should be_false }
27
+ end
28
+
29
+ context "when value is not blank" do
30
+ let(:value) { Object.new }
31
+
32
+ it { should be_true }
33
+ end
34
+ end
35
+
36
+ it { should respond_to(:validate_required) }
37
+
38
+ describe ".validate_required" do
39
+ subject { errors }
40
+ let(:errors) { model_class.validate_required(property_name => value) }
41
+
42
+ context "when value is nil" do
43
+ let(:value) { nil }
44
+
45
+ it { should_not be_empty }
46
+
47
+ describe "first error" do
48
+ subject { errors.first }
49
+
50
+ it { should be_an ArgumentError }
51
+ its(:message) { should match(/#{property_name}/i) }
52
+ end
53
+ end
54
+
55
+ context "when value is blank" do
56
+ let(:value) { ' ' }
57
+
58
+ it { should_not be_empty }
59
+
60
+ describe "first error" do
61
+ subject { errors.first }
62
+
63
+ it { should be_an ArgumentError }
64
+ its(:message) { should match(/#{property_name}/i) }
65
+ end
66
+ end
67
+
68
+ context "when value is not blank" do
69
+ let(:value) { Object.new }
70
+
71
+ it { should be_empty }
72
+ end
73
+ end
74
+
75
+ it { should respond_to(:validate_required!) }
76
+
77
+ describe ".validate_required!" do
78
+ subject { model_class.validate_required!(property_name => value) }
79
+
80
+ context "when value is nil" do
81
+ let(:value) { nil }
82
+
83
+ it "should raise an ArgumentError" do
84
+ expect { subject }.to raise_error(ArgumentError)
85
+ end
86
+
87
+ it "should include the name in the error message" do
88
+ expect { subject }.to raise_error { |e| e.message.should match(/#{property_name}/i) }
89
+ end
90
+ end
91
+
92
+ context "when value is blank" do
93
+ let(:value) { ' ' }
94
+
95
+ it "should raise an ArgumentError" do
96
+ expect { subject }.to raise_error(ArgumentError)
97
+ end
98
+
99
+ it "should include the name in the error message" do
100
+ expect { subject }.to raise_error { |e| e.message.should match(/#{property_name}/i) }
101
+ end
102
+ end
103
+
104
+ context "when value is not blank" do
105
+ let(:value) { 'valid value' }
106
+
107
+ it "should not raise any errors" do
108
+ expect { subject }.to_not raise_error
109
+ end
110
+ end
111
+ end
112
+
113
+ it { should respond_to(:validate_length) }
114
+
115
+ describe ".validate_length" do
116
+ subject { model_class.validate_length(property_name, value, max_length) }
117
+ let(:max_length) { 10 }
118
+
119
+ context "when value is longer than max_length" do
120
+ let(:value) { 'a' * (max_length + 1) }
121
+
122
+ it { should be_an ArgumentError }
123
+ its(:message) { should match(/#{property_name}/i) }
124
+ its(:message) { should match(/#{max_length}/) }
125
+ end
126
+
127
+ context "when value is shorter than max_length" do
128
+ let(:value) { 'a' * (max_length - 1) }
129
+
130
+ it { should be_nil }
131
+ end
132
+
133
+ context "when value is the same length as max_length" do
134
+ let(:value) { 'a' * max_length }
135
+
136
+ it { should be_nil }
137
+ end
138
+
139
+ context "when value is nil" do
140
+ let(:value) { nil }
141
+
142
+ it { should be_nil }
143
+ end
144
+
145
+ context "when max_length is nil" do
146
+ let(:value) { mock(:length => 50) }
147
+ let(:max_length) { nil }
148
+
149
+ it { should be_nil }
150
+ end
151
+ end
152
+
153
+ it { should respond_to(:validate_length?) }
154
+
155
+ describe ".validate_length?" do
156
+ subject { model_class.validate_length?(property_name, value, max_length) }
157
+ let(:max_length) { 16 }
158
+
159
+ context "when value is longer than max_length" do
160
+ let(:value) { 'a' * (max_length + 1) }
161
+
162
+ it { should be_false }
163
+ end
164
+
165
+ context "when value is shorter than max_length" do
166
+ let(:value) { 'a' * (max_length - 1) }
167
+
168
+ it { should be_true }
169
+ end
170
+
171
+ context "when value is the same length as max_length" do
172
+ let(:value) { 'a' * max_length }
173
+
174
+ it { should be_true }
175
+ end
176
+
177
+ context "when value is nil" do
178
+ let(:value) { nil }
179
+
180
+ it { should be_true }
181
+ end
182
+
183
+ context "when max_length is nil" do
184
+ let(:value) { Object.new }
185
+ let(:max_length) { nil }
186
+
187
+ it { should be_true }
188
+ end
189
+ end
190
+
191
+ it { should respond_to(:validate_length!) }
192
+
193
+ describe ".validate_length!" do
194
+ subject { model_class.validate_length!(property_name, value, max_length) }
195
+ let(:max_length) { 8 }
196
+
197
+ context "when value is longer than max_length" do
198
+ let(:value) { 'a' * (max_length + 1) }
199
+
200
+ it "should raise an ArgumentError" do
201
+ expect { subject }.to raise_error(ArgumentError)
202
+ end
203
+
204
+ it "should include the name in the error message" do
205
+ expect { subject }.to raise_error { |e| e.message.should match(/#{property_name}/i) }
206
+ end
207
+
208
+ it "should include the max length in the error message" do
209
+ expect { subject }.to raise_error { |e| e.message.should match(/#{max_length}/) }
210
+ end
211
+ end
212
+
213
+ context "when value is shorter than max_length" do
214
+ let(:value) { 'a' * (max_length - 1) }
215
+
216
+ it "should not raise an error" do
217
+ expect { subject }.to_not raise_error
218
+ end
219
+ end
220
+
221
+ context "when value is nil" do
222
+ let(:value) { nil }
223
+
224
+ it "should not raise an error" do
225
+ expect { subject }.to_not raise_error
226
+ end
227
+ end
228
+
229
+ context "when max_length is nil" do
230
+ let(:max_length) { nil }
231
+ let(:value) { 'aaaaaaaaaaa' }
232
+
233
+ it "should not raise an error" do
234
+ expect { subject }.to_not raise_error
235
+ end
236
+ end
237
+ end
238
+
239
+ it { should respond_to(:validate_type) }
240
+
241
+ describe ".validate_type" do
242
+ subject { model_class.validate_type(property_name, property_value, property_type) }
243
+
244
+ context "for type Integer" do
245
+ let(:property_type) { Integer }
246
+
247
+ context "with nil value" do
248
+ let(:property_value) { nil }
249
+
250
+ it { should be_nil }
251
+ end
252
+
253
+ context "with valid int" do
254
+ let(:property_value) { 42 }
255
+
256
+ it { should be_nil }
257
+ end
258
+
259
+ context "with valid string" do
260
+ let(:property_value) { '42' }
261
+
262
+ it { should be_nil }
263
+ end
264
+
265
+ context "with invalid value" do
266
+ let(:property_value) { 42.99 }
267
+
268
+ it { should be_an ArgumentError }
269
+ its(:message) { should match(/#{property_name}/i) }
270
+ its(:message) { should match(/#{property_value.inspect}/i) }
271
+ its(:message) { should match(/#{property_type}/i) }
272
+ end
273
+ end
274
+
275
+ context "for type Float" do
276
+ let(:property_type) { Float }
277
+
278
+ context "with nil value" do
279
+ let(:property_value) { nil }
280
+
281
+ it { should be_nil }
282
+ end
283
+
284
+ context "with valid float value" do
285
+ let(:property_value) { 42.5 }
286
+
287
+ it { should be_nil }
288
+ end
289
+
290
+ context "with valid string" do
291
+ let(:property_value) { '42.5' }
292
+
293
+ it { should be_nil }
294
+ end
295
+
296
+ context "with valid type that can be upcast" do
297
+ let(:property_value) { 42 }
298
+
299
+ it { should be_nil }
300
+ end
301
+
302
+ context "with invalid value" do
303
+ let(:property_value) { 'forty-two' }
304
+
305
+ it { should be_an ArgumentError }
306
+ its(:message) { should match(/#{property_name}/i) }
307
+ its(:message) { should match(/#{property_value.inspect}/i) }
308
+ its(:message) { should match(/#{property_type}/i) }
309
+ end
310
+ end
311
+
312
+ context "for type Array" do
313
+ let(:property_type) { Array }
314
+
315
+ context "with nil" do
316
+ let(:property_value) { nil }
317
+
318
+ it { should be_nil }
319
+ end
320
+ context "with a valid array" do
321
+ let(:property_value) { [1,2,3] }
322
+
323
+ it { should be_nil }
324
+ end
325
+
326
+ context "with an invalid value" do
327
+ let(:property_value) { {1 => 2, 3 => 4} }
328
+
329
+ it { should be_an ArgumentError }
330
+ its(:message) { should match(/#{property_name}/i) }
331
+ its(:message) { should match(/#{property_value.inspect.gsub('{', '\{').gsub('}', '\}')}/i) }
332
+ its(:message) { should match(/#{property_type}/i) }
333
+ end
334
+ end
335
+
336
+ context "for an arbitrary class" do
337
+ let(:property_type) { Hash }
338
+
339
+ context "with nil value" do
340
+ let(:property_value) { nil }
341
+
342
+ it { should be_nil }
343
+ end
344
+
345
+ context "with valid hash value" do
346
+ let(:property_value) { {1 => 2} }
347
+
348
+ it { should be_nil }
349
+ end
350
+
351
+ context "with invalid value" do
352
+ let(:property_value) { [1,2] }
353
+
354
+ it { should be_an ArgumentError }
355
+ its(:message) { should match(/#{property_name}/i) }
356
+ its(:message) { should match(/#{property_value.inspect}/i) }
357
+ its(:message) { should match(/#{property_type}/i) }
358
+ end
359
+ end
360
+
361
+ context "for type Date" do
362
+ let(:property_type) { Date }
363
+
364
+ context "with nil value" do
365
+ let(:property_value) { nil }
366
+
367
+ it { should be_nil }
368
+ end
369
+
370
+ context "with valid string" do
371
+ let(:property_value) { '2011-03-10' }
372
+
373
+ it { should be_nil }
374
+ end
375
+
376
+ context "with valid Date" do
377
+ let(:property_value) { Date.civil(2011, 3, 10) }
378
+
379
+ it { should be_nil }
380
+ end
381
+
382
+ context "with invalid value" do
383
+ let(:property_value) { 'this is not a date' }
384
+
385
+ it { should be_an ArgumentError }
386
+ its(:message) { should match(/#{property_name}/i) }
387
+ its(:message) { should match(/#{property_value.inspect}/i) }
388
+ its(:message) { should match(/#{property_type}/i) }
389
+ end
390
+ end
391
+
392
+ context "for a Symbol type" do
393
+ let(:property_type) { Symbol }
394
+
395
+ context "with nil value" do
396
+ let(:property_value) { nil }
397
+
398
+ it { should be_nil }
399
+ end
400
+
401
+ context "with a valid string" do
402
+ let(:property_value) { 'my string' }
403
+
404
+ it { should be_nil }
405
+ end
406
+
407
+ context "with a valid symbol" do
408
+ let(:property_value) { :my_symbol }
409
+
410
+ it { should be_nil }
411
+ end
412
+
413
+ context "with an invalid value" do
414
+ let(:property_value) { Object.new }
415
+
416
+ it { should be_an ArgumentError }
417
+ its(:message) { should match(/#{property_name}/i) }
418
+ its(:message) { should match(/#{property_value.inspect}/i) }
419
+ its(:message) { should match(/#{property_type}/i) }
420
+ end
421
+ end
422
+
423
+ context "when type is nil" do
424
+ let(:property_type) { nil }
425
+
426
+ context "with any value" do
427
+ let(:property_value) { 'foo' }
428
+
429
+ it { should be_nil }
430
+ end
431
+ end
432
+ end
433
+
434
+ it { should respond_to(:validate_type!) }
435
+
436
+ describe ".validate_type!" do
437
+ subject { model_class.validate_type!(property_name, property_value, property_type) }
438
+
439
+ context "for type Integer" do
440
+ let(:property_type) { Integer }
441
+
442
+ context "with nil value" do
443
+ let(:property_value) { nil }
444
+
445
+ it "should not raise any errors" do
446
+ expect { subject }.to_not raise_error
447
+ end
448
+ end
449
+
450
+ context "with valid int" do
451
+ let(:property_value) { 42 }
452
+
453
+ it "should not raise any errors" do
454
+ expect { subject }.to_not raise_error
455
+ end
456
+ end
457
+
458
+ context "with valid string" do
459
+ let(:property_value) { '42' }
460
+
461
+ it "should not raise any errors" do
462
+ expect { subject }.to_not raise_error
463
+ end
464
+ end
465
+
466
+ context "with invalid value" do
467
+ let(:property_value) { 42.99 }
468
+
469
+ it "should raise an ArgumentError" do
470
+ expect { subject }.to raise_error(ArgumentError)
471
+ end
472
+
473
+ it "should reference the property name in the error message" do
474
+ expect { subject }.to raise_error { |e| e.message.should match(/#{property_name}/i) }
475
+ end
476
+ end
477
+ end
478
+ end
479
+
480
+ it { should respond_to(:validate_type?) }
481
+
482
+ describe ".validate_type?" do
483
+ subject { model_class.validate_type?(property_name, property_value, property_type) }
484
+
485
+ context "for type Integer" do
486
+ let(:property_type) { Integer }
487
+
488
+ context "with nil value" do
489
+ let(:property_value) { nil }
490
+
491
+ it { should be_true }
492
+ end
493
+
494
+ context "with valid int" do
495
+ let(:property_value) { 42 }
496
+
497
+ it { should be_true }
498
+ end
499
+
500
+ context "with valid string" do
501
+ let(:property_value) { '42' }
502
+
503
+ it { should be_true }
504
+ end
505
+
506
+ context "with invalid value" do
507
+ let(:property_value) { 42.99 }
508
+
509
+ it { should be_false }
510
+ end
511
+ end
512
+ end
513
+
514
+ it { should respond_to(:add_validator) }
515
+
516
+ context "with simple validated property" do
517
+ before do
518
+ model_class.add_validator(property_name, &validator_block)
519
+ model.send("#{property_name}=", property_value)
520
+ end
521
+
522
+ subject { model }
523
+
524
+ let(:property_value) { '42' }
525
+
526
+ let(:message_validator) { lambda { |val| val.to_i != 42 ? "#{property_name} must be 42" : nil } }
527
+ let(:error_validator) { lambda { |val| val.to_i != 42 ? ArgumentError.new("#{property_name} must be 42") : nil } }
528
+
529
+ describe ".validators" do
530
+ let(:validator_block) { message_validator }
531
+
532
+ subject { validators }
533
+ let(:validators) { model_class.validators }
534
+
535
+ it { should have(1).property }
536
+
537
+ describe "[property_name]" do
538
+ subject { prop_validators }
539
+ let(:prop_validators) { validators[property_name] }
540
+
541
+ it { should have(1).validator }
542
+
543
+ describe "#first" do
544
+ subject { prop_validators.first }
545
+
546
+ it { should respond_to(:call) }
547
+ it { should == validator_block }
548
+ end
549
+ end
550
+ end
551
+
552
+ context "with valid value" do
553
+ context "with validator that returns an error message" do
554
+ let(:validator_block) { message_validator }
555
+
556
+ it_should_behave_like 'a valid model'
557
+ end
558
+
559
+ context "with validator that returns an error" do
560
+ let(:validator_block) { error_validator }
561
+
562
+ it_should_behave_like 'a valid model'
563
+ end
564
+ end
565
+
566
+ context "with invalid value" do
567
+ let(:property_value) { 'not valid' }
568
+
569
+ context "with validator that returns an error message" do
570
+ let(:validator_block) { message_validator }
571
+
572
+ it_should_behave_like 'a model with an invalid property' do
573
+ let(:error_count) { 1 }
574
+ end
575
+ end
576
+
577
+ context "with validator that returns an error" do
578
+ let(:validator_block) { error_validator }
579
+
580
+ it_should_behave_like 'a model with an invalid property' do
581
+ let(:error_count) { 1 }
582
+ end
583
+ end
584
+ end
585
+ end
586
+
587
+ context "with property that has multiple validations" do
588
+ before do
589
+ model_class.add_validator(property_name, &nil_validator)
590
+ model_class.add_validator(property_name, &int_validator)
591
+ model.send("#{property_name}=", property_value)
592
+ end
593
+
594
+ subject { model }
595
+
596
+ let(:nil_validator) { lambda { |val| val.nil? ? "#{property_name} should not be nil" : nil } }
597
+ let(:int_validator) { lambda { |val| "#{property_name} must represent the integer 42" unless val.to_i == 42 } }
598
+
599
+ let(:property_value) { '42' }
600
+
601
+ describe ".validators" do
602
+ subject { validators }
603
+ let(:validators) { model_class.validators }
604
+
605
+ it { should have(1).property }
606
+
607
+ describe "[property_name]" do
608
+ subject { prop_validators }
609
+ let(:prop_validators) { validators[property_name] }
610
+
611
+ it { should have(2).validators }
612
+
613
+ describe "#first" do
614
+ subject { prop_validators.first }
615
+ it { should respond_to(:call) }
616
+ it { should == nil_validator }
617
+ end
618
+
619
+ describe "#last" do
620
+ subject { prop_validators.last }
621
+ it { should respond_to(:call) }
622
+ it { should == int_validator }
623
+ end
624
+ end
625
+ end
626
+
627
+ context "with valid value" do
628
+ it { should be_valid }
629
+
630
+ describe "#validate" do
631
+ subject { model.validate }
632
+
633
+ it { should be_empty }
634
+ end
635
+
636
+ describe "#validate!" do
637
+ subject { model.validate! }
638
+
639
+ it "should not raise any errors" do
640
+ expect { subject }.to_not raise_error
641
+ end
642
+ end
643
+ end
644
+
645
+ context "with nil value" do
646
+ let(:property_value) { nil }
647
+
648
+ it { should_not be_valid }
649
+
650
+ describe "#validate" do
651
+ subject { errors }
652
+ let(:errors) { model.validate }
653
+
654
+ it { should have(1).property }
655
+
656
+ it { should have_key(property_name) }
657
+
658
+ describe "[property_name]" do
659
+ subject { prop_errors }
660
+ let(:prop_errors) { errors[property_name] }
661
+
662
+ it { should have(2).errors }
663
+
664
+ describe "#first" do
665
+ subject { prop_errors.first }
666
+
667
+ it { should be_a ArgumentError }
668
+ its(:message) { should match(/#{property_name}/i) }
669
+ its(:message) { should match(/nil/i) }
670
+ end
671
+
672
+ describe "#last" do
673
+ subject { prop_errors.last }
674
+
675
+ it { should be_a ArgumentError }
676
+ its(:message) { should match(/#{property_name}/i) }
677
+ its(:message) { should match(/integer/i) }
678
+ end
679
+ end
680
+ end
681
+
682
+ describe "#validate!" do
683
+ subject { model.validate! }
684
+
685
+ it "should raise an ArgumentError" do
686
+ expect { subject }.to raise_error(ArgumentError)
687
+ end
688
+
689
+ it "should reference the property name in the error message" do
690
+ expect { subject }.to raise_error { |e| e.message.should match(/#{property_name}/i) }
691
+ end
692
+ end
693
+ end
694
+ end
695
+ end