lotus-validations 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 36c1599d9dc59f556a6041f459b41fe23a629e12
4
- data.tar.gz: 16fade6ce990dfa2dc5f6bd477dae2bdf0160ab5
3
+ metadata.gz: 0249c7c704d709f9579693a8f8b8faac3c1ca338
4
+ data.tar.gz: e2913b58194002b58ec6da05154982e46d30efd5
5
5
  SHA512:
6
- metadata.gz: b53e4dfe4cc649cd1f7818d00b5de4a8171d6a0ffded3b12e603497abdbec84a31d97d062b87ed38b130e314beef3b06408a4632f957ba257823b9a4bafb22dc
7
- data.tar.gz: 32dfdd1ed995e28ce04b631c2f9ee367347c7dfdf05936e9771f2c1254ea0aad763a91c62e0921c4ad9433487f5259bfb71e24a1133f7a7011aed78c9fa604b2
6
+ metadata.gz: e82fe5c215943d1fdfd11068c1b5639dcec94062a1e1cce5dafee5ee643e09dde7b05b826f1c32d7d9c10e33a0b618587cdae1a393c21f0361388f8162798afa
7
+ data.tar.gz: b92988e71b73a27e7319acf7957e001042035ce9091c3f2a1e55d83f7f9d4afb45f6c41c9742d1ba663e4eca107977c714685e53577143b7baf5e51bc11ad4b7
data/CHANGELOG.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # Lotus::Validations
2
2
  Validations mixin for Ruby objects
3
3
 
4
+ ## v0.2.2 - 2015-01-08
5
+ ### Added
6
+ - [Steve Hodgkiss] Introduced `Validations.validates`. It defines validations, for already existing attributes.
7
+
4
8
  ## v0.2.1 - 2014-12-23
5
9
  ### Added
6
10
  - [Luca Guidi] Introduced `Validations::Errors#to_h` and `to_a`
7
11
  - [Luca Guidi] Introduced `Validations::Errors#any?`
12
+ - [Luca Guidi] Official support for Ruby 2.2
8
13
 
9
14
  ### Fixed
10
15
  - [Satoshi Amemiya] Made `Validations#valid?` idempotent
data/README.md CHANGED
@@ -57,14 +57,46 @@ require 'lotus/validations'
57
57
  class Person
58
58
  include Lotus::Validations
59
59
 
60
- attribute :name
60
+ attribute :name, presence: true
61
+ attribute :email, presence: true
61
62
  end
62
63
 
63
- person = Person.new(name: 'Luca', age: 32)
64
- person.name # => "Luca"
65
- person.age # => raises NoMethodError because `:age` wasn't defined as attribute.
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.
66
68
  ```
67
69
 
70
+ ### Validations
71
+
72
+ If you prefer Lotus::Validations to **only define validations**, but **not attributes**,
73
+ you can use the following alternative syntax.
74
+
75
+ ```ruby
76
+ require 'lotus/validations'
77
+
78
+ class Person
79
+ include Lotus::Validations
80
+ attr_accessor :name, :email
81
+
82
+ # Custom initializer
83
+ def initialize(attributes = {})
84
+ @name, @email = attributes.values_at(:name, :email)
85
+ end
86
+
87
+ validates :name, presence: true
88
+ validates :email, presence: true
89
+ end
90
+
91
+ person = Person.new(name: 'Luca', email: 'me@example.org')
92
+ person.name # => "Luca"
93
+ person.email # => "me@example.org"
94
+ ```
95
+
96
+ This is a bit more verbose, but offers a great level of flexibility for your
97
+ Ruby objects. It also allows to use Lotus::Validations in combination with
98
+ **other frameworks**.
99
+
68
100
  ### Coercions
69
101
 
70
102
  If a Ruby class is passed to the `:type` option, the given value is coerced, accordingly.
@@ -129,7 +161,7 @@ end
129
161
  person = Person.new(fav_number: '23', date: 'Oct 23, 2014')
130
162
  person.valid?
131
163
 
132
- person.fav_number # => 23
164
+ person.fav_number # => #<FavNumber:0x007ffc644bba00 @number="23">
133
165
  person.date # => this raises an error, because BirthDate#initialize doesn't accept any arg
134
166
  ```
135
167
 
@@ -24,10 +24,6 @@ module Lotus
24
24
  # @api private
25
25
  CONFIRMATION_TEMPLATE = '%{name}_confirmation'.freeze
26
26
 
27
- # @since 0.2.0
28
- # @api private
29
- BLANK_STRING_MATCHER = /\A[[:space:]]*\z/.freeze
30
-
31
27
  # Instantiate an attribute
32
28
  #
33
29
  # @param attributes [Hash] a set of attributes and values coming from the
@@ -50,23 +46,13 @@ module Lotus
50
46
  # @since 0.2.0
51
47
  def validate
52
48
  _with_cleared_errors do
53
- presence
54
- acceptance
55
-
56
49
  _run_validations
57
50
  end
58
51
  end
59
52
 
60
53
  # @api private
61
54
  # @since 0.2.0
62
- def value
63
- if (coercer = @validations[:type])
64
- return nil if blank_value?
65
- Lotus::Validations::Coercions.coerce(coercer, @value)
66
- else
67
- @value
68
- end
69
- end
55
+ attr_reader :value
70
56
 
71
57
  private
72
58
  # Validates presence of the value.
@@ -197,71 +183,20 @@ module Lotus
197
183
  end
198
184
  end
199
185
 
200
- # Coerces the value to the defined type.
201
- # Built in types are:
202
- #
203
- # * `Array`
204
- # * `BigDecimal`
205
- # * `Boolean`
206
- # * `Date`
207
- # * `DateTime`
208
- # * `Float`
209
- # * `Hash`
210
- # * `Integer`
211
- # * `Pathname`
212
- # * `Set`
213
- # * `String`
214
- # * `Symbol`
215
- # * `Time`
216
- #
217
- # If a user defined class is specified, it can be freely used for coercion
218
- # purposes. The only limitation is that the constructor should have
219
- # **arity of 1**.
220
- #
221
- # @raise [ArgumentError] if the custom coercer's `#initialize` has a wrong arity.
222
- #
223
- # @see Lotus::Validations::ClassMethods#attribute
224
- # @see Lotus::Validations::Coercions
225
- #
226
- # @since 0.2.0
227
- # @api private
228
- def coerce
229
- _validate(:type) do |coercer|
230
- Lotus::Validations::Coercions.coerce(coercer, @value)
231
- true
232
- end
233
- end
234
-
235
- # Checks if the value is `nil`.
236
- #
237
- # @since 0.2.0
186
+ # @since 0.1.0
238
187
  # @api private
239
- def nil_value?
188
+ def skip?
240
189
  @value.nil?
241
190
  end
242
191
 
243
- alias_method :skip?, :nil_value?
244
-
245
192
  # Checks if the value is "blank".
246
193
  #
247
- # @see Lotus::Validations::Attribute#presence
194
+ # @see Lotus::Validations::BlankValueChecker
248
195
  #
249
196
  # @since 0.2.0
250
197
  # @api private
251
198
  def blank_value?
252
- nil_value? || _blank_string? || _empty_value?
253
- end
254
-
255
- # @since 0.2.0
256
- # @api private
257
- def _blank_string?
258
- (@value.respond_to?(:match) and @value.match(BLANK_STRING_MATCHER))
259
- end
260
-
261
- # @since 0.2.0
262
- # @api private
263
- def _empty_value?
264
- (@value.respond_to?(:empty?) and @value.empty?)
199
+ BlankValueChecker.new(@value).blank_value?
265
200
  end
266
201
 
267
202
  # Run the defined validations
@@ -269,10 +204,12 @@ module Lotus
269
204
  # @since 0.2.0
270
205
  # @api private
271
206
  def _run_validations
207
+ presence
208
+ acceptance
209
+
272
210
  return if skip?
273
211
 
274
212
  format
275
- coerce
276
213
  inclusion
277
214
  exclusion
278
215
  size
@@ -0,0 +1,354 @@
1
+ require 'set'
2
+ require 'lotus/utils/attributes'
3
+
4
+ module Lotus
5
+ module Validations
6
+ # Define attributes and their validations together
7
+ #
8
+ # @since 0.2.2
9
+ # @api private
10
+ module AttributeDefiner
11
+ # Override Ruby's hook for modules.
12
+ #
13
+ # @param base [Class] the target class
14
+ #
15
+ # @since 0.2.2
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
+ # @since 0.2.2
24
+ # @api private
25
+ module ClassMethods
26
+ # Override Ruby's hook for modules.
27
+ #
28
+ # @param base [Class] the target class
29
+ #
30
+ # @since 0.2.2
31
+ # @api private
32
+ #
33
+ # @see http://www.ruby-doc.org/core/Module.html#method-i-extended
34
+ def included(base)
35
+ super
36
+ base.defined_attributes.merge(defined_attributes)
37
+ end
38
+
39
+ # Override Ruby's hook for modules.
40
+ #
41
+ # @param base [Class] the target class
42
+ #
43
+ # @since 0.2.2
44
+ # @api private
45
+ #
46
+ # @see http://www.ruby-doc.org/core/Module.html#method-i-extended
47
+ def inherited(base)
48
+ super
49
+ base.defined_attributes.merge(defined_attributes)
50
+ end
51
+
52
+ # Define an attribute
53
+ #
54
+ # @param name [#to_sym] the name of the attribute
55
+ # @param options [Hash] optional set of validations
56
+ # @option options [Class] :type the Ruby type used to coerce the value
57
+ # @option options [TrueClass,FalseClass] :acceptance requires Ruby
58
+ # thruthiness of the value
59
+ # @option options [TrueClass,FalseClass] :confirmation requires the value
60
+ # to be confirmed twice
61
+ # @option options [#include?] :exclusion requires the value NOT be
62
+ # included in the given collection
63
+ # @option options [Regexp] :format requires value to match the given
64
+ # Regexp
65
+ # @option options [#include?] :inclusion requires the value BE included in
66
+ # the given collection
67
+ # @option options [TrueClass,FalseClass] :presence requires the value be
68
+ # included in the given collection
69
+ # @option options [Numeric,Range] :size requires value's to be equal or
70
+ # included by the given validator
71
+ #
72
+ # @raise [ArgumentError] if an unknown or mispelled validation is given
73
+ #
74
+ # @since 0.2.2
75
+ #
76
+ # @example Attributes
77
+ # require 'lotus/validations'
78
+ #
79
+ # class Person
80
+ # include Lotus::Validations
81
+ #
82
+ # attribute :name
83
+ # end
84
+ #
85
+ # person = Person.new(name: 'Luca', age: 32)
86
+ # person.name # => "Luca"
87
+ # person.age # => raises NoMethodError because `:age` wasn't defined as attribute.
88
+ #
89
+ # @example Standard coercions
90
+ # require 'lotus/validations'
91
+ #
92
+ # class Person
93
+ # include Lotus::Validations
94
+ #
95
+ # attribute :fav_number, type: Integer
96
+ # end
97
+ #
98
+ # person = Person.new(fav_number: '23')
99
+ # person.valid?
100
+ #
101
+ # person.fav_number # => 23
102
+ #
103
+ # @example Custom coercions
104
+ # require 'lotus/validations'
105
+ #
106
+ # class FavNumber
107
+ # def initialize(number)
108
+ # @number = number
109
+ # end
110
+ # end
111
+ #
112
+ # class BirthDate
113
+ # end
114
+ #
115
+ # class Person
116
+ # include Lotus::Validations
117
+ #
118
+ # attribute :fav_number, type: FavNumber
119
+ # attribute :date, type: BirthDate
120
+ # end
121
+ #
122
+ # person = Person.new(fav_number: '23', date: 'Oct 23, 2014')
123
+ # person.valid?
124
+ #
125
+ # person.fav_number # => 23
126
+ # person.date # => this raises an error, because BirthDate#initialize doesn't accept any arg
127
+ #
128
+ # @example Acceptance
129
+ # require 'lotus/validations'
130
+ #
131
+ # class Signup
132
+ # include Lotus::Validations
133
+ #
134
+ # attribute :terms_of_service, acceptance: true
135
+ # end
136
+ #
137
+ # signup = Signup.new(terms_of_service: '1')
138
+ # signup.valid? # => true
139
+ #
140
+ # signup = Signup.new(terms_of_service: '')
141
+ # signup.valid? # => false
142
+ #
143
+ # @example Confirmation
144
+ # require 'lotus/validations'
145
+ #
146
+ # class Signup
147
+ # include Lotus::Validations
148
+ #
149
+ # attribute :password, confirmation: true
150
+ # end
151
+ #
152
+ # signup = Signup.new(password: 'secret', password_confirmation: 'secret')
153
+ # signup.valid? # => true
154
+ #
155
+ # signup = Signup.new(password: 'secret', password_confirmation: 'x')
156
+ # signup.valid? # => false
157
+ #
158
+ # @example Exclusion
159
+ # require 'lotus/validations'
160
+ #
161
+ # class Signup
162
+ # include Lotus::Validations
163
+ #
164
+ # attribute :music, exclusion: ['pop']
165
+ # end
166
+ #
167
+ # signup = Signup.new(music: 'rock')
168
+ # signup.valid? # => true
169
+ #
170
+ # signup = Signup.new(music: 'pop')
171
+ # signup.valid? # => false
172
+ #
173
+ # @example Format
174
+ # require 'lotus/validations'
175
+ #
176
+ # class Signup
177
+ # include Lotus::Validations
178
+ #
179
+ # attribute :name, format: /\A[a-zA-Z]+\z/
180
+ # end
181
+ #
182
+ # signup = Signup.new(name: 'Luca')
183
+ # signup.valid? # => true
184
+ #
185
+ # signup = Signup.new(name: '23')
186
+ # signup.valid? # => false
187
+ #
188
+ # @example Inclusion
189
+ # require 'lotus/validations'
190
+ #
191
+ # class Signup
192
+ # include Lotus::Validations
193
+ #
194
+ # attribute :age, inclusion: 18..99
195
+ # end
196
+ #
197
+ # signup = Signup.new(age: 32)
198
+ # signup.valid? # => true
199
+ #
200
+ # signup = Signup.new(age: 17)
201
+ # signup.valid? # => false
202
+ #
203
+ # @example Presence
204
+ # require 'lotus/validations'
205
+ #
206
+ # class Signup
207
+ # include Lotus::Validations
208
+ #
209
+ # attribute :name, presence: true
210
+ # end
211
+ #
212
+ # signup = Signup.new(name: 'Luca')
213
+ # signup.valid? # => true
214
+ #
215
+ # signup = Signup.new(name: nil)
216
+ # signup.valid? # => false
217
+ #
218
+ # @example Size
219
+ # require 'lotus/validations'
220
+ #
221
+ # class Signup
222
+ # MEGABYTE = 1024 ** 2
223
+ # include Lotus::Validations
224
+ #
225
+ # attribute :ssn, size: 11 # exact match
226
+ # attribute :password, size: 8..64 # range
227
+ # attribute :avatar, size 1..(5 * MEGABYTE)
228
+ # end
229
+ #
230
+ # signup = Signup.new(password: 'a-very-long-password')
231
+ # signup.valid? # => true
232
+ #
233
+ # signup = Signup.new(password: 'short')
234
+ # signup.valid? # => false
235
+ def attribute(name, options = {})
236
+ define_attribute(name, options)
237
+ validates(name, options)
238
+ end
239
+
240
+ def defined_attributes
241
+ @defined_attributes ||= Set.new
242
+ end
243
+
244
+ private
245
+
246
+ # @since 0.2.2
247
+ # @api private
248
+ def define_attribute(name, options)
249
+ type = options.delete(:type)
250
+ define_accessor(name, type)
251
+ defined_attributes.add(name.to_s)
252
+
253
+ if options[:confirmation]
254
+ confirmation_accessor = "#{ name }_confirmation"
255
+ define_accessor(confirmation_accessor, type)
256
+ defined_attributes.add(confirmation_accessor)
257
+ end
258
+ end
259
+
260
+ # @since 0.2.2
261
+ # @api private
262
+ def define_accessor(name, type)
263
+ if type
264
+ define_reader(name)
265
+ define_coerced_writer(name, type)
266
+ else
267
+ define_reader(name)
268
+ define_writer(name)
269
+ end
270
+ end
271
+
272
+ # @since 0.2.2
273
+ # @api private
274
+ def define_coerced_writer(name, type)
275
+ define_method("#{ name }=") do |value|
276
+ @attributes.set(name, Lotus::Validations::Coercions.coerce(type, value))
277
+ end
278
+ end
279
+
280
+ # @since 0.2.2
281
+ # @api private
282
+ def define_writer(name)
283
+ define_method("#{ name }=") do |value|
284
+ @attributes.set(name, value)
285
+ end
286
+ end
287
+
288
+ # @since 0.2.2
289
+ # @api private
290
+ def define_reader(name)
291
+ define_method(name) do
292
+ @attributes.get(name)
293
+ end
294
+ end
295
+ end
296
+
297
+ # Create a new instance with the given attributes
298
+ #
299
+ # @param attributes [#to_h] an Hash like object which contains the
300
+ # attributes
301
+ #
302
+ # @since 0.2.2
303
+ #
304
+ # @example Initialize with Hash
305
+ # require 'lotus/validations'
306
+ #
307
+ # class Signup
308
+ # include Lotus::Validations
309
+ #
310
+ # attribute :name
311
+ # end
312
+ #
313
+ # signup = Signup.new(name: 'Luca')
314
+ #
315
+ # @example Initialize with Hash like
316
+ # require 'lotus/validations'
317
+ #
318
+ # class Params
319
+ # def initialize(attributes)
320
+ # @attributes = Hash[*attributes]
321
+ # end
322
+ #
323
+ # def to_h
324
+ # @attributes.to_h
325
+ # end
326
+ # end
327
+ #
328
+ # class Signup
329
+ # include Lotus::Validations
330
+ #
331
+ # attribute :name
332
+ # end
333
+ #
334
+ # params = Params.new([:name, 'Luca'])
335
+ # signup = Signup.new(params)
336
+ #
337
+ # signup.name # => "Luca"
338
+ def initialize(attributes)
339
+ @attributes ||= Utils::Attributes.new
340
+
341
+ attributes.to_h.each do |key, value|
342
+ public_send("#{ key }=", value) if assign_attribute?(key)
343
+ end
344
+ end
345
+
346
+ private
347
+ # @since 0.2.2
348
+ # @api private
349
+ def assign_attribute?(attr)
350
+ self.class.defined_attributes.include?(attr.to_s)
351
+ end
352
+ end
353
+ end
354
+ end
@@ -0,0 +1,45 @@
1
+ module Lotus
2
+ module Validations
3
+ # @since 0.2.2
4
+ # @api private
5
+ class BlankValueChecker
6
+ # @since 0.2.2
7
+ # @api private
8
+ BLANK_STRING_MATCHER = /\A[[:space:]]*\z/.freeze
9
+
10
+ def initialize(value)
11
+ @value = value
12
+ end
13
+
14
+ # Checks if the value is "blank".
15
+ #
16
+ # @since 0.2.2
17
+ # @api private
18
+ def blank_value?
19
+ _nil_value? || _blank_string? || _empty_value?
20
+ end
21
+
22
+ private
23
+
24
+ # Checks if the value is `nil`.
25
+ #
26
+ # @since 0.2.2
27
+ # @api private
28
+ def _nil_value?
29
+ @value.nil?
30
+ end
31
+
32
+ # @since 0.2.2
33
+ # @api private
34
+ def _blank_string?
35
+ (@value.respond_to?(:match) and @value.match(BLANK_STRING_MATCHER))
36
+ end
37
+
38
+ # @since 0.2.2
39
+ # @api private
40
+ def _empty_value?
41
+ (@value.respond_to?(:empty?) and @value.empty?)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -10,7 +10,7 @@ module Lotus
10
10
  # Coerces the given values with the given type
11
11
  #
12
12
  # @param coercer [Class] the type
13
- # @param values [Array] of objects to be coerced
13
+ # @param value [Array] of objects to be coerced
14
14
  # @param blk [Proc] an optional block to pass to the custom coercer
15
15
  #
16
16
  # @return [Object,nil] The result of the coercion, if possible
@@ -19,11 +19,11 @@ module Lotus
19
19
  #
20
20
  # @since 0.1.0
21
21
  # @api private
22
- def self.coerce(coercer, *values, &blk)
22
+ def self.coerce(coercer, value, &blk)
23
23
  if ::Lotus::Utils::Kernel.respond_to?(coercer.to_s)
24
- ::Lotus::Utils::Kernel.__send__(coercer.to_s, *values, &blk) rescue nil
24
+ ::Lotus::Utils::Kernel.__send__(coercer.to_s, value, &blk) rescue nil
25
25
  else
26
- coercer.new(*values, &blk)
26
+ coercer.new(value, &blk)
27
27
  end
28
28
  end
29
29
  end
@@ -0,0 +1,73 @@
1
+ module Lotus
2
+ module Validations
3
+ # A set of validations defined on an object
4
+ #
5
+ # @since 0.2.2
6
+ # @api private
7
+ class ValidationSet
8
+ # Allowed validations
9
+ #
10
+ # @since 0.2.2
11
+ # @api private
12
+ VALIDATIONS = [
13
+ :presence,
14
+ :acceptance,
15
+ :format,
16
+ :inclusion,
17
+ :exclusion,
18
+ :confirmation,
19
+ :size
20
+ ].freeze
21
+
22
+ # @since 0.2.2
23
+ # @api private
24
+ def initialize
25
+ @validations = Hash.new {|h,k| h[k] = {} }
26
+ end
27
+
28
+ # @since 0.2.2
29
+ # @api private
30
+ def add(name, options)
31
+ @validations[name.to_sym].merge!(
32
+ validate_options!(name, options)
33
+ )
34
+ end
35
+
36
+ # @since 0.2.2
37
+ # @api private
38
+ def each(&blk)
39
+ @validations.each(&blk)
40
+ end
41
+
42
+ # @since 0.2.2
43
+ # @api private
44
+ def each_key(&blk)
45
+ @validations.each_key(&blk)
46
+ end
47
+
48
+ private
49
+ # Checks at the loading time if the user defined validations are recognized
50
+ #
51
+ # @param name [Symbol] the attribute name
52
+ # @param options [Hash] the set of validations associated with the given attribute
53
+ #
54
+ # @raise [ArgumentError] if at least one of the validations are not
55
+ # recognized
56
+ #
57
+ # @since 0.2.2
58
+ # @api private
59
+ def validate_options!(name, options)
60
+ if (unknown = (options.keys - VALIDATIONS)) && unknown.any?
61
+ raise ArgumentError.new(%(Unknown validation(s): #{ unknown.join ', ' } for "#{ name }" attribute))
62
+ end
63
+
64
+ # FIXME remove
65
+ if options[:confirmation]
66
+ add(:"#{ name }_confirmation", {})
67
+ end
68
+
69
+ options
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,28 @@
1
+ module Lotus
2
+ module Validations
3
+ # Validate given validations and return a set of errors
4
+ #
5
+ # @since 0.2.2
6
+ # @api private
7
+ class Validator
8
+ def initialize(validation_set, attributes)
9
+ @validation_set = validation_set
10
+ @attributes = attributes
11
+ end
12
+
13
+ # @since 0.2.2
14
+ # @api private
15
+ def validate
16
+ Errors.new.tap do |errors|
17
+ @validation_set.each do |name, validations|
18
+ value = @attributes[name]
19
+ value = @attributes[name.to_s] if value.nil?
20
+
21
+ attribute = Attribute.new(@attributes, name, value, validations)
22
+ errors.add name, *attribute.validate
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,6 +1,6 @@
1
1
  module Lotus
2
2
  module Validations
3
3
  # @since 0.1.0
4
- VERSION = '0.2.1'.freeze
4
+ VERSION = '0.2.2'.freeze
5
5
  end
6
6
  end
@@ -1,7 +1,9 @@
1
1
  require 'lotus/utils/hash'
2
2
  require 'lotus/validations/version'
3
- require 'lotus/validations/attribute_set'
4
- require 'lotus/validations/attributes'
3
+ require 'lotus/validations/blank_value_checker'
4
+ require 'lotus/validations/attribute_definer'
5
+ require 'lotus/validations/validation_set'
6
+ require 'lotus/validations/validator'
5
7
  require 'lotus/validations/attribute'
6
8
  require 'lotus/validations/errors'
7
9
 
@@ -19,16 +21,34 @@ module Lotus
19
21
  #
20
22
  # @see http://www.ruby-doc.org/core/Module.html#method-i-included
21
23
  def self.included(base)
22
- base.extend ClassMethods
24
+ base.class_eval do
25
+ extend ClassMethods
26
+ include AttributeDefiner
27
+ end
23
28
  end
24
29
 
25
30
  # Validations DSL
26
31
  #
27
32
  # @since 0.1.0
28
33
  module ClassMethods
34
+ # Override Ruby's hook for class inheritance. When a class includes
35
+ # Lotus::Validations and it is subclassed, this passes
36
+ # the attributes from the superclass to the subclass.
37
+ #
38
+ # @param base [Class] the target action
39
+ #
40
+ # @since 0.2.2
41
+ # @api private
42
+ #
43
+ # @see http://www.ruby-doc.org/core/Class.html#method-i-inherited
44
+ def inherited(base)
45
+ transfer_validations_to_base(base)
46
+ super
47
+ end
48
+
29
49
  # Override Ruby's hook for modules. When a module includes
30
50
  # Lotus::Validations and it is included in a class or module, this passes
31
- # the attributes from the module to the base.
51
+ # the validations from the module to the base.
32
52
  #
33
53
  # @param base [Class] the target action
34
54
  #
@@ -60,167 +80,31 @@ module Lotus
60
80
  include Lotus::Validations
61
81
  end
62
82
 
63
- attributes.each do |attribute, options|
64
- base.attribute attribute, options
65
- end
83
+ super
84
+
85
+ transfer_validations_to_base(base)
66
86
  end
67
87
 
68
- # Define an attribute
88
+ # Define a validation for an existing attribute
69
89
  #
70
90
  # @param name [#to_sym] the name of the attribute
71
- # @param options [Hash] optional set of validations
72
- # @option options [Class] :type the Ruby type used to coerce the value
73
- # @option options [TrueClass,FalseClass] :acceptance requires Ruby
74
- # thruthiness of the value
75
- # @option options [TrueClass,FalseClass] :confirmation requires the value
76
- # to be confirmed twice
77
- # @option options [#include?] :exclusion requires the value NOT be
78
- # included in the given collection
79
- # @option options [Regexp] :format requires value to match the given
80
- # Regexp
81
- # @option options [#include?] :inclusion requires the value BE included in
82
- # the given collection
83
- # @option options [TrueClass,FalseClass] :presence requires the value be
84
- # included in the given collection
85
- # @option options [Numeric,Range] :size requires value's to be equal or
86
- # included by the given validator
87
- #
88
- # @raise [ArgumentError] if an unknown or mispelled validation is given
89
- #
90
- # @example Attributes
91
- # require 'lotus/validations'
92
- #
93
- # class Person
94
- # include Lotus::Validations
95
- #
96
- # attribute :name
97
- # end
98
- #
99
- # person = Person.new(name: 'Luca', age: 32)
100
- # person.name # => "Luca"
101
- # person.age # => raises NoMethodError because `:age` wasn't defined as attribute.
102
- #
103
- # @example Standard coercions
104
- # require 'lotus/validations'
105
- #
106
- # class Person
107
- # include Lotus::Validations
108
- #
109
- # attribute :fav_number, type: Integer
110
- # end
111
- #
112
- # person = Person.new(fav_number: '23')
113
- # person.valid?
114
- #
115
- # person.fav_number # => 23
116
- #
117
- # @example Custom coercions
118
- # require 'lotus/validations'
119
- #
120
- # class FavNumber
121
- # def initialize(number)
122
- # @number = number
123
- # end
124
- # end
125
- #
126
- # class BirthDate
127
- # end
128
- #
129
- # class Person
130
- # include Lotus::Validations
131
- #
132
- # attribute :fav_number, type: FavNumber
133
- # attribute :date, type: BirthDate
134
- # end
91
+ # @param options [Hash] set of validations
135
92
  #
136
- # person = Person.new(fav_number: '23', date: 'Oct 23, 2014')
137
- # person.valid?
93
+ # @see Lotus::Validations::ClassMethods#validations
138
94
  #
139
- # person.fav_number # => 23
140
- # person.date # => this raises an error, because BirthDate#initialize doesn't accept any arg
141
- #
142
- # @example Acceptance
143
- # require 'lotus/validations'
144
- #
145
- # class Signup
146
- # include Lotus::Validations
147
- #
148
- # attribute :terms_of_service, acceptance: true
149
- # end
150
- #
151
- # signup = Signup.new(terms_of_service: '1')
152
- # signup.valid? # => true
153
- #
154
- # signup = Signup.new(terms_of_service: '')
155
- # signup.valid? # => false
156
- #
157
- # @example Confirmation
158
- # require 'lotus/validations'
159
- #
160
- # class Signup
161
- # include Lotus::Validations
162
- #
163
- # attribute :password, confirmation: true
164
- # end
165
- #
166
- # signup = Signup.new(password: 'secret', password_confirmation: 'secret')
167
- # signup.valid? # => true
168
- #
169
- # signup = Signup.new(password: 'secret', password_confirmation: 'x')
170
- # signup.valid? # => false
171
- #
172
- # @example Exclusion
173
- # require 'lotus/validations'
174
- #
175
- # class Signup
176
- # include Lotus::Validations
177
- #
178
- # attribute :music, exclusion: ['pop']
179
- # end
180
- #
181
- # signup = Signup.new(music: 'rock')
182
- # signup.valid? # => true
183
- #
184
- # signup = Signup.new(music: 'pop')
185
- # signup.valid? # => false
186
- #
187
- # @example Format
188
- # require 'lotus/validations'
189
- #
190
- # class Signup
191
- # include Lotus::Validations
192
- #
193
- # attribute :name, format: /\A[a-zA-Z]+\z/
194
- # end
195
- #
196
- # signup = Signup.new(name: 'Luca')
197
- # signup.valid? # => true
198
- #
199
- # signup = Signup.new(name: '23')
200
- # signup.valid? # => false
201
- #
202
- # @example Inclusion
95
+ # @example Presence
203
96
  # require 'lotus/validations'
204
97
  #
205
98
  # class Signup
206
99
  # include Lotus::Validations
207
100
  #
208
- # attribute :age, inclusion: 18..99
209
- # end
210
- #
211
- # signup = Signup.new(age: 32)
212
- # signup.valid? # => true
213
- #
214
- # signup = Signup.new(age: 17)
215
- # signup.valid? # => false
101
+ # def initialize(attributes = {})
102
+ # @name = attributes.fetch(:name)
103
+ # end
216
104
  #
217
- # @example Presence
218
- # require 'lotus/validations'
105
+ # attr_accessor :name
219
106
  #
220
- # class Signup
221
- # include Lotus::Validations
222
- #
223
- # attribute :name, presence: true
107
+ # validates :name, presence: true
224
108
  # end
225
109
  #
226
110
  # signup = Signup.new(name: 'Luca')
@@ -228,43 +112,32 @@ module Lotus
228
112
  #
229
113
  # signup = Signup.new(name: nil)
230
114
  # signup.valid? # => false
115
+ def validates(name, options)
116
+ validations.add(name, options)
117
+ end
118
+
119
+ # Set of user defined validations
231
120
  #
232
- # @example Size
233
- # require 'lotus/validations'
234
- #
235
- # class Signup
236
- # MEGABYTE = 1024 ** 2
237
- # include Lotus::Validations
238
- #
239
- # attribute :ssn, size: 11 # exact match
240
- # attribute :password, size: 8..64 # range
241
- # attribute :avatar, size 1..(5 * MEGABYTE)
242
- # end
243
- #
244
- # signup = Signup.new(password: 'a-very-long-password')
245
- # signup.valid? # => true
121
+ # @return [Hash]
246
122
  #
247
- # signup = Signup.new(password: 'short')
248
- # signup.valid? # => false
249
- def attribute(name, options = {})
250
- attributes.add(name, options)
251
-
252
- class_eval %{
253
- def #{ name }
254
- @attributes.get(:#{ name })
255
- end
256
- }
123
+ # @since 0.2.2
124
+ # @api private
125
+ def validations
126
+ @validations ||= ValidationSet.new
257
127
  end
258
128
 
259
129
  private
260
- # Set of user defined attributes
130
+
131
+ # Transfers attributes to a base class
261
132
  #
262
- # @return [Hash]
133
+ # @param base [Module] the base class to transfer attributes to
263
134
  #
264
- # @since 0.1.0
135
+ # @since 0.2.2
265
136
  # @api private
266
- def attributes
267
- @attributes ||= AttributeSet.new
137
+ def transfer_validations_to_base(base)
138
+ validations.each do |attribute, options|
139
+ base.validates attribute, options
140
+ end
268
141
  end
269
142
  end
270
143
 
@@ -315,52 +188,8 @@ module Lotus
315
188
  # # #<Lotus::Validations::Error:0x007fe00cee30d8 @attribute=:age, @validation=:size, @expected=18..99, @actual=17>
316
189
  # # ]
317
190
  # # }>
318
- attr_reader :errors
319
-
320
- # Create a new instance with the given attributes
321
- #
322
- # @param attributes [#to_h] an Hash like object which contains the
323
- # attributes
324
- #
325
- # @since 0.1.0
326
- #
327
- # @example Initialize with Hash
328
- # require 'lotus/validations'
329
- #
330
- # class Signup
331
- # include Lotus::Validations
332
- #
333
- # attribute :name
334
- # end
335
- #
336
- # signup = Signup.new(name: 'Luca')
337
- #
338
- # @example Initialize with Hash like
339
- # require 'lotus/validations'
340
- #
341
- # class Params
342
- # def initialize(attributes)
343
- # @attributes = Hash[*attributes]
344
- # end
345
- #
346
- # def to_h
347
- # @attributes.to_h
348
- # end
349
- # end
350
- #
351
- # class Signup
352
- # include Lotus::Validations
353
- #
354
- # attribute :name
355
- # end
356
- #
357
- # params = Params.new([:name, 'Luca'])
358
- # signup = Signup.new(params)
359
- #
360
- # signup.name # => "Luca"
361
- def initialize(attributes)
362
- @attributes = Attributes.new(defined_attributes, attributes)
363
- @errors = Errors.new
191
+ def errors
192
+ @errors ||= Errors.new
364
193
  end
365
194
 
366
195
  # Checks if the current data satisfies the defined validations
@@ -369,13 +198,10 @@ module Lotus
369
198
  #
370
199
  # @since 0.1.0
371
200
  def valid?
372
- @errors.clear
201
+ validator = Validator.new(defined_validations, read_attributes)
202
+ @errors = validator.validate
373
203
 
374
- @attributes.each do |name, attribute|
375
- @errors.add(name, *attribute.validate)
376
- end
377
-
378
- @errors.empty?
204
+ errors.empty?
379
205
  end
380
206
 
381
207
  # Iterates thru the defined attributes and their values
@@ -396,18 +222,30 @@ module Lotus
396
222
  #
397
223
  # @since 0.1.0
398
224
  def to_h
399
- @attributes.dup
225
+ Utils::Hash.new(read_attributes).deep_dup
400
226
  end
401
227
 
402
228
  private
403
- # The set of user defined attributes.
229
+ # The set of user defined validations.
404
230
  #
405
- # @since 0.1.0
231
+ # @since 0.2.2
406
232
  # @api private
407
233
  #
408
- # @see Lotus::Validations::ClassMethods#attributes
409
- def defined_attributes
410
- self.class.__send__(:attributes)
234
+ # @see Lotus::Validations::ClassMethods#validations
235
+ def defined_validations
236
+ self.class.__send__(:validations)
237
+ end
238
+
239
+ # Builds a Hash of current attribute values.
240
+ #
241
+ # @since 0.2.2
242
+ # @api private
243
+ def read_attributes
244
+ {}.tap do |attributes|
245
+ defined_validations.each_key do |attribute|
246
+ attributes[attribute] = public_send(attribute)
247
+ end
248
+ end
411
249
  end
412
250
  end
413
251
  end
@@ -6,8 +6,8 @@ require 'lotus/validations/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'lotus-validations'
8
8
  spec.version = Lotus::Validations::VERSION
9
- spec.authors = ['Luca Guidi']
10
- spec.email = ['me@lucaguidi.com']
9
+ spec.authors = ['Luca Guidi', 'Trung Lê']
10
+ spec.email = ['me@lucaguidi.com', 'trung.le@ruby-journal.com']
11
11
  spec.summary = %q{Validations mixin for Ruby objects}
12
12
  spec.description = %q{Validations mixin for Ruby objects and support for Lotus}
13
13
  spec.homepage = 'http://lotusrb.org'
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
  spec.required_ruby_version = '>= 2.0.0'
21
21
 
22
- spec.add_dependency 'lotus-utils', '~> 0.3', '>= 0.3.2'
22
+ spec.add_dependency 'lotus-utils', '~> 0.3', '>= 0.3.3'
23
23
 
24
24
  spec.add_development_dependency 'bundler', '~> 1.6'
25
25
  spec.add_development_dependency 'minitest', '~> 5'
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lotus-validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
8
+ - Trung Lê
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-12-23 00:00:00.000000000 Z
12
+ date: 2015-01-08 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: lotus-utils
@@ -19,7 +20,7 @@ dependencies:
19
20
  version: '0.3'
20
21
  - - ">="
21
22
  - !ruby/object:Gem::Version
22
- version: 0.3.2
23
+ version: 0.3.3
23
24
  type: :runtime
24
25
  prerelease: false
25
26
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +30,7 @@ dependencies:
29
30
  version: '0.3'
30
31
  - - ">="
31
32
  - !ruby/object:Gem::Version
32
- version: 0.3.2
33
+ version: 0.3.3
33
34
  - !ruby/object:Gem::Dependency
34
35
  name: bundler
35
36
  requirement: !ruby/object:Gem::Requirement
@@ -75,6 +76,7 @@ dependencies:
75
76
  description: Validations mixin for Ruby objects and support for Lotus
76
77
  email:
77
78
  - me@lucaguidi.com
79
+ - trung.le@ruby-journal.com
78
80
  executables: []
79
81
  extensions: []
80
82
  extra_rdoc_files: []
@@ -85,11 +87,13 @@ files:
85
87
  - lib/lotus-validations.rb
86
88
  - lib/lotus/validations.rb
87
89
  - lib/lotus/validations/attribute.rb
88
- - lib/lotus/validations/attribute_set.rb
89
- - lib/lotus/validations/attributes.rb
90
+ - lib/lotus/validations/attribute_definer.rb
91
+ - lib/lotus/validations/blank_value_checker.rb
90
92
  - lib/lotus/validations/coercions.rb
91
93
  - lib/lotus/validations/error.rb
92
94
  - lib/lotus/validations/errors.rb
95
+ - lib/lotus/validations/validation_set.rb
96
+ - lib/lotus/validations/validator.rb
93
97
  - lib/lotus/validations/version.rb
94
98
  - lotus-validations.gemspec
95
99
  homepage: http://lotusrb.org
@@ -112,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
116
  version: '0'
113
117
  requirements: []
114
118
  rubyforge_project:
115
- rubygems_version: 2.2.2
119
+ rubygems_version: 2.4.5
116
120
  signing_key:
117
121
  specification_version: 4
118
122
  summary: Validations mixin for Ruby objects
@@ -1,51 +0,0 @@
1
- class AttributeSet
2
- VALIDATIONS = [:presence, :acceptance, :format, :inclusion, :exclusion, :confirmation, :size, :type].freeze
3
-
4
- def initialize
5
- @attributes = Hash.new {|h,k| h[k] = {} }
6
- end
7
-
8
- def add(name, options)
9
- @attributes[name.to_sym].merge!(
10
- validate_options!(name, options)
11
- )
12
- end
13
-
14
- def each(&blk)
15
- @attributes.each(&blk)
16
- end
17
-
18
- def iterate(attributes, &blk)
19
- if @attributes.any?
20
- @attributes.each(&blk)
21
- else
22
- attributes.each do |name, _|
23
- blk.call(name, {})
24
- end
25
- end
26
- end
27
-
28
- private
29
- # Checks at the loading time if the user defined validations are recognized
30
- #
31
- # @param name [Symbol] the attribute name
32
- # @param options [Hash] the set of validations associated with the given attribute
33
- #
34
- # @raise [ArgumentError] if at least one of the validations are not
35
- # recognized
36
- #
37
- # @since 0.2.0
38
- # @api private
39
- def validate_options!(name, options)
40
- if (unknown = (options.keys - VALIDATIONS)) && unknown.any?
41
- raise ArgumentError.new(%(Unknown validation(s): #{ unknown.join ', ' } for "#{ name }" attribute))
42
- end
43
-
44
- # FIXME remove
45
- if options[:confirmation]
46
- add(:"#{ name }_confirmation", {})
47
- end
48
-
49
- options
50
- end
51
- end
@@ -1,38 +0,0 @@
1
- module Lotus
2
- module Validations
3
- class Attributes
4
- def initialize(definitions, attributes)
5
- attributes = attributes.to_h
6
-
7
- @attributes = Utils::Hash.new.tap do |result|
8
- definitions.iterate(attributes) do |name, validations|
9
- value = attributes[name]
10
- value = attributes[name.to_s] if value.nil?
11
-
12
- result[name] = Attribute.new(attributes, name, value, validations)
13
- end
14
- end
15
- end
16
-
17
- def get(name)
18
- (attr = @attributes[name]) and attr.value
19
- end
20
-
21
- def dup
22
- Utils::Hash.new(to_h).deep_dup
23
- end
24
-
25
- def each(&blk)
26
- @attributes.each(&blk)
27
- end
28
-
29
- def to_h
30
- ::Hash.new.tap do |result|
31
- each do |name, _|
32
- result[name] = get(name)
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end