modelish 0.1.0

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.
@@ -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