hanami-validations 0.0.0 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +89 -0
- data/LICENSE.md +22 -0
- data/README.md +559 -7
- data/hanami-validations.gemspec +16 -12
- data/lib/hanami-validations.rb +1 -0
- data/lib/hanami/validations.rb +300 -2
- data/lib/hanami/validations/attribute.rb +252 -0
- data/lib/hanami/validations/attribute_definer.rb +526 -0
- data/lib/hanami/validations/blank_value_checker.rb +55 -0
- data/lib/hanami/validations/coercions.rb +31 -0
- data/lib/hanami/validations/error.rb +95 -0
- data/lib/hanami/validations/errors.rb +155 -0
- data/lib/hanami/validations/nested_attributes.rb +22 -0
- data/lib/hanami/validations/validation_set.rb +81 -0
- data/lib/hanami/validations/validator.rb +29 -0
- data/lib/hanami/validations/version.rb +2 -1
- metadata +57 -16
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/Rakefile +0 -2
- data/bin/console +0 -14
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: adeb97811a77f27c28ac940395e0fab28c0670c0
|
4
|
+
data.tar.gz: d6bc2be331bde334ff930692e1b66458b08f04c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fb1f27b1db7511d4b2cbb926acba8650596615c171901d27c2dcc5c5c5d6ed55ff0edb41c47d267441b98c07780ec90eb900ebc3d4240c78e55f05f07762eeb
|
7
|
+
data.tar.gz: 517a5991fae90f9c8b83fe0dbf96ad9e86ff93ac0898c9b8fc296da16547a78d9fbb7d8341772fc9e32bdc4454f507cf59a382e9c386202b0c5485fa4c15468e
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# Hanami::Validations
|
2
|
+
Validations mixin for Ruby objects
|
3
|
+
|
4
|
+
## v0.5.0 - 2016-01-22
|
5
|
+
### Changed
|
6
|
+
- [Luca Guidi] Renamed the project
|
7
|
+
|
8
|
+
## v0.4.0 - 2016-01-12
|
9
|
+
## Changed
|
10
|
+
- [Hélio Costa e Silva & Luca Guidi] Ignore blank values for format and size validation
|
11
|
+
|
12
|
+
### Fixed
|
13
|
+
- [Pascal Betz] Ensure acceptance validation to reject blank strings
|
14
|
+
|
15
|
+
## v0.3.3 - 2015-09-30
|
16
|
+
### Added
|
17
|
+
- [Luca Guidi] Official support for JRuby 9k+
|
18
|
+
|
19
|
+
## v0.3.2 - 2015-05-22
|
20
|
+
### Added
|
21
|
+
- [deepj] Introduced `Lotus::Validations#invalid?`
|
22
|
+
|
23
|
+
## v0.3.1 - 2015-05-15
|
24
|
+
### Fixed
|
25
|
+
- [Luca Guidi] Fixed Hash serialization for nested validations. It always return nested `::Hash` structure.
|
26
|
+
- [Alfonso Uceda Pompa & Dmitry Tymchuk] Fixed Hash serialization when `Lotus::Entity` is included in the same class.
|
27
|
+
|
28
|
+
## v0.3.0 - 2015-03-23
|
29
|
+
|
30
|
+
## v0.2.4 - 2015-01-30
|
31
|
+
### Added
|
32
|
+
- [Steve Hodgkiss] Introduced `Lotus::Validations::Error#attribute_name`
|
33
|
+
- [Steve Hodgkiss] Nested validations
|
34
|
+
|
35
|
+
### Changed
|
36
|
+
- [Steve Hodgkiss] `Lotus::Validations::Error#name` returns the complete attribute name (Eg. `first_name` or `address.street`)
|
37
|
+
|
38
|
+
## v0.2.3 - 2015-01-12
|
39
|
+
### Added
|
40
|
+
- [Luca Guidi] Compatibility with Lotus::Entity
|
41
|
+
|
42
|
+
### Fixed
|
43
|
+
- [Luca Guidi] Ensure `.validates` usage to not raise `ArgumentError` when `:type` option is passed
|
44
|
+
- [Luca Guidi] Ensure to assign attributes when only `.validates` is used
|
45
|
+
|
46
|
+
## v0.2.2 - 2015-01-08
|
47
|
+
### Added
|
48
|
+
- [Steve Hodgkiss] Introduced `Validations.validates`. It defines validations, for already existing attributes.
|
49
|
+
|
50
|
+
## v0.2.1 - 2014-12-23
|
51
|
+
### Added
|
52
|
+
- [Luca Guidi] Introduced `Validations::Errors#to_h` and `to_a`
|
53
|
+
- [Luca Guidi] Introduced `Validations::Errors#any?`
|
54
|
+
- [Luca Guidi] Official support for Ruby 2.2
|
55
|
+
|
56
|
+
### Fixed
|
57
|
+
- [Satoshi Amemiya] Made `Validations#valid?` idempotent
|
58
|
+
|
59
|
+
## v0.2.0 - 2014-11-23
|
60
|
+
### Added
|
61
|
+
- [Luca Guidi] Skip attribute whitelisting when a validator does not define any attribute
|
62
|
+
- [Luca Guidi] Official support for Rubinius 2.3+
|
63
|
+
- [Luca Guidi] Implemented `#each` in order to allow bulk operations on attributes
|
64
|
+
- [Luca Guidi] Implemented `#to_h` to make validations usable by other libraries
|
65
|
+
- [Luca Guidi] Made `#initialize` to accept Hashes with strings as keys, but only for declared attributes
|
66
|
+
- [Luca Guidi] Lazy coercions, from now on `valid?` is not required to obtain a coerced value from a single attribute
|
67
|
+
- [Rik Tonnard] Made validators reusable by allowing infinite inclusion
|
68
|
+
|
69
|
+
## v0.1.0 - 2014-10-23
|
70
|
+
### Added
|
71
|
+
- [Luca Guidi] Made `#initialize` to accept any object that implements `#to_h`
|
72
|
+
- [Luca Guidi] Custom coercions for user defined classes
|
73
|
+
- [Luca Guidi] Raise an exception at the load time when a validation is not recognized
|
74
|
+
- [Luca Guidi] Allow validators inheritance
|
75
|
+
- [Luca Guidi] Confirmation validation
|
76
|
+
- [Luca Guidi] Exclusion validation
|
77
|
+
- [Luca Guidi] Size validation
|
78
|
+
- [Luca Guidi] Acceptance validation
|
79
|
+
- [Jeremy Stephens] Inclusion validation
|
80
|
+
- [Luca Guidi] Format validation
|
81
|
+
- [Luca Guidi] Presence validation
|
82
|
+
- [Luca Guidi] Coercions
|
83
|
+
- [Luca Guidi] Basic module inclusion
|
84
|
+
- [Luca Guidi] Official support for JRuby 1.7+ (with 2.0 mode)
|
85
|
+
- [Luca Guidi] Official support for MRI 2.0+
|
86
|
+
|
87
|
+
### Fixed
|
88
|
+
- [Jeremy Stephens] Ensure to not fail validations when coerce falsey values
|
89
|
+
- [Luca Guidi] Ensure `Lotus::Validations#valid?` to be idempotent
|
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright © 2014-2016 Luca Guidi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,8 +1,28 @@
|
|
1
1
|
# Hanami::Validations
|
2
2
|
|
3
|
-
|
3
|
+
Validations mixins for objects
|
4
4
|
|
5
|
-
|
5
|
+
## Status
|
6
|
+
|
7
|
+
[](https://badge.fury.io/rb/hanami-validations)
|
8
|
+
[](https://travis-ci.org/hanami/validations?branch=master)
|
9
|
+
[](https://coveralls.io/r/hanami/validations)
|
10
|
+
[](https://codeclimate.com/github/hanami/validations)
|
11
|
+
[](https://gemnasium.com/hanami/validations)
|
12
|
+
[](http://inch-ci.org/github/hanami/validations)
|
13
|
+
|
14
|
+
## Contact
|
15
|
+
|
16
|
+
* Home page: http://hanamirb.org
|
17
|
+
* Mailing List: http://hanamirb.org/mailing-list
|
18
|
+
* API Doc: http://rdoc.info/gems/hanami-validations
|
19
|
+
* Bugs/Issues: https://github.com/hanami/validations/issues
|
20
|
+
* Support: http://stackoverflow.com/questions/tagged/hanami
|
21
|
+
* Chat: http://chat.hanamirb.org
|
22
|
+
|
23
|
+
## Rubies
|
24
|
+
|
25
|
+
__Hanami::Validations__ supports Ruby (MRI) 2+, JRuby 9k+
|
6
26
|
|
7
27
|
## Installation
|
8
28
|
|
@@ -22,15 +42,547 @@ Or install it yourself as:
|
|
22
42
|
|
23
43
|
## Usage
|
24
44
|
|
25
|
-
|
45
|
+
`Hanami::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 'hanami/validations'
|
56
|
+
|
57
|
+
class Person
|
58
|
+
include Hanami::Validations
|
59
|
+
|
60
|
+
attribute :name, presence: true
|
61
|
+
attribute :email, presence: true
|
62
|
+
end
|
63
|
+
|
64
|
+
person = Person.new(name: 'Luca', email: 'me@example.org', age: 32)
|
65
|
+
person.name # => "Luca"
|
66
|
+
person.email # => "me@example.org"
|
67
|
+
person.age # => raises NoMethodError because `:age` wasn't defined as attribute.
|
68
|
+
```
|
69
|
+
|
70
|
+
#### Blank Values
|
71
|
+
|
72
|
+
The framework will treat as valid any blank attributes, __without__ `presence`, for both `format` and `size` predicates.
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
require 'hanami/validations'
|
76
|
+
|
77
|
+
class Person
|
78
|
+
include Hanami::Validations
|
79
|
+
|
80
|
+
attribute :name, type: String, size: 5..45
|
81
|
+
attribute :email, type: String, size: 20..80, format: /@/
|
82
|
+
attribute :skills, type: Array, size: 1..3
|
83
|
+
attribute :keys, type: Hash, size: 1..3
|
84
|
+
end
|
85
|
+
|
86
|
+
Person.new.valid? # < true
|
87
|
+
Person.new(name: '').valid? # < true
|
88
|
+
Person.new(skills: '').valid? # < true
|
89
|
+
Person.new(skills: ['ruby', 'hanami']).valid? # < true
|
90
|
+
|
91
|
+
Person.new(skills: []).valid? # < false
|
92
|
+
Person.new(keys: {}).valid? # < false
|
93
|
+
Person.new(keys: {a: :b}, skills: []).valid? # < false
|
94
|
+
```
|
95
|
+
|
96
|
+
If you want to _disable_ this behaviour, please, refer to [presence](https://github.com/hanami/validations#presence).
|
97
|
+
|
98
|
+
### Validations
|
99
|
+
|
100
|
+
If you prefer Hanami::Validations to **only define validations**, but **not attributes**,
|
101
|
+
you can use the following alternative syntax.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
require 'hanami/validations'
|
105
|
+
|
106
|
+
class Person
|
107
|
+
include Hanami::Validations
|
108
|
+
attr_accessor :name, :email
|
109
|
+
|
110
|
+
# Custom initializer
|
111
|
+
def initialize(attributes = {})
|
112
|
+
@name, @email = attributes.values_at(:name, :email)
|
113
|
+
end
|
114
|
+
|
115
|
+
validates :name, presence: true
|
116
|
+
validates :email, presence: true
|
117
|
+
end
|
118
|
+
|
119
|
+
person = Person.new(name: 'Luca', email: 'me@example.org')
|
120
|
+
person.name # => "Luca"
|
121
|
+
person.email # => "me@example.org"
|
122
|
+
```
|
123
|
+
|
124
|
+
This is a bit more verbose, but offers a great level of flexibility for your
|
125
|
+
Ruby objects. It also allows to use Hanami::Validations in combination with
|
126
|
+
**other frameworks**.
|
127
|
+
|
128
|
+
### Coercions
|
129
|
+
|
130
|
+
If a Ruby class is passed to the `:type` option, the given value is coerced, accordingly.
|
131
|
+
|
132
|
+
#### Standard coercions
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
require 'hanami/validations'
|
136
|
+
|
137
|
+
class Person
|
138
|
+
include Hanami::Validations
|
139
|
+
|
140
|
+
attribute :fav_number, type: Integer
|
141
|
+
end
|
142
|
+
|
143
|
+
person = Person.new(fav_number: '23')
|
144
|
+
person.valid?
|
145
|
+
|
146
|
+
person.fav_number # => 23
|
147
|
+
```
|
148
|
+
|
149
|
+
Allowed types are:
|
150
|
+
|
151
|
+
* `Array`
|
152
|
+
* `BigDecimal`
|
153
|
+
* `Boolean`
|
154
|
+
* `Date`
|
155
|
+
* `DateTime`
|
156
|
+
* `Float`
|
157
|
+
* `Hash`
|
158
|
+
* `Integer`
|
159
|
+
* `Pathname`
|
160
|
+
* `Set`
|
161
|
+
* `String`
|
162
|
+
* `Symbol`
|
163
|
+
* `Time`
|
164
|
+
|
165
|
+
#### Custom coercions
|
166
|
+
|
167
|
+
If a user defined class is specified, it can be freely used for coercion purposes.
|
168
|
+
The only limitation is that the constructor should have **arity of 1**.
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
require 'hanami/validations'
|
172
|
+
|
173
|
+
class FavNumber
|
174
|
+
def initialize(number)
|
175
|
+
@number = number
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class BirthDate
|
180
|
+
end
|
181
|
+
|
182
|
+
class Person
|
183
|
+
include Hanami::Validations
|
184
|
+
|
185
|
+
attribute :fav_number, type: FavNumber
|
186
|
+
attribute :date, type: BirthDate
|
187
|
+
end
|
188
|
+
|
189
|
+
person = Person.new(fav_number: '23', date: 'Oct 23, 2014')
|
190
|
+
person.valid?
|
191
|
+
|
192
|
+
person.fav_number # => #<FavNumber:0x007ffc644bba00 @number="23">
|
193
|
+
person.date # => this raises an error, because BirthDate#initialize doesn't accept any arg
|
194
|
+
```
|
195
|
+
|
196
|
+
### Validations
|
197
|
+
|
198
|
+
Each attribute definition can receive a set of options to define one or more
|
199
|
+
validations.
|
200
|
+
|
201
|
+
**Validations are triggered when you invoke `#valid?`.**
|
202
|
+
|
203
|
+
#### Acceptance
|
204
|
+
|
205
|
+
An attribute is valid if its value is _truthy_.
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
require 'hanami/validations'
|
209
|
+
|
210
|
+
class Signup
|
211
|
+
include Hanami::Validations
|
212
|
+
|
213
|
+
attribute :terms_of_service, acceptance: true
|
214
|
+
end
|
215
|
+
|
216
|
+
signup = Signup.new(terms_of_service: '1')
|
217
|
+
signup.valid? # => true
|
218
|
+
|
219
|
+
signup = Signup.new(terms_of_service: 'true')
|
220
|
+
signup.valid? # => true
|
221
|
+
|
222
|
+
signup = Signup.new(terms_of_service: '')
|
223
|
+
signup.valid? # => false
|
224
|
+
|
225
|
+
signup = Signup.new(terms_of_service: '0')
|
226
|
+
signup.valid? # => false
|
227
|
+
```
|
228
|
+
|
229
|
+
#### Confirmation
|
230
|
+
|
231
|
+
An attribute is valid if its value and the value of a corresponding attribute
|
232
|
+
is valid.
|
233
|
+
|
234
|
+
By convention, if you have a `password` attribute, the validation looks for `password_confirmation`.
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
require 'hanami/validations'
|
238
|
+
|
239
|
+
class Signup
|
240
|
+
include Hanami::Validations
|
241
|
+
|
242
|
+
attribute :password, confirmation: true
|
243
|
+
end
|
244
|
+
|
245
|
+
signup = Signup.new(password: 'secret', password_confirmation: 'secret')
|
246
|
+
signup.valid? # => true
|
247
|
+
|
248
|
+
signup = Signup.new(password: 'secret', password_confirmation: 'x')
|
249
|
+
signup.valid? # => false
|
250
|
+
```
|
251
|
+
|
252
|
+
#### Exclusion
|
253
|
+
|
254
|
+
An attribute is valid, if the value isn't excluded from the value described by
|
255
|
+
the validator.
|
256
|
+
|
257
|
+
The validator value can be anything that responds to `#include?`.
|
258
|
+
In Ruby, this includes most of the core objects: `String`, `Enumerable` (`Array`, `Hash`,
|
259
|
+
`Range`, `Set`).
|
260
|
+
|
261
|
+
See also [Inclusion](#inclusion).
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
require 'hanami/validations'
|
265
|
+
|
266
|
+
class Signup
|
267
|
+
include Hanami::Validations
|
268
|
+
|
269
|
+
attribute :music, exclusion: ['pop']
|
270
|
+
end
|
271
|
+
|
272
|
+
signup = Signup.new(music: 'rock')
|
273
|
+
signup.valid? # => true
|
274
|
+
|
275
|
+
signup = Signup.new(music: 'pop')
|
276
|
+
signup.valid? # => false
|
277
|
+
```
|
278
|
+
|
279
|
+
#### Format
|
280
|
+
|
281
|
+
An attribute is valid if it matches the given Regular Expression.
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
require 'hanami/validations'
|
285
|
+
|
286
|
+
class Signup
|
287
|
+
include Hanami::Validations
|
288
|
+
|
289
|
+
attribute :name, format: /\A[a-zA-Z]+\z/
|
290
|
+
end
|
26
291
|
|
27
|
-
|
292
|
+
signup = Signup.new(name: 'Luca')
|
293
|
+
signup.valid? # => true
|
28
294
|
|
29
|
-
|
295
|
+
signup = Signup.new(name: '23')
|
296
|
+
signup.valid? # => false
|
297
|
+
```
|
298
|
+
|
299
|
+
#### Inclusion
|
300
|
+
|
301
|
+
An attribute is valid, if the value provided is included in the validator's
|
302
|
+
value.
|
303
|
+
|
304
|
+
The validator value can be anything that responds to `#include?`.
|
305
|
+
In Ruby, this includes most of the core objects: like `String`, `Enumerable` (`Array`, `Hash`,
|
306
|
+
`Range`, `Set`).
|
307
|
+
|
308
|
+
See also [Exclusion](#exclusion).
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
require 'prime'
|
312
|
+
require 'hanami/validations'
|
313
|
+
|
314
|
+
class PrimeNumbers
|
315
|
+
def initialize(limit)
|
316
|
+
@numbers = Prime.each(limit).to_a
|
317
|
+
end
|
318
|
+
|
319
|
+
def include?(number)
|
320
|
+
@numbers.include?(number)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
class Signup
|
325
|
+
include Hanami::Validations
|
326
|
+
|
327
|
+
attribute :age, inclusion: 18..99
|
328
|
+
attribute :fav_number, inclusion: PrimeNumbers.new(100)
|
329
|
+
end
|
330
|
+
|
331
|
+
signup = Signup.new(age: 32)
|
332
|
+
signup.valid? # => true
|
333
|
+
|
334
|
+
signup = Signup.new(age: 17)
|
335
|
+
signup.valid? # => false
|
336
|
+
|
337
|
+
signup = Signup.new(fav_number: 23)
|
338
|
+
signup.valid? # => true
|
339
|
+
|
340
|
+
signup = Signup.new(fav_number: 8)
|
341
|
+
signup.valid? # => false
|
342
|
+
```
|
343
|
+
|
344
|
+
#### Presence
|
345
|
+
|
346
|
+
An attribute is valid if present.
|
347
|
+
|
348
|
+
```ruby
|
349
|
+
require 'hanami/validations'
|
350
|
+
|
351
|
+
class Signup
|
352
|
+
include Hanami::Validations
|
353
|
+
|
354
|
+
attribute :name, presence: true
|
355
|
+
end
|
356
|
+
|
357
|
+
signup = Signup.new(name: 'Luca')
|
358
|
+
signup.valid? # => true
|
359
|
+
|
360
|
+
signup = Signup.new(name: '')
|
361
|
+
signup.valid? # => false
|
362
|
+
|
363
|
+
signup = Signup.new(name: nil)
|
364
|
+
signup.valid? # => false
|
365
|
+
```
|
366
|
+
|
367
|
+
#### Size
|
368
|
+
|
369
|
+
An attribute is valid if its `#size` falls within the described value.
|
370
|
+
|
371
|
+
```ruby
|
372
|
+
require 'hanami/validations'
|
373
|
+
|
374
|
+
class Signup
|
375
|
+
MEGABYTE = 1024 ** 2
|
376
|
+
include Hanami::Validations
|
377
|
+
|
378
|
+
attribute :ssn, size: 11 # exact match
|
379
|
+
attribute :password, size: 8..64 # range
|
380
|
+
attribute :avatar, size: 1..(5 * MEGABYTE)
|
381
|
+
end
|
382
|
+
|
383
|
+
signup = Signup.new(password: 'a-very-long-password')
|
384
|
+
signup.valid? # => true
|
385
|
+
|
386
|
+
signup = Signup.new(password: 'short')
|
387
|
+
signup.valid? # => false
|
388
|
+
```
|
389
|
+
|
390
|
+
**Note that in the example above you are able to validate the weight of the file,
|
391
|
+
because Ruby's `File` and `Tempfile` both respond to `#size`.**
|
392
|
+
|
393
|
+
#### Uniqueness
|
394
|
+
|
395
|
+
Uniqueness validations aren't implemented because this library doesn't deal with persistence.
|
396
|
+
The other reason is that this isn't an effective way to ensure uniqueness of a value in a database.
|
397
|
+
|
398
|
+
Please read more at: [The Perils of Uniqueness Validations](http://robots.thoughtbot.com/the-perils-of-uniqueness-validations).
|
399
|
+
|
400
|
+
### Nested validations
|
401
|
+
|
402
|
+
Nested validations are handled with a nested block syntax.
|
403
|
+
|
404
|
+
```ruby
|
405
|
+
class ShippingDetails
|
406
|
+
include Hanami::Validations
|
407
|
+
|
408
|
+
attribute :full_name, presence: true
|
409
|
+
|
410
|
+
attribute :address do
|
411
|
+
attribute :street, presence: true
|
412
|
+
attribute :city, presence: true
|
413
|
+
attribute :country, presence: true
|
414
|
+
attribute :postal_code, presence: true, format: /.../
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
validator = ShippingDetails.new
|
419
|
+
validator.valid? # => false
|
420
|
+
```
|
421
|
+
|
422
|
+
Bulk operations on errors are guaranteed by `#each`.
|
423
|
+
This method yields a **flattened collection of errors**.
|
424
|
+
|
425
|
+
```ruby
|
426
|
+
validator.errors.each do |error|
|
427
|
+
error.name
|
428
|
+
# => on the first iteration it returns "full_name"
|
429
|
+
# => the second time it returns "address.street" and so on..
|
430
|
+
end
|
431
|
+
```
|
432
|
+
|
433
|
+
Errors for a specific attribute can be accessed via `#for`.
|
434
|
+
|
435
|
+
```ruby
|
436
|
+
error = validator.errors.for('full_name').first
|
437
|
+
error.name # => "full_name"
|
438
|
+
error.attribute_name # => "full_name"
|
439
|
+
|
440
|
+
error = validator.errors.for('address.street').first
|
441
|
+
error.name # => "address.street"
|
442
|
+
error.attribute_name # => "street"
|
443
|
+
```
|
444
|
+
|
445
|
+
### Composable validations
|
446
|
+
|
447
|
+
Validations can be reused via composition:
|
448
|
+
|
449
|
+
```ruby
|
450
|
+
require 'hanami/validations'
|
451
|
+
|
452
|
+
module NameValidations
|
453
|
+
include Hanami::Validations
|
454
|
+
|
455
|
+
attribute :name, presence: true
|
456
|
+
end
|
30
457
|
|
31
|
-
|
458
|
+
module EmailValidations
|
459
|
+
include Hanami::Validations
|
460
|
+
|
461
|
+
attribute :email, presence: true, format: /.../
|
462
|
+
end
|
463
|
+
|
464
|
+
module PasswordValidations
|
465
|
+
include Hanami::Validations
|
466
|
+
|
467
|
+
# We validate only the presence here
|
468
|
+
attribute :password, presence: true
|
469
|
+
end
|
470
|
+
|
471
|
+
module CommonValidations
|
472
|
+
include EmailValidations
|
473
|
+
include PasswordValidations
|
474
|
+
end
|
475
|
+
|
476
|
+
# A valid signup requires:
|
477
|
+
# * name (presence)
|
478
|
+
# * email (presence and format)
|
479
|
+
# * password (presence and confirmation)
|
480
|
+
class Signup
|
481
|
+
include NameValidations
|
482
|
+
include CommonValidations
|
483
|
+
|
484
|
+
# We decorate PasswordValidations behavior, by requiring the confirmation too.
|
485
|
+
# This additional validation is active only in this case.
|
486
|
+
attribute :password, confirmation: true
|
487
|
+
end
|
488
|
+
|
489
|
+
# A valid signin requires:
|
490
|
+
# * email (presence)
|
491
|
+
# * password (presence)
|
492
|
+
class Signin
|
493
|
+
include CommonValidations
|
494
|
+
end
|
495
|
+
|
496
|
+
# A valid "forgot password" requires:
|
497
|
+
# * email (presence)
|
498
|
+
class ForgotPassword
|
499
|
+
include EmailValidations
|
500
|
+
end
|
501
|
+
```
|
502
|
+
|
503
|
+
### Complete example
|
504
|
+
|
505
|
+
```ruby
|
506
|
+
require 'hanami/validations'
|
507
|
+
|
508
|
+
class Signup
|
509
|
+
include Hanami::Validations
|
510
|
+
|
511
|
+
attribute :first_name, presence: true
|
512
|
+
attribute :last_name, presence: true
|
513
|
+
attribute :email, presence: true, format: /\A(.*)@(.*)\.(.*)\z/
|
514
|
+
attribute :password, presence: true, confirmation: true, size: 8..64
|
515
|
+
end
|
516
|
+
```
|
517
|
+
|
518
|
+
### Errors
|
519
|
+
|
520
|
+
When you invoke `#valid?`, validation errors are available at `#errors`.
|
521
|
+
It's a set of errors grouped by attribute. Each error contains the name of the
|
522
|
+
invalid attribute, the failed validation, the expected value, and the current one.
|
523
|
+
|
524
|
+
```ruby
|
525
|
+
require 'hanami/validations'
|
526
|
+
|
527
|
+
class Signup
|
528
|
+
include Hanami::Validations
|
529
|
+
|
530
|
+
attribute :email, presence: true, format: /\A(.*)@(.*)\.(.*)\z/
|
531
|
+
attribute :age, size: 18..99
|
532
|
+
end
|
533
|
+
|
534
|
+
signup = Signup.new(email: 'user@example.org')
|
535
|
+
signup.valid? # => true
|
536
|
+
|
537
|
+
signup = Signup.new(email: '', age: 17)
|
538
|
+
signup.valid? # => false
|
539
|
+
|
540
|
+
signup.errors
|
541
|
+
# => #<Hanami::Validations::Errors:0x007fe00ced9b78
|
542
|
+
# @errors={
|
543
|
+
# :email=>[
|
544
|
+
# #<Hanami::Validations::Error:0x007fe00cee3290 @attribute=:email, @validation=:presence, @expected=true, @actual="">,
|
545
|
+
# #<Hanami::Validations::Error:0x007fe00cee31f0 @attribute=:email, @validation=:format, @expected=/\A(.*)@(.*)\.(.*)\z/, @actual="">
|
546
|
+
# ],
|
547
|
+
# :age=>[
|
548
|
+
# #<Hanami::Validations::Error:0x007fe00cee30d8 @attribute=:age, @validation=:size, @expected=18..99, @actual=17>
|
549
|
+
# ]
|
550
|
+
# }>
|
551
|
+
```
|
552
|
+
|
553
|
+
### Hanami::Entity
|
554
|
+
|
555
|
+
Integration with [`Hanami::Entity`](https://github.com/hanami/model) is straight forward.
|
556
|
+
|
557
|
+
```ruby
|
558
|
+
require 'hanami/model'
|
559
|
+
require 'hanami/validations'
|
560
|
+
|
561
|
+
class Product
|
562
|
+
include Hanami::Entity
|
563
|
+
include Hanami::Validations
|
564
|
+
|
565
|
+
attribute :name, type: String, presence: true
|
566
|
+
attribute :price, type: Integer, presence: true
|
567
|
+
end
|
568
|
+
|
569
|
+
product = Product.new(name: 'Book', price: '100')
|
570
|
+
product.valid? # => true
|
571
|
+
|
572
|
+
product.name # => "Book"
|
573
|
+
product.price # => 100
|
574
|
+
```
|
32
575
|
|
33
576
|
## Contributing
|
34
577
|
|
35
|
-
|
578
|
+
1. Fork it ( https://github.com/hanami/hanami-validations/fork )
|
579
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
580
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
581
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
582
|
+
5. Create a new Pull Request
|
583
|
+
|
584
|
+
## Copyright
|
585
|
+
|
586
|
+
Copyright © 2014-2016 Luca Guidi – Released under MIT License
|
36
587
|
|
588
|
+
This project was formerly known as Lotus (`lotus-validations`).
|