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.
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/.rvmrc +3 -0
- data/Gemfile +4 -0
- data/LICENSE +19 -0
- data/README.md +95 -0
- data/Rakefile +17 -0
- data/lib/modelish.rb +8 -0
- data/lib/modelish/base.rb +44 -0
- data/lib/modelish/property_types.rb +133 -0
- data/lib/modelish/validations.rb +208 -0
- data/lib/modelish/version.rb +3 -0
- data/modelish.gemspec +30 -0
- data/spec/modelish/base_spec.rb +285 -0
- data/spec/modelish/property_types_spec.rb +128 -0
- data/spec/modelish/validations_spec.rb +695 -0
- data/spec/modelish_spec.rb +7 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/property_examples.rb +38 -0
- data/spec/support/typed_property_examples.rb +90 -0
- data/spec/support/validation_examples.rb +70 -0
- metadata +161 -0
data/modelish.gemspec
ADDED
@@ -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
|