omg-activemodel 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +67 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +266 -0
  5. data/lib/active_model/access.rb +16 -0
  6. data/lib/active_model/api.rb +99 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +55 -0
  8. data/lib/active_model/attribute.rb +277 -0
  9. data/lib/active_model/attribute_assignment.rb +78 -0
  10. data/lib/active_model/attribute_methods.rb +592 -0
  11. data/lib/active_model/attribute_mutation_tracker.rb +189 -0
  12. data/lib/active_model/attribute_registration.rb +117 -0
  13. data/lib/active_model/attribute_set/builder.rb +182 -0
  14. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  15. data/lib/active_model/attribute_set.rb +118 -0
  16. data/lib/active_model/attributes.rb +165 -0
  17. data/lib/active_model/callbacks.rb +155 -0
  18. data/lib/active_model/conversion.rb +121 -0
  19. data/lib/active_model/deprecator.rb +7 -0
  20. data/lib/active_model/dirty.rb +416 -0
  21. data/lib/active_model/error.rb +208 -0
  22. data/lib/active_model/errors.rb +547 -0
  23. data/lib/active_model/forbidden_attributes_protection.rb +33 -0
  24. data/lib/active_model/gem_version.rb +17 -0
  25. data/lib/active_model/lint.rb +118 -0
  26. data/lib/active_model/locale/en.yml +38 -0
  27. data/lib/active_model/model.rb +78 -0
  28. data/lib/active_model/naming.rb +359 -0
  29. data/lib/active_model/nested_error.rb +22 -0
  30. data/lib/active_model/railtie.rb +24 -0
  31. data/lib/active_model/secure_password.rb +231 -0
  32. data/lib/active_model/serialization.rb +198 -0
  33. data/lib/active_model/serializers/json.rb +154 -0
  34. data/lib/active_model/translation.rb +78 -0
  35. data/lib/active_model/type/big_integer.rb +36 -0
  36. data/lib/active_model/type/binary.rb +62 -0
  37. data/lib/active_model/type/boolean.rb +48 -0
  38. data/lib/active_model/type/date.rb +78 -0
  39. data/lib/active_model/type/date_time.rb +88 -0
  40. data/lib/active_model/type/decimal.rb +107 -0
  41. data/lib/active_model/type/float.rb +64 -0
  42. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
  43. data/lib/active_model/type/helpers/mutable.rb +24 -0
  44. data/lib/active_model/type/helpers/numeric.rb +61 -0
  45. data/lib/active_model/type/helpers/time_value.rb +127 -0
  46. data/lib/active_model/type/helpers/timezone.rb +23 -0
  47. data/lib/active_model/type/helpers.rb +7 -0
  48. data/lib/active_model/type/immutable_string.rb +71 -0
  49. data/lib/active_model/type/integer.rb +113 -0
  50. data/lib/active_model/type/registry.rb +37 -0
  51. data/lib/active_model/type/serialize_cast_value.rb +47 -0
  52. data/lib/active_model/type/string.rb +43 -0
  53. data/lib/active_model/type/time.rb +87 -0
  54. data/lib/active_model/type/value.rb +157 -0
  55. data/lib/active_model/type.rb +55 -0
  56. data/lib/active_model/validations/absence.rb +33 -0
  57. data/lib/active_model/validations/acceptance.rb +113 -0
  58. data/lib/active_model/validations/callbacks.rb +119 -0
  59. data/lib/active_model/validations/clusivity.rb +54 -0
  60. data/lib/active_model/validations/comparability.rb +18 -0
  61. data/lib/active_model/validations/comparison.rb +90 -0
  62. data/lib/active_model/validations/confirmation.rb +80 -0
  63. data/lib/active_model/validations/exclusion.rb +49 -0
  64. data/lib/active_model/validations/format.rb +112 -0
  65. data/lib/active_model/validations/helper_methods.rb +15 -0
  66. data/lib/active_model/validations/inclusion.rb +47 -0
  67. data/lib/active_model/validations/length.rb +130 -0
  68. data/lib/active_model/validations/numericality.rb +222 -0
  69. data/lib/active_model/validations/presence.rb +39 -0
  70. data/lib/active_model/validations/resolve_value.rb +26 -0
  71. data/lib/active_model/validations/validates.rb +175 -0
  72. data/lib/active_model/validations/with.rb +154 -0
  73. data/lib/active_model/validations.rb +489 -0
  74. data/lib/active_model/validator.rb +190 -0
  75. data/lib/active_model/version.rb +10 -0
  76. data/lib/active_model.rb +84 -0
  77. metadata +139 -0
@@ -0,0 +1,489 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/extract_options"
4
+
5
+ module ActiveModel
6
+ # = Active \Model \Validations
7
+ #
8
+ # Provides a full validation framework to your objects.
9
+ #
10
+ # A minimal implementation could be:
11
+ #
12
+ # class Person
13
+ # include ActiveModel::Validations
14
+ #
15
+ # attr_accessor :first_name, :last_name
16
+ #
17
+ # validates_each :first_name, :last_name do |record, attr, value|
18
+ # record.errors.add attr, "starts with z." if value.start_with?("z")
19
+ # end
20
+ # end
21
+ #
22
+ # Which provides you with the full standard validation stack that you
23
+ # know from Active Record:
24
+ #
25
+ # person = Person.new
26
+ # person.valid? # => true
27
+ # person.invalid? # => false
28
+ #
29
+ # person.first_name = 'zoolander'
30
+ # person.valid? # => false
31
+ # person.invalid? # => true
32
+ # person.errors.messages # => {first_name:["starts with z."]}
33
+ #
34
+ # Note that +ActiveModel::Validations+ automatically adds an +errors+
35
+ # method to your instances initialized with a new ActiveModel::Errors
36
+ # object, so there is no need for you to do this manually.
37
+ module Validations
38
+ extend ActiveSupport::Concern
39
+
40
+ included do
41
+ extend ActiveModel::Naming
42
+ extend ActiveModel::Callbacks
43
+ extend ActiveModel::Translation
44
+
45
+ extend HelperMethods
46
+ include HelperMethods
47
+
48
+ define_callbacks :validate, scope: :name
49
+
50
+ class_attribute :_validators, instance_writer: false, default: Hash.new { |h, k| h[k] = [] }
51
+ end
52
+
53
+ module ClassMethods
54
+ # Validates each attribute against a block.
55
+ #
56
+ # class Person
57
+ # include ActiveModel::Validations
58
+ #
59
+ # attr_accessor :first_name, :last_name
60
+ #
61
+ # validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
62
+ # record.errors.add attr, "starts with z." if value.start_with?("z")
63
+ # end
64
+ # end
65
+ #
66
+ # Options:
67
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
68
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
69
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
70
+ # <tt>on: :custom_validation_context</tt> or
71
+ # <tt>on: [:create, :custom_validation_context]</tt>)
72
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
73
+ # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
74
+ # * <tt>:if</tt> - Specifies a method, proc, or string to call to determine
75
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
76
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
77
+ # proc or string should return or evaluate to a +true+ or +false+ value.
78
+ # * <tt>:unless</tt> - Specifies a method, proc, or string to call to
79
+ # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
80
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
81
+ # method, proc, or string should return or evaluate to a +true+ or +false+
82
+ # value.
83
+ def validates_each(*attr_names, &block)
84
+ validates_with BlockValidator, _merge_attributes(attr_names), &block
85
+ end
86
+
87
+ VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc:
88
+
89
+ # Adds a validation method or block to the class. This is useful when
90
+ # overriding the +validate+ instance method becomes too unwieldy and
91
+ # you're looking for more descriptive declaration of your validations.
92
+ #
93
+ # This can be done with a symbol pointing to a method:
94
+ #
95
+ # class Comment
96
+ # include ActiveModel::Validations
97
+ #
98
+ # validate :must_be_friends
99
+ #
100
+ # def must_be_friends
101
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
102
+ # end
103
+ # end
104
+ #
105
+ # With a block which is passed with the current record to be validated:
106
+ #
107
+ # class Comment
108
+ # include ActiveModel::Validations
109
+ #
110
+ # validate do |comment|
111
+ # comment.must_be_friends
112
+ # end
113
+ #
114
+ # def must_be_friends
115
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
116
+ # end
117
+ # end
118
+ #
119
+ # Or with a block where +self+ points to the current record to be validated:
120
+ #
121
+ # class Comment
122
+ # include ActiveModel::Validations
123
+ #
124
+ # validate do
125
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
126
+ # end
127
+ # end
128
+ #
129
+ # Note that the return value of validation methods is not relevant.
130
+ # It's not possible to halt the validate callback chain.
131
+ #
132
+ # Options:
133
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
134
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
135
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
136
+ # <tt>on: :custom_validation_context</tt> or
137
+ # <tt>on: [:create, :custom_validation_context]</tt>)
138
+ # * <tt>:if</tt> - Specifies a method, proc, or string to call to determine
139
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
140
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
141
+ # proc or string should return or evaluate to a +true+ or +false+ value.
142
+ # * <tt>:unless</tt> - Specifies a method, proc, or string to call to
143
+ # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
144
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
145
+ # method, proc, or string should return or evaluate to a +true+ or +false+
146
+ # value.
147
+ #
148
+ # NOTE: Calling +validate+ multiple times on the same method will overwrite previous definitions.
149
+ #
150
+ def validate(*args, &block)
151
+ options = args.extract_options!
152
+
153
+ if args.all?(Symbol)
154
+ options.each_key do |k|
155
+ unless VALID_OPTIONS_FOR_VALIDATE.include?(k)
156
+ raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?")
157
+ end
158
+ end
159
+ end
160
+
161
+ if options.key?(:on)
162
+ options = options.merge(if: [predicate_for_validation_context(options[:on]), *options[:if]])
163
+ end
164
+
165
+ set_callback(:validate, *args, options, &block)
166
+ end
167
+
168
+ # List all validators that are being used to validate the model using
169
+ # +validates_with+ method.
170
+ #
171
+ # class Person
172
+ # include ActiveModel::Validations
173
+ #
174
+ # validates_with MyValidator
175
+ # validates_with OtherValidator, on: :create
176
+ # validates_with StrictValidator, strict: true
177
+ # end
178
+ #
179
+ # Person.validators
180
+ # # => [
181
+ # # #<MyValidator:0x007fbff403e808 @options={}>,
182
+ # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
183
+ # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
184
+ # # ]
185
+ def validators
186
+ _validators.values.flatten.uniq
187
+ end
188
+
189
+ # Clears all of the validators and validations.
190
+ #
191
+ # Note that this will clear anything that is being used to validate
192
+ # the model for both the +validates_with+ and +validate+ methods.
193
+ # It clears the validators that are created with an invocation of
194
+ # +validates_with+ and the callbacks that are set by an invocation
195
+ # of +validate+.
196
+ #
197
+ # class Person
198
+ # include ActiveModel::Validations
199
+ #
200
+ # validates_with MyValidator
201
+ # validates_with OtherValidator, on: :create
202
+ # validates_with StrictValidator, strict: true
203
+ # validate :cannot_be_robot
204
+ #
205
+ # def cannot_be_robot
206
+ # errors.add(:base, 'A person cannot be a robot') if person_is_robot
207
+ # end
208
+ # end
209
+ #
210
+ # Person.validators
211
+ # # => [
212
+ # # #<MyValidator:0x007fbff403e808 @options={}>,
213
+ # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
214
+ # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
215
+ # # ]
216
+ #
217
+ # If one runs <tt>Person.clear_validators!</tt> and then checks to see what
218
+ # validators this class has, you would obtain:
219
+ #
220
+ # Person.validators # => []
221
+ #
222
+ # Also, the callback set by <tt>validate :cannot_be_robot</tt> will be erased
223
+ # so that:
224
+ #
225
+ # Person._validate_callbacks.empty? # => true
226
+ #
227
+ def clear_validators!
228
+ reset_callbacks(:validate)
229
+ _validators.clear
230
+ end
231
+
232
+ # List all validators that are being used to validate a specific attribute.
233
+ #
234
+ # class Person
235
+ # include ActiveModel::Validations
236
+ #
237
+ # attr_accessor :name, :age
238
+ #
239
+ # validates_presence_of :name
240
+ # validates_inclusion_of :age, in: 0..99
241
+ # end
242
+ #
243
+ # Person.validators_on(:name)
244
+ # # => [
245
+ # # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
246
+ # # ]
247
+ def validators_on(*attributes)
248
+ attributes.flat_map do |attribute|
249
+ _validators[attribute.to_sym]
250
+ end
251
+ end
252
+
253
+ # Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
254
+ #
255
+ # class Person
256
+ # include ActiveModel::Validations
257
+ #
258
+ # attr_accessor :name
259
+ # end
260
+ #
261
+ # User.attribute_method?(:name) # => true
262
+ # User.attribute_method?(:age) # => false
263
+ def attribute_method?(attribute)
264
+ method_defined?(attribute)
265
+ end
266
+
267
+ # Copy validators on inheritance.
268
+ def inherited(base) # :nodoc:
269
+ dup = _validators.dup
270
+ base._validators = dup.each { |k, v| dup[k] = v.dup }
271
+ super
272
+ end
273
+
274
+ private
275
+ @@predicates_for_validation_contexts = {}
276
+
277
+ def predicate_for_validation_context(context)
278
+ context = context.is_a?(Array) ? context.sort : Array(context)
279
+
280
+ @@predicates_for_validation_contexts[context] ||= -> (model) do
281
+ if model.validation_context.is_a?(Array)
282
+ model.validation_context.any? { |model_context| context.include?(model_context) }
283
+ else
284
+ context.include?(model.validation_context)
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ # Clean the +Errors+ object if instance is duped.
291
+ def initialize_dup(other) # :nodoc:
292
+ @errors = nil
293
+ super
294
+ end
295
+
296
+ # Returns the +Errors+ object that holds all information about attribute
297
+ # error messages.
298
+ #
299
+ # class Person
300
+ # include ActiveModel::Validations
301
+ #
302
+ # attr_accessor :name
303
+ # validates_presence_of :name
304
+ # end
305
+ #
306
+ # person = Person.new
307
+ # person.valid? # => false
308
+ # person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
309
+ def errors
310
+ @errors ||= Errors.new(self)
311
+ end
312
+
313
+ # Runs all the specified validations and returns +true+ if no errors were
314
+ # added otherwise +false+.
315
+ #
316
+ # class Person
317
+ # include ActiveModel::Validations
318
+ #
319
+ # attr_accessor :name
320
+ # validates_presence_of :name
321
+ # end
322
+ #
323
+ # person = Person.new
324
+ # person.name = ''
325
+ # person.valid? # => false
326
+ # person.name = 'david'
327
+ # person.valid? # => true
328
+ #
329
+ # Context can optionally be supplied to define which callbacks to test
330
+ # against (the context is defined on the validations using <tt>:on</tt>).
331
+ #
332
+ # class Person
333
+ # include ActiveModel::Validations
334
+ #
335
+ # attr_accessor :name
336
+ # validates_presence_of :name, on: :new
337
+ # end
338
+ #
339
+ # person = Person.new
340
+ # person.valid? # => true
341
+ # person.valid?(:new) # => false
342
+ def valid?(context = nil)
343
+ current_context = validation_context
344
+ context_for_validation.context = context
345
+ errors.clear
346
+ run_validations!
347
+ ensure
348
+ context_for_validation.context = current_context
349
+ end
350
+
351
+ alias_method :validate, :valid?
352
+
353
+ def freeze
354
+ errors
355
+ context_for_validation
356
+
357
+ super
358
+ end
359
+
360
+ # Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
361
+ # added, +false+ otherwise.
362
+ #
363
+ # class Person
364
+ # include ActiveModel::Validations
365
+ #
366
+ # attr_accessor :name
367
+ # validates_presence_of :name
368
+ # end
369
+ #
370
+ # person = Person.new
371
+ # person.name = ''
372
+ # person.invalid? # => true
373
+ # person.name = 'david'
374
+ # person.invalid? # => false
375
+ #
376
+ # Context can optionally be supplied to define which callbacks to test
377
+ # against (the context is defined on the validations using <tt>:on</tt>).
378
+ #
379
+ # class Person
380
+ # include ActiveModel::Validations
381
+ #
382
+ # attr_accessor :name
383
+ # validates_presence_of :name, on: :new
384
+ # end
385
+ #
386
+ # person = Person.new
387
+ # person.invalid? # => false
388
+ # person.invalid?(:new) # => true
389
+ def invalid?(context = nil)
390
+ !valid?(context)
391
+ end
392
+
393
+ # Runs all the validations within the specified context. Returns +true+ if
394
+ # no errors are found, raises +ValidationError+ otherwise.
395
+ #
396
+ # Validations with no <tt>:on</tt> option will run no matter the context. Validations with
397
+ # some <tt>:on</tt> option will only run in the specified context.
398
+ def validate!(context = nil)
399
+ valid?(context) || raise_validation_error
400
+ end
401
+
402
+ # Hook method defining how an attribute value should be retrieved. By default
403
+ # this is assumed to be an instance named after the attribute. Override this
404
+ # method in subclasses should you need to retrieve the value for a given
405
+ # attribute differently:
406
+ #
407
+ # class MyClass
408
+ # include ActiveModel::Validations
409
+ #
410
+ # def initialize(data = {})
411
+ # @data = data
412
+ # end
413
+ #
414
+ # def read_attribute_for_validation(key)
415
+ # @data[key]
416
+ # end
417
+ # end
418
+ alias :read_attribute_for_validation :send
419
+
420
+ # Returns the context when running validations.
421
+ #
422
+ # This is useful when running validations except a certain context (opposite to the +on+ option).
423
+ #
424
+ # class Person
425
+ # include ActiveModel::Validations
426
+ #
427
+ # attr_accessor :name
428
+ # validates :name, presence: true, if: -> { validation_context != :custom }
429
+ # end
430
+ #
431
+ # person = Person.new
432
+ # person.valid? #=> false
433
+ # person.valid?(:new) #=> false
434
+ # person.valid?(:custom) #=> true
435
+ def validation_context
436
+ context_for_validation.context
437
+ end
438
+
439
+ private
440
+ def validation_context=(context)
441
+ context_for_validation.context = context
442
+ end
443
+
444
+ def context_for_validation
445
+ @context_for_validation ||= ValidationContext.new
446
+ end
447
+
448
+ def init_internals
449
+ super
450
+ @errors = nil
451
+ @context_for_validation = nil
452
+ end
453
+
454
+ def run_validations!
455
+ _run_validate_callbacks
456
+ errors.empty?
457
+ end
458
+
459
+ def raise_validation_error # :doc:
460
+ raise(ValidationError.new(self))
461
+ end
462
+ end
463
+
464
+ # = Active \Model \ValidationError
465
+ #
466
+ # Raised by <tt>validate!</tt> when the model is invalid. Use the
467
+ # +model+ method to retrieve the record which did not validate.
468
+ #
469
+ # begin
470
+ # complex_operation_that_internally_calls_validate!
471
+ # rescue ActiveModel::ValidationError => invalid
472
+ # puts invalid.model.errors
473
+ # end
474
+ class ValidationError < StandardError
475
+ attr_reader :model
476
+
477
+ def initialize(model)
478
+ @model = model
479
+ errors = @model.errors.full_messages.join(", ")
480
+ super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid"))
481
+ end
482
+ end
483
+
484
+ class ValidationContext # :nodoc:
485
+ attr_accessor :context
486
+ end
487
+ end
488
+
489
+ Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file }
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/anonymous"
4
+
5
+ module ActiveModel
6
+ # = Active \Model \Validator
7
+ #
8
+ # A simple base class that can be used along with
9
+ # ActiveModel::Validations::ClassMethods.validates_with
10
+ #
11
+ # class Person
12
+ # include ActiveModel::Validations
13
+ # validates_with MyValidator
14
+ # end
15
+ #
16
+ # class MyValidator < ActiveModel::Validator
17
+ # def validate(record)
18
+ # if some_complex_logic
19
+ # record.errors.add(:base, "This record is invalid")
20
+ # end
21
+ # end
22
+ #
23
+ # private
24
+ # def some_complex_logic
25
+ # # ...
26
+ # end
27
+ # end
28
+ #
29
+ # Any class that inherits from \ActiveModel::Validator must implement a method
30
+ # called +validate+ which accepts a +record+.
31
+ #
32
+ # class Person
33
+ # include ActiveModel::Validations
34
+ # validates_with MyValidator
35
+ # end
36
+ #
37
+ # class MyValidator < ActiveModel::Validator
38
+ # def validate(record)
39
+ # record # => The person instance being validated
40
+ # options # => Any non-standard options passed to validates_with
41
+ # end
42
+ # end
43
+ #
44
+ # To cause a validation error, you must add to the +record+'s errors directly
45
+ # from within the validators message.
46
+ #
47
+ # class MyValidator < ActiveModel::Validator
48
+ # def validate(record)
49
+ # record.errors.add :base, "This is some custom error message"
50
+ # record.errors.add :first_name, "This is some complex validation"
51
+ # # etc...
52
+ # end
53
+ # end
54
+ #
55
+ # To add behavior to the initialize method, use the following signature:
56
+ #
57
+ # class MyValidator < ActiveModel::Validator
58
+ # def initialize(options)
59
+ # super
60
+ # @my_custom_field = options[:field_name] || :first_name
61
+ # end
62
+ # end
63
+ #
64
+ # Note that the validator is initialized only once for the whole application
65
+ # life cycle, and not on each validation run.
66
+ #
67
+ # The easiest way to add custom validators for validating individual attributes
68
+ # is with the convenient ActiveModel::EachValidator class.
69
+ #
70
+ # class TitleValidator < ActiveModel::EachValidator
71
+ # def validate_each(record, attribute, value)
72
+ # record.errors.add attribute, 'must be Mr., Mrs., or Dr.' unless %w(Mr. Mrs. Dr.).include?(value)
73
+ # end
74
+ # end
75
+ #
76
+ # This can now be used in combination with the +validates+ method.
77
+ # See ActiveModel::Validations::ClassMethods#validates for more on this.
78
+ #
79
+ # class Person
80
+ # include ActiveModel::Validations
81
+ # attr_accessor :title
82
+ #
83
+ # validates :title, presence: true, title: true
84
+ # end
85
+ #
86
+ # It can be useful to access the class that is using that validator when there are prerequisites such
87
+ # as an +attr_accessor+ being present. This class is accessible via <tt>options[:class]</tt> in the constructor.
88
+ # To set up your validator override the constructor.
89
+ #
90
+ # class MyValidator < ActiveModel::Validator
91
+ # def initialize(options={})
92
+ # super
93
+ # options[:class].attr_accessor :custom_attribute
94
+ # end
95
+ # end
96
+ class Validator
97
+ attr_reader :options
98
+
99
+ # Returns the kind of the validator.
100
+ #
101
+ # PresenceValidator.kind # => :presence
102
+ # AcceptanceValidator.kind # => :acceptance
103
+ def self.kind
104
+ @kind ||= name.split("::").last.underscore.chomp("_validator").to_sym unless anonymous?
105
+ end
106
+
107
+ # Accepts options that will be made available through the +options+ reader.
108
+ def initialize(options = {})
109
+ @options = options.except(:class).freeze
110
+ end
111
+
112
+ # Returns the kind for this validator.
113
+ #
114
+ # PresenceValidator.new(attributes: [:username]).kind # => :presence
115
+ # AcceptanceValidator.new(attributes: [:terms]).kind # => :acceptance
116
+ def kind
117
+ self.class.kind
118
+ end
119
+
120
+ # Override this method in subclasses with validation logic, adding errors
121
+ # to the records +errors+ array where necessary.
122
+ def validate(record)
123
+ raise NotImplementedError, "Subclasses must implement a validate(record) method."
124
+ end
125
+ end
126
+
127
+ # = Active \Model \EachValidator
128
+ #
129
+ # +EachValidator+ is a validator which iterates through the attributes given
130
+ # in the options hash invoking the <tt>validate_each</tt> method passing in the
131
+ # record, attribute, and value.
132
+ #
133
+ # All \Active \Model validations are built on top of this validator.
134
+ class EachValidator < Validator
135
+ attr_reader :attributes
136
+
137
+ # Returns a new validator instance. All options will be available via the
138
+ # +options+ reader, however the <tt>:attributes</tt> option will be removed
139
+ # and instead be made available through the +attributes+ reader.
140
+ def initialize(options)
141
+ @attributes = Array(options.delete(:attributes))
142
+ raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
143
+ super
144
+ check_validity!
145
+ end
146
+
147
+ # Performs validation on the supplied record. By default this will call
148
+ # +validate_each+ to determine validity therefore subclasses should
149
+ # override +validate_each+ with validation logic.
150
+ def validate(record)
151
+ attributes.each do |attribute|
152
+ value = record.read_attribute_for_validation(attribute)
153
+ next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
154
+ value = prepare_value_for_validation(value, record, attribute)
155
+ validate_each(record, attribute, value)
156
+ end
157
+ end
158
+
159
+ # Override this method in subclasses with the validation logic, adding
160
+ # errors to the records +errors+ array where necessary.
161
+ def validate_each(record, attribute, value)
162
+ raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method"
163
+ end
164
+
165
+ # Hook method that gets called by the initializer allowing verification
166
+ # that the arguments supplied are valid. You could for example raise an
167
+ # +ArgumentError+ when invalid options are supplied.
168
+ def check_validity!
169
+ end
170
+
171
+ private
172
+ def prepare_value_for_validation(value, record, attr_name)
173
+ value
174
+ end
175
+ end
176
+
177
+ # +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
178
+ # and call this block for each attribute being validated. +validates_each+ uses this validator.
179
+ class BlockValidator < EachValidator # :nodoc:
180
+ def initialize(options, &block)
181
+ @block = block
182
+ super
183
+ end
184
+
185
+ private
186
+ def validate_each(record, attribute, value)
187
+ @block.call(record, attribute, value)
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gem_version"
4
+
5
+ module ActiveModel
6
+ # Returns the currently loaded version of \Active \Model as a +Gem::Version+.
7
+ def self.version
8
+ gem_version
9
+ end
10
+ end