lotus-validations 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{LICENSE.txt → LICENSE.md} +0 -0
- data/README.md +366 -3
- data/lib/lotus-validations.rb +1 -0
- data/lib/lotus/validations.rb +393 -2
- data/lib/lotus/validations/attribute_validator.rb +280 -0
- data/lib/lotus/validations/coercions.rb +30 -0
- data/lib/lotus/validations/error.rb +61 -0
- data/lib/lotus/validations/errors.rb +123 -0
- data/lib/lotus/validations/version.rb +2 -1
- data/lotus-validations.gemspec +15 -11
- metadata +41 -11
- data/.gitignore +0 -14
- data/Gemfile +0 -4
- data/Rakefile +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88761697180386ed3a562170900b63ceb0618b03
|
4
|
+
data.tar.gz: e98d3b024612251600f74e104a3f6f43eb9bf33e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76fac7ccbbafca4210be4ab570a3e5ae708378a08478aae7836a4dfd2e0ddf70c33a99bdaef93e544bbcd3d78917a65ce5f5868ea17e17788e75a1203471d7cb
|
7
|
+
data.tar.gz: 7185e7295684adaf616fa8abb450f11126adc2cc4ea8ed7d1769c27ebfad9a975b76e2727096832ff49e7cfa94a1d7c536293b612e823daaf90659e109502d46
|
data/{LICENSE.txt → LICENSE.md}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,6 +1,28 @@
|
|
1
1
|
# Lotus::Validations
|
2
2
|
|
3
|
-
|
3
|
+
Validations mixins for objects
|
4
|
+
|
5
|
+
## Status
|
6
|
+
|
7
|
+
[![Gem Version](http://img.shields.io/gem/v/lotus-validations.svg)](https://badge.fury.io/rb/lotus-validations)
|
8
|
+
[![Build Status](http://img.shields.io/travis/lotus/validations/master.svg)](https://travis-ci.org/lotus/validations?branch=master)
|
9
|
+
[![Coverage](http://img.shields.io/coveralls/lotus/validations/master.svg)](https://coveralls.io/r/lotus/validations)
|
10
|
+
[![Code Climate](http://img.shields.io/codeclimate/github/lotus/validations.svg)](https://codeclimate.com/github/lotus/validations)
|
11
|
+
[![Dependencies](http://img.shields.io/gemnasium/lotus/validations.svg)](https://gemnasium.com/lotus/validations)
|
12
|
+
[![Inline Docs](http://inch-ci.org/github/lotus/validations.svg)](http://inch-ci.org/github/lotus/validations)
|
13
|
+
|
14
|
+
## Contact
|
15
|
+
|
16
|
+
* Home page: http://lotusrb.org
|
17
|
+
* Mailing List: http://lotusrb.org/mailing-list
|
18
|
+
* API Doc: http://rdoc.info/gems/lotus-validations
|
19
|
+
* Bugs/Issues: https://github.com/lotus/validations/issues
|
20
|
+
* Support: http://stackoverflow.com/questions/tagged/lotus-ruby
|
21
|
+
* Chat: https://gitter.im/lotus/chat
|
22
|
+
|
23
|
+
## Rubies
|
24
|
+
|
25
|
+
__Lotus::Validations__ supports Ruby (MRI) 2+ and JRuby 1.7 (with 2.0 mode).
|
4
26
|
|
5
27
|
## Installation
|
6
28
|
|
@@ -20,12 +42,353 @@ Or install it yourself as:
|
|
20
42
|
|
21
43
|
## Usage
|
22
44
|
|
23
|
-
|
45
|
+
`Lotus::Validations` is a set of lightweight validations for Ruby objects.
|
46
|
+
|
47
|
+
### Attributes
|
48
|
+
|
49
|
+
The framework allows you to define attributes for each object.
|
50
|
+
|
51
|
+
It defines an initializer, whose attributes can be passed as a hash.
|
52
|
+
All unknown values are ignored, which is useful for whitelisting attributes.
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require 'lotus/validations'
|
56
|
+
|
57
|
+
class Person
|
58
|
+
include Lotus::Validations
|
59
|
+
|
60
|
+
attribute :name
|
61
|
+
end
|
62
|
+
|
63
|
+
person = Person.new(name: 'Luca', age: 32)
|
64
|
+
person.name # => "Luca"
|
65
|
+
person.age # => raises NoMethodError because `:age` wasn't defined as attribute.
|
66
|
+
```
|
67
|
+
|
68
|
+
### Coercions
|
69
|
+
|
70
|
+
If a Ruby class is passed to the `:type` option, the given value is coerced, accordingly.
|
71
|
+
|
72
|
+
#### Standard coercions
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
require 'lotus/validations'
|
76
|
+
|
77
|
+
class Person
|
78
|
+
include Lotus::Validations
|
79
|
+
|
80
|
+
attribute :fav_number, type: Integer
|
81
|
+
end
|
82
|
+
|
83
|
+
person = Person.new(fav_number: '23')
|
84
|
+
person.valid?
|
85
|
+
|
86
|
+
person.fav_number # => 23
|
87
|
+
```
|
88
|
+
|
89
|
+
Allowed types are:
|
90
|
+
|
91
|
+
* `Array`
|
92
|
+
* `BigDecimal`
|
93
|
+
* `Boolean`
|
94
|
+
* `Date`
|
95
|
+
* `DateTime`
|
96
|
+
* `Float`
|
97
|
+
* `Hash`
|
98
|
+
* `Integer`
|
99
|
+
* `Pathname`
|
100
|
+
* `Set`
|
101
|
+
* `String`
|
102
|
+
* `Symbol`
|
103
|
+
* `Time`
|
104
|
+
|
105
|
+
#### Custom coercions
|
106
|
+
|
107
|
+
If a user defined class is specified, it can be freely used for coercion purposes.
|
108
|
+
The only limitation is that the constructor should have **arity of 1**.
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
require 'lotus/validations'
|
112
|
+
|
113
|
+
class FavNumber
|
114
|
+
def initialize(number)
|
115
|
+
@number = number
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class BirthDate
|
120
|
+
end
|
121
|
+
|
122
|
+
class Person
|
123
|
+
include Lotus::Validations
|
124
|
+
|
125
|
+
attribute :fav_number, type: FavNumber
|
126
|
+
attribute :date, type: BirthDate
|
127
|
+
end
|
128
|
+
|
129
|
+
person = Person.new(fav_number: '23', date: 'Oct 23, 2014')
|
130
|
+
person.valid?
|
131
|
+
|
132
|
+
person.fav_number # => 23
|
133
|
+
person.date # => this raises an error, because BirthDate#initialize doesn't accept any arg
|
134
|
+
```
|
135
|
+
|
136
|
+
### Validations
|
137
|
+
|
138
|
+
Each attribute definition can receive a set of options to define one or more
|
139
|
+
validations.
|
140
|
+
|
141
|
+
**Validations are triggered when you invoke `#valid?`.**
|
142
|
+
|
143
|
+
#### Acceptance
|
144
|
+
|
145
|
+
An attribute is valid if it's value satisfies [Ruby's _truthiness_](http://ruby.about.com/od/control/a/Boolean-Expressions.htm).
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
require 'lotus/validations'
|
149
|
+
|
150
|
+
class Signup
|
151
|
+
include Lotus::Validations
|
152
|
+
|
153
|
+
attribute :terms_of_service, acceptance: true
|
154
|
+
end
|
155
|
+
|
156
|
+
signup = Signup.new(terms_of_service: '1')
|
157
|
+
signup.valid? # => true
|
158
|
+
|
159
|
+
signup = Signup.new(terms_of_service: '')
|
160
|
+
signup.valid? # => false
|
161
|
+
```
|
162
|
+
|
163
|
+
#### Confirmation
|
164
|
+
|
165
|
+
An attribute is valid if it's value and the value of a corresponding attribute
|
166
|
+
is valid.
|
167
|
+
|
168
|
+
By convention, if you have a `password` attribute, the validation looks for `password_validation`.
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
require 'lotus/validations'
|
172
|
+
|
173
|
+
class Signup
|
174
|
+
include Lotus::Validations
|
175
|
+
|
176
|
+
attribute :password, confirmation: true
|
177
|
+
end
|
178
|
+
|
179
|
+
signup = Signup.new(password: 'secret', password_confirmation: 'secret')
|
180
|
+
signup.valid? # => true
|
181
|
+
|
182
|
+
signup = Signup.new(password: 'secret', password_confirmation: 'x')
|
183
|
+
signup.valid? # => false
|
184
|
+
```
|
185
|
+
|
186
|
+
#### Exclusion
|
187
|
+
|
188
|
+
An attribute is valid, if the value isn't excluded from the value described by
|
189
|
+
the validator.
|
190
|
+
|
191
|
+
The validator value can be anything that responds to `#include?`.
|
192
|
+
In Ruby, this includes most of the core objects: `String`, `Enumerable` (`Array`, `Hash`,
|
193
|
+
`Range`, `Set`).
|
194
|
+
|
195
|
+
See also [Inclusion](#inclusion).
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
require 'lotus/validations'
|
199
|
+
|
200
|
+
class Signup
|
201
|
+
include Lotus::Validations
|
202
|
+
|
203
|
+
attribute :music, exclusion: ['pop']
|
204
|
+
end
|
205
|
+
|
206
|
+
signup = Signup.new(music: 'rock')
|
207
|
+
signup.valid? # => true
|
208
|
+
|
209
|
+
signup = Signup.new(music: 'pop')
|
210
|
+
signup.valid? # => false
|
211
|
+
```
|
212
|
+
|
213
|
+
#### Format
|
214
|
+
|
215
|
+
An attribute is valid if it matches the given Regular Expression.
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
require 'lotus/validations'
|
219
|
+
|
220
|
+
class Signup
|
221
|
+
include Lotus::Validations
|
222
|
+
|
223
|
+
attribute :name, format: /\A[a-zA-Z]+\z/
|
224
|
+
end
|
225
|
+
|
226
|
+
signup = Signup.new(name: 'Luca')
|
227
|
+
signup.valid? # => true
|
228
|
+
|
229
|
+
signup = Signup.new(name: '23')
|
230
|
+
signup.valid? # => false
|
231
|
+
```
|
232
|
+
|
233
|
+
#### Inclusion
|
234
|
+
|
235
|
+
An attribute is valid, if the value provided is included in the validator's
|
236
|
+
value.
|
237
|
+
|
238
|
+
The validator value can be anything that responds to `#include?`.
|
239
|
+
In Ruby, this includes most of the core objects: like `String`, `Enumerable` (`Array`, `Hash`,
|
240
|
+
`Range`, `Set`).
|
241
|
+
|
242
|
+
See also [Exclusion](#exclusion).
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
require 'prime'
|
246
|
+
require 'lotus/validations'
|
247
|
+
|
248
|
+
class PrimeNumbers
|
249
|
+
def initialize(limit)
|
250
|
+
@numbers = Prime.each(limit).to_a
|
251
|
+
end
|
252
|
+
|
253
|
+
def include?(number)
|
254
|
+
@numbers.include?(number)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
class Signup
|
259
|
+
include Lotus::Validations
|
260
|
+
|
261
|
+
attribute :age, inclusion: 18..99
|
262
|
+
attribute :fav_number, inclusion: PrimeNumbers.new(100)
|
263
|
+
end
|
264
|
+
|
265
|
+
signup = Signup.new(age: 32)
|
266
|
+
signup.valid? # => true
|
267
|
+
|
268
|
+
signup = Signup.new(age: 17)
|
269
|
+
signup.valid? # => false
|
270
|
+
|
271
|
+
signup = Signup.new(fav_number: 23)
|
272
|
+
signup.valid? # => true
|
273
|
+
|
274
|
+
signup = Signup.new(fav_number: 8)
|
275
|
+
signup.valid? # => false
|
276
|
+
```
|
277
|
+
|
278
|
+
#### Presence
|
279
|
+
|
280
|
+
An attribute is valid if present.
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
require 'lotus/validations'
|
284
|
+
|
285
|
+
class Signup
|
286
|
+
include Lotus::Validations
|
287
|
+
|
288
|
+
attribute :name, presence: true
|
289
|
+
end
|
290
|
+
|
291
|
+
signup = Signup.new(name: 'Luca')
|
292
|
+
signup.valid? # => true
|
293
|
+
|
294
|
+
signup = Signup.new(name: '')
|
295
|
+
signup.valid? # => false
|
296
|
+
|
297
|
+
signup = Signup.new(name: nil)
|
298
|
+
signup.valid? # => false
|
299
|
+
```
|
300
|
+
|
301
|
+
#### Size
|
302
|
+
|
303
|
+
An attribute is valid if it's `#size` falls within the described value.
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
require 'lotus/validations'
|
307
|
+
|
308
|
+
class Signup
|
309
|
+
MEGABYTE = 1024 ** 2
|
310
|
+
include Lotus::Validations
|
311
|
+
|
312
|
+
attribute :ssn, size: 11 # exact match
|
313
|
+
attribute :password, size: 8..64 # range
|
314
|
+
attribute :avatar, size 1..(5 * MEGABYTE)
|
315
|
+
end
|
316
|
+
|
317
|
+
signup = Signup.new(password: 'a-very-long-password')
|
318
|
+
signup.valid? # => true
|
319
|
+
|
320
|
+
signup = Signup.new(password: 'short')
|
321
|
+
signup.valid? # => false
|
322
|
+
```
|
323
|
+
|
324
|
+
**Note that in the example above you are able to validate the weight of the file,
|
325
|
+
because Ruby's `File` and `Tempfile` both respond to `#size`.**
|
326
|
+
|
327
|
+
#### Uniqueness
|
328
|
+
|
329
|
+
Uniqueness validations aren't implemented because this library doesn't deal with persistence.
|
330
|
+
The other reason is that this isn't an effective way to ensure uniqueness of a value in a database.
|
331
|
+
|
332
|
+
Please read more at: [The Perils of Uniqueness Validations](http://robots.thoughtbot.com/the-perils-of-uniqueness-validations).
|
333
|
+
|
334
|
+
### Complete example
|
335
|
+
|
336
|
+
```ruby
|
337
|
+
require 'lotus/validations'
|
338
|
+
|
339
|
+
class Signup
|
340
|
+
include Lotus::Validations
|
341
|
+
|
342
|
+
attribute :first_name, presence: true
|
343
|
+
attribute :last_name, presence: true
|
344
|
+
attribute :email, presence: true, format: /\A(.*)@(.*)\.(.*)\z/
|
345
|
+
attribute :password, presence: true, confirmation: true, size: 8..64
|
346
|
+
end
|
347
|
+
```
|
348
|
+
|
349
|
+
### Errors
|
350
|
+
|
351
|
+
When you invoke `#valid?`, validations errors are available at `#errors`.
|
352
|
+
It's a set of errors grouped by attribute. Each error contains the name of the
|
353
|
+
invalid attribute, the failed validation, the expected value and the current one.
|
354
|
+
|
355
|
+
```ruby
|
356
|
+
require 'lotus/validations'
|
357
|
+
|
358
|
+
class Signup
|
359
|
+
include Lotus::Validations
|
360
|
+
|
361
|
+
attribute :email, presence: true, format: /\A(.*)@(.*)\.(.*)\z/
|
362
|
+
attribute :age, size: 18..99
|
363
|
+
end
|
364
|
+
|
365
|
+
signup = Signup.new(email: 'user@example.org')
|
366
|
+
signup.valid? # => true
|
367
|
+
|
368
|
+
signup = Signup.new(email: '', age: 17)
|
369
|
+
signup.valid? # => false
|
370
|
+
|
371
|
+
signup.errors
|
372
|
+
# => #<Lotus::Validations::Errors:0x007fe00ced9b78
|
373
|
+
# @errors={
|
374
|
+
# :email=>[
|
375
|
+
# #<Lotus::Validations::Error:0x007fe00cee3290 @attribute=:email, @validation=:presence, @expected=true, @actual="">,
|
376
|
+
# #<Lotus::Validations::Error:0x007fe00cee31f0 @attribute=:email, @validation=:format, @expected=/\A(.*)@(.*)\.(.*)\z/, @actual="">
|
377
|
+
# ],
|
378
|
+
# :age=>[
|
379
|
+
# #<Lotus::Validations::Error:0x007fe00cee30d8 @attribute=:age, @validation=:size, @expected=18..99, @actual=17>
|
380
|
+
# ]
|
381
|
+
# }>
|
382
|
+
```
|
24
383
|
|
25
384
|
## Contributing
|
26
385
|
|
27
|
-
1. Fork it ( https://github.com/
|
386
|
+
1. Fork it ( https://github.com/lotus/lotus-validations/fork )
|
28
387
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
388
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
389
|
4. Push to the branch (`git push origin my-new-feature`)
|
31
390
|
5. Create a new Pull Request
|
391
|
+
|
392
|
+
## Copyright
|
393
|
+
|
394
|
+
Copyright 2014 Luca Guidi – Released under MIT License
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'lotus/validations'
|
data/lib/lotus/validations.rb
CHANGED
@@ -1,7 +1,398 @@
|
|
1
|
-
require
|
1
|
+
require 'lotus/validations/version'
|
2
|
+
require 'lotus/validations/errors'
|
3
|
+
require 'lotus/validations/attribute_validator'
|
2
4
|
|
3
5
|
module Lotus
|
6
|
+
# Lotus::Validations is a set of lightweight validations for Ruby objects.
|
7
|
+
#
|
8
|
+
# @since 0.1.0
|
4
9
|
module Validations
|
5
|
-
|
10
|
+
|
11
|
+
# Override Ruby's hook for modules.
|
12
|
+
#
|
13
|
+
# @param base [Class] the target action
|
14
|
+
#
|
15
|
+
# @since 0.1.0
|
16
|
+
# @api private
|
17
|
+
#
|
18
|
+
# @see http://www.ruby-doc.org/core/Module.html#method-i-included
|
19
|
+
def self.included(base)
|
20
|
+
base.extend ClassMethods
|
21
|
+
end
|
22
|
+
|
23
|
+
# Validations DSL
|
24
|
+
#
|
25
|
+
# @since 0.1.0
|
26
|
+
module ClassMethods
|
27
|
+
# Define an attribute
|
28
|
+
#
|
29
|
+
# @param name [#to_sym] the name of the attribute
|
30
|
+
# @param options [Hash] optional set of validations
|
31
|
+
# @option options [Class] :type the Ruby type used to coerce the value
|
32
|
+
# @option options [TrueClass,FalseClass] :acceptance requires Ruby
|
33
|
+
# thruthiness of the value
|
34
|
+
# @option options [TrueClass,FalseClass] :confirmation requires the value
|
35
|
+
# to be confirmed twice
|
36
|
+
# @option options [#include?] :exclusion requires the value NOT be
|
37
|
+
# included in the given collection
|
38
|
+
# @option options [Regexp] :format requires value to match the given
|
39
|
+
# Regexp
|
40
|
+
# @option options [#include?] :inclusion requires the value BE included in
|
41
|
+
# the given collection
|
42
|
+
# @option options [TrueClass,FalseClass] :presence requires the value be
|
43
|
+
# included in the given collection
|
44
|
+
# @option options [Numeric,Range] :size requires value's to be equal or
|
45
|
+
# included by the given validator
|
46
|
+
#
|
47
|
+
# @raise [ArgumentError] if an unknown or mispelled validation is given
|
48
|
+
#
|
49
|
+
# @example Attributes
|
50
|
+
# require 'lotus/validations'
|
51
|
+
#
|
52
|
+
# class Person
|
53
|
+
# include Lotus::Validations
|
54
|
+
#
|
55
|
+
# attribute :name
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# person = Person.new(name: 'Luca', age: 32)
|
59
|
+
# person.name # => "Luca"
|
60
|
+
# person.age # => raises NoMethodError because `:age` wasn't defined as attribute.
|
61
|
+
#
|
62
|
+
# @example Standard coercions
|
63
|
+
# require 'lotus/validations'
|
64
|
+
#
|
65
|
+
# class Person
|
66
|
+
# include Lotus::Validations
|
67
|
+
#
|
68
|
+
# attribute :fav_number, type: Integer
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# person = Person.new(fav_number: '23')
|
72
|
+
# person.valid?
|
73
|
+
#
|
74
|
+
# person.fav_number # => 23
|
75
|
+
#
|
76
|
+
# @example Custom coercions
|
77
|
+
# require 'lotus/validations'
|
78
|
+
#
|
79
|
+
# class FavNumber
|
80
|
+
# def initialize(number)
|
81
|
+
# @number = number
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# class BirthDate
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# class Person
|
89
|
+
# include Lotus::Validations
|
90
|
+
#
|
91
|
+
# attribute :fav_number, type: FavNumber
|
92
|
+
# attribute :date, type: BirthDate
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# person = Person.new(fav_number: '23', date: 'Oct 23, 2014')
|
96
|
+
# person.valid?
|
97
|
+
#
|
98
|
+
# person.fav_number # => 23
|
99
|
+
# person.date # => this raises an error, because BirthDate#initialize doesn't accept any arg
|
100
|
+
#
|
101
|
+
# @example Acceptance
|
102
|
+
# require 'lotus/validations'
|
103
|
+
#
|
104
|
+
# class Signup
|
105
|
+
# include Lotus::Validations
|
106
|
+
#
|
107
|
+
# attribute :terms_of_service, acceptance: true
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# signup = Signup.new(terms_of_service: '1')
|
111
|
+
# signup.valid? # => true
|
112
|
+
#
|
113
|
+
# signup = Signup.new(terms_of_service: '')
|
114
|
+
# signup.valid? # => false
|
115
|
+
#
|
116
|
+
# @example Confirmation
|
117
|
+
# require 'lotus/validations'
|
118
|
+
#
|
119
|
+
# class Signup
|
120
|
+
# include Lotus::Validations
|
121
|
+
#
|
122
|
+
# attribute :password, confirmation: true
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# signup = Signup.new(password: 'secret', password_confirmation: 'secret')
|
126
|
+
# signup.valid? # => true
|
127
|
+
#
|
128
|
+
# signup = Signup.new(password: 'secret', password_confirmation: 'x')
|
129
|
+
# signup.valid? # => false
|
130
|
+
#
|
131
|
+
# @example Exclusion
|
132
|
+
# require 'lotus/validations'
|
133
|
+
#
|
134
|
+
# class Signup
|
135
|
+
# include Lotus::Validations
|
136
|
+
#
|
137
|
+
# attribute :music, exclusion: ['pop']
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# signup = Signup.new(music: 'rock')
|
141
|
+
# signup.valid? # => true
|
142
|
+
#
|
143
|
+
# signup = Signup.new(music: 'pop')
|
144
|
+
# signup.valid? # => false
|
145
|
+
#
|
146
|
+
# @example Format
|
147
|
+
# require 'lotus/validations'
|
148
|
+
#
|
149
|
+
# class Signup
|
150
|
+
# include Lotus::Validations
|
151
|
+
#
|
152
|
+
# attribute :name, format: /\A[a-zA-Z]+\z/
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# signup = Signup.new(name: 'Luca')
|
156
|
+
# signup.valid? # => true
|
157
|
+
#
|
158
|
+
# signup = Signup.new(name: '23')
|
159
|
+
# signup.valid? # => false
|
160
|
+
#
|
161
|
+
# @example Inclusion
|
162
|
+
# require 'lotus/validations'
|
163
|
+
#
|
164
|
+
# class Signup
|
165
|
+
# include Lotus::Validations
|
166
|
+
#
|
167
|
+
# attribute :age, inclusion: 18..99
|
168
|
+
# end
|
169
|
+
#
|
170
|
+
# signup = Signup.new(age: 32)
|
171
|
+
# signup.valid? # => true
|
172
|
+
#
|
173
|
+
# signup = Signup.new(age: 17)
|
174
|
+
# signup.valid? # => false
|
175
|
+
#
|
176
|
+
# @example Presence
|
177
|
+
# require 'lotus/validations'
|
178
|
+
#
|
179
|
+
# class Signup
|
180
|
+
# include Lotus::Validations
|
181
|
+
#
|
182
|
+
# attribute :name, presence: true
|
183
|
+
# end
|
184
|
+
#
|
185
|
+
# signup = Signup.new(name: 'Luca')
|
186
|
+
# signup.valid? # => true
|
187
|
+
#
|
188
|
+
# signup = Signup.new(name: nil)
|
189
|
+
# signup.valid? # => false
|
190
|
+
#
|
191
|
+
# @example Size
|
192
|
+
# require 'lotus/validations'
|
193
|
+
#
|
194
|
+
# class Signup
|
195
|
+
# MEGABYTE = 1024 ** 2
|
196
|
+
# include Lotus::Validations
|
197
|
+
#
|
198
|
+
# attribute :ssn, size: 11 # exact match
|
199
|
+
# attribute :password, size: 8..64 # range
|
200
|
+
# attribute :avatar, size 1..(5 * MEGABYTE)
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# signup = Signup.new(password: 'a-very-long-password')
|
204
|
+
# signup.valid? # => true
|
205
|
+
#
|
206
|
+
# signup = Signup.new(password: 'short')
|
207
|
+
# signup.valid? # => false
|
208
|
+
def attribute(name, options = {})
|
209
|
+
attributes[name.to_sym] = validate_options!(name, options)
|
210
|
+
|
211
|
+
class_eval %{
|
212
|
+
def #{ name }
|
213
|
+
@attributes[:#{ name }]
|
214
|
+
end
|
215
|
+
}
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
# Set of user defined attributes
|
220
|
+
#
|
221
|
+
# @return [Hash]
|
222
|
+
#
|
223
|
+
# @since 0.1.0
|
224
|
+
# @api private
|
225
|
+
def attributes
|
226
|
+
@attributes ||= Hash.new
|
227
|
+
end
|
228
|
+
|
229
|
+
# Checks at the loading time if the user defined validations are recognized
|
230
|
+
#
|
231
|
+
# @param name [Symbol] the attribute name
|
232
|
+
# @param options [Hash] the set of validations associated with the given attribute
|
233
|
+
#
|
234
|
+
# @raise [ArgumentError] if at least one of the validations are not
|
235
|
+
# recognized
|
236
|
+
#
|
237
|
+
# @since 0.1.0
|
238
|
+
# @api private
|
239
|
+
def validate_options!(name, options)
|
240
|
+
if (unknown = (options.keys - validations)) && unknown.any?
|
241
|
+
raise ArgumentError.new(%(Unknown validation(s): #{ unknown.join ', ' } for "#{ name }" attribute))
|
242
|
+
end
|
243
|
+
|
244
|
+
options
|
245
|
+
end
|
246
|
+
|
247
|
+
# Names of the implemented validations
|
248
|
+
#
|
249
|
+
# @return [Array]
|
250
|
+
#
|
251
|
+
# @since 0.1.0
|
252
|
+
# @api private
|
253
|
+
def validations
|
254
|
+
[:presence, :acceptance, :format, :inclusion, :exclusion, :confirmation, :size, :type]
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Validation errors
|
259
|
+
#
|
260
|
+
# @return [Lotus::Validations::Errors] the set of validation errors
|
261
|
+
#
|
262
|
+
# @since 0.1.0
|
263
|
+
#
|
264
|
+
# @see Lotus::Validations::Errors
|
265
|
+
#
|
266
|
+
# @example Valid attributes
|
267
|
+
# require 'lotus/validations'
|
268
|
+
#
|
269
|
+
# class Signup
|
270
|
+
# include Lotus::Validations
|
271
|
+
#
|
272
|
+
# attribute :email, presence: true, format: /\A(.*)@(.*)\.(.*)\z/
|
273
|
+
# end
|
274
|
+
#
|
275
|
+
# signup = Signup.new(email: 'user@example.org')
|
276
|
+
# signup.valid? # => true
|
277
|
+
#
|
278
|
+
# signup.errors
|
279
|
+
# # => #<Lotus::Validations::Errors:0x007fd594ba9228 @errors={}>
|
280
|
+
#
|
281
|
+
# @example Invalid attributes
|
282
|
+
# require 'lotus/validations'
|
283
|
+
#
|
284
|
+
# class Signup
|
285
|
+
# include Lotus::Validations
|
286
|
+
#
|
287
|
+
# attribute :email, presence: true, format: /\A(.*)@(.*)\.(.*)\z/
|
288
|
+
# attribute :age, size: 18..99
|
289
|
+
# end
|
290
|
+
#
|
291
|
+
# signup = Signup.new(email: '', age: 17)
|
292
|
+
# signup.valid? # => false
|
293
|
+
#
|
294
|
+
# signup.errors
|
295
|
+
# # => #<Lotus::Validations::Errors:0x007fe00ced9b78
|
296
|
+
# # @errors={
|
297
|
+
# # :email=>[
|
298
|
+
# # #<Lotus::Validations::Error:0x007fe00cee3290 @attribute=:email, @validation=:presence, @expected=true, @actual="">,
|
299
|
+
# # #<Lotus::Validations::Error:0x007fe00cee31f0 @attribute=:email, @validation=:format, @expected=/\A(.*)@(.*)\.(.*)\z/, @actual="">
|
300
|
+
# # ],
|
301
|
+
# # :age=>[
|
302
|
+
# # #<Lotus::Validations::Error:0x007fe00cee30d8 @attribute=:age, @validation=:size, @expected=18..99, @actual=17>
|
303
|
+
# # ]
|
304
|
+
# # }>
|
305
|
+
attr_reader :errors
|
306
|
+
|
307
|
+
# Create a new instance with the given attributes
|
308
|
+
#
|
309
|
+
# @param attributes [#to_h] an Hash like object which contains the
|
310
|
+
# attributes
|
311
|
+
#
|
312
|
+
# @since 0.1.0
|
313
|
+
#
|
314
|
+
# @example Initialize with Hash
|
315
|
+
# require 'lotus/validations'
|
316
|
+
#
|
317
|
+
# class Signup
|
318
|
+
# include Lotus::Validations
|
319
|
+
#
|
320
|
+
# attribute :name
|
321
|
+
# end
|
322
|
+
#
|
323
|
+
# signup = Signup.new(name: 'Luca')
|
324
|
+
#
|
325
|
+
# @example Initialize with Hash like
|
326
|
+
# require 'lotus/validations'
|
327
|
+
#
|
328
|
+
# class Params
|
329
|
+
# def initialize(attributes)
|
330
|
+
# @attributes = Hash[*attributes]
|
331
|
+
# end
|
332
|
+
#
|
333
|
+
# def to_h
|
334
|
+
# @attributes.to_h
|
335
|
+
# end
|
336
|
+
# end
|
337
|
+
#
|
338
|
+
# class Signup
|
339
|
+
# include Lotus::Validations
|
340
|
+
#
|
341
|
+
# attribute :name
|
342
|
+
# end
|
343
|
+
#
|
344
|
+
# params = Params.new([:name, 'Luca'])
|
345
|
+
# signup = Signup.new(params)
|
346
|
+
#
|
347
|
+
# signup.name # => "Luca"
|
348
|
+
def initialize(attributes)
|
349
|
+
@attributes = attributes.to_h
|
350
|
+
@errors = Errors.new
|
351
|
+
end
|
352
|
+
|
353
|
+
# Checks if the current data satisfies the defined validations
|
354
|
+
#
|
355
|
+
# @return [TrueClass,FalseClass] the result of the validations
|
356
|
+
#
|
357
|
+
# @since 0.1.0
|
358
|
+
def valid?
|
359
|
+
@errors.clear
|
360
|
+
|
361
|
+
_attributes.each do |name, options|
|
362
|
+
AttributeValidator.new(self, name, options).validate!
|
363
|
+
end
|
364
|
+
|
365
|
+
@errors.empty?
|
366
|
+
end
|
367
|
+
|
368
|
+
protected
|
369
|
+
# Returns the attributes passed at the initialize time
|
370
|
+
#
|
371
|
+
# @return [Hash] attributes
|
372
|
+
#
|
373
|
+
# @since 0.1.0
|
374
|
+
# @api private
|
375
|
+
#
|
376
|
+
# @example
|
377
|
+
# require 'lotus/validations'
|
378
|
+
#
|
379
|
+
# class Signup
|
380
|
+
# include Lotus::Validations
|
381
|
+
#
|
382
|
+
# attribute :email
|
383
|
+
# end
|
384
|
+
#
|
385
|
+
# signup = Signup.new(email: 'user@example.org')
|
386
|
+
# signup.attributes # => {:email=>"user@example.org"}
|
387
|
+
attr_reader :attributes
|
388
|
+
|
389
|
+
private
|
390
|
+
# @since 0.1.0
|
391
|
+
# @api private
|
392
|
+
#
|
393
|
+
# @see Lotus::Validations::ClassMethods#attributes
|
394
|
+
def _attributes
|
395
|
+
self.class.__send__(:attributes)
|
396
|
+
end
|
6
397
|
end
|
7
398
|
end
|