activemodel 5.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +114 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +264 -0
  5. data/lib/active_model.rb +77 -0
  6. data/lib/active_model/attribute.rb +248 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +52 -0
  8. data/lib/active_model/attribute_assignment.rb +57 -0
  9. data/lib/active_model/attribute_methods.rb +478 -0
  10. data/lib/active_model/attribute_mutation_tracker.rb +124 -0
  11. data/lib/active_model/attribute_set.rb +114 -0
  12. data/lib/active_model/attribute_set/builder.rb +126 -0
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
  14. data/lib/active_model/attributes.rb +111 -0
  15. data/lib/active_model/callbacks.rb +153 -0
  16. data/lib/active_model/conversion.rb +111 -0
  17. data/lib/active_model/dirty.rb +343 -0
  18. data/lib/active_model/errors.rb +517 -0
  19. data/lib/active_model/forbidden_attributes_protection.rb +31 -0
  20. data/lib/active_model/gem_version.rb +17 -0
  21. data/lib/active_model/lint.rb +118 -0
  22. data/lib/active_model/locale/en.yml +36 -0
  23. data/lib/active_model/model.rb +99 -0
  24. data/lib/active_model/naming.rb +318 -0
  25. data/lib/active_model/railtie.rb +14 -0
  26. data/lib/active_model/secure_password.rb +129 -0
  27. data/lib/active_model/serialization.rb +192 -0
  28. data/lib/active_model/serializers/json.rb +146 -0
  29. data/lib/active_model/translation.rb +70 -0
  30. data/lib/active_model/type.rb +53 -0
  31. data/lib/active_model/type/big_integer.rb +15 -0
  32. data/lib/active_model/type/binary.rb +52 -0
  33. data/lib/active_model/type/boolean.rb +38 -0
  34. data/lib/active_model/type/date.rb +57 -0
  35. data/lib/active_model/type/date_time.rb +51 -0
  36. data/lib/active_model/type/decimal.rb +70 -0
  37. data/lib/active_model/type/float.rb +36 -0
  38. data/lib/active_model/type/helpers.rb +7 -0
  39. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +41 -0
  40. data/lib/active_model/type/helpers/mutable.rb +20 -0
  41. data/lib/active_model/type/helpers/numeric.rb +37 -0
  42. data/lib/active_model/type/helpers/time_value.rb +68 -0
  43. data/lib/active_model/type/helpers/timezone.rb +19 -0
  44. data/lib/active_model/type/immutable_string.rb +32 -0
  45. data/lib/active_model/type/integer.rb +70 -0
  46. data/lib/active_model/type/registry.rb +70 -0
  47. data/lib/active_model/type/string.rb +26 -0
  48. data/lib/active_model/type/time.rb +51 -0
  49. data/lib/active_model/type/value.rb +126 -0
  50. data/lib/active_model/validations.rb +439 -0
  51. data/lib/active_model/validations/absence.rb +33 -0
  52. data/lib/active_model/validations/acceptance.rb +106 -0
  53. data/lib/active_model/validations/callbacks.rb +122 -0
  54. data/lib/active_model/validations/clusivity.rb +54 -0
  55. data/lib/active_model/validations/confirmation.rb +80 -0
  56. data/lib/active_model/validations/exclusion.rb +49 -0
  57. data/lib/active_model/validations/format.rb +114 -0
  58. data/lib/active_model/validations/helper_methods.rb +15 -0
  59. data/lib/active_model/validations/inclusion.rb +47 -0
  60. data/lib/active_model/validations/length.rb +129 -0
  61. data/lib/active_model/validations/numericality.rb +189 -0
  62. data/lib/active_model/validations/presence.rb +39 -0
  63. data/lib/active_model/validations/validates.rb +174 -0
  64. data/lib/active_model/validations/with.rb +147 -0
  65. data/lib/active_model/validator.rb +183 -0
  66. data/lib/active_model/version.rb +10 -0
  67. metadata +125 -0
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class Value
6
+ attr_reader :precision, :scale, :limit
7
+
8
+ def initialize(precision: nil, limit: nil, scale: nil)
9
+ @precision = precision
10
+ @scale = scale
11
+ @limit = limit
12
+ end
13
+
14
+ def type # :nodoc:
15
+ end
16
+
17
+ # Converts a value from database input to the appropriate ruby type. The
18
+ # return value of this method will be returned from
19
+ # ActiveRecord::AttributeMethods::Read#read_attribute. The default
20
+ # implementation just calls Value#cast.
21
+ #
22
+ # +value+ The raw input, as provided from the database.
23
+ def deserialize(value)
24
+ cast(value)
25
+ end
26
+
27
+ # Type casts a value from user input (e.g. from a setter). This value may
28
+ # be a string from the form builder, or a ruby object passed to a setter.
29
+ # There is currently no way to differentiate between which source it came
30
+ # from.
31
+ #
32
+ # The return value of this method will be returned from
33
+ # ActiveRecord::AttributeMethods::Read#read_attribute. See also:
34
+ # Value#cast_value.
35
+ #
36
+ # +value+ The raw input, as provided to the attribute setter.
37
+ def cast(value)
38
+ cast_value(value) unless value.nil?
39
+ end
40
+
41
+ # Casts a value from the ruby type to a type that the database knows how
42
+ # to understand. The returned value from this method should be a
43
+ # +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
44
+ # +nil+.
45
+ def serialize(value)
46
+ value
47
+ end
48
+
49
+ # Type casts a value for schema dumping. This method is private, as we are
50
+ # hoping to remove it entirely.
51
+ def type_cast_for_schema(value) # :nodoc:
52
+ value.inspect
53
+ end
54
+
55
+ # These predicates are not documented, as I need to look further into
56
+ # their use, and see if they can be removed entirely.
57
+ def binary? # :nodoc:
58
+ false
59
+ end
60
+
61
+ # Determines whether a value has changed for dirty checking. +old_value+
62
+ # and +new_value+ will always be type-cast. Types should not need to
63
+ # override this method.
64
+ def changed?(old_value, new_value, _new_value_before_type_cast)
65
+ old_value != new_value
66
+ end
67
+
68
+ # Determines whether the mutable value has been modified since it was
69
+ # read. Returns +false+ by default. If your type returns an object
70
+ # which could be mutated, you should override this method. You will need
71
+ # to either:
72
+ #
73
+ # - pass +new_value+ to Value#serialize and compare it to
74
+ # +raw_old_value+
75
+ #
76
+ # or
77
+ #
78
+ # - pass +raw_old_value+ to Value#deserialize and compare it to
79
+ # +new_value+
80
+ #
81
+ # +raw_old_value+ The original value, before being passed to
82
+ # +deserialize+.
83
+ #
84
+ # +new_value+ The current value, after type casting.
85
+ def changed_in_place?(raw_old_value, new_value)
86
+ false
87
+ end
88
+
89
+ def value_constructed_by_mass_assignment?(_value) # :nodoc:
90
+ false
91
+ end
92
+
93
+ def force_equality?(_value) # :nodoc:
94
+ false
95
+ end
96
+
97
+ def map(value) # :nodoc:
98
+ yield value
99
+ end
100
+
101
+ def ==(other)
102
+ self.class == other.class &&
103
+ precision == other.precision &&
104
+ scale == other.scale &&
105
+ limit == other.limit
106
+ end
107
+ alias eql? ==
108
+
109
+ def hash
110
+ [self.class, precision, scale, limit].hash
111
+ end
112
+
113
+ def assert_valid_value(*)
114
+ end
115
+
116
+ private
117
+
118
+ # Convenience method for types which do not need separate type casting
119
+ # behavior for user and database inputs. Called by Value#cast for
120
+ # values except +nil+.
121
+ def cast_value(value) # :doc:
122
+ value
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,439 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/extract_options"
4
+ require "active_support/core_ext/hash/keys"
5
+ require "active_support/core_ext/hash/except"
6
+
7
+ module ActiveModel
8
+ # == Active \Model \Validations
9
+ #
10
+ # Provides a full validation framework to your objects.
11
+ #
12
+ # A minimal implementation could be:
13
+ #
14
+ # class Person
15
+ # include ActiveModel::Validations
16
+ #
17
+ # attr_accessor :first_name, :last_name
18
+ #
19
+ # validates_each :first_name, :last_name do |record, attr, value|
20
+ # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
21
+ # end
22
+ # end
23
+ #
24
+ # Which provides you with the full standard validation stack that you
25
+ # know from Active Record:
26
+ #
27
+ # person = Person.new
28
+ # person.valid? # => true
29
+ # person.invalid? # => false
30
+ #
31
+ # person.first_name = 'zoolander'
32
+ # person.valid? # => false
33
+ # person.invalid? # => true
34
+ # person.errors.messages # => {first_name:["starts with z."]}
35
+ #
36
+ # Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+
37
+ # method to your instances initialized with a new <tt>ActiveModel::Errors</tt>
38
+ # object, so there is no need for you to do this manually.
39
+ module Validations
40
+ extend ActiveSupport::Concern
41
+
42
+ included do
43
+ extend ActiveModel::Naming
44
+ extend ActiveModel::Callbacks
45
+ extend ActiveModel::Translation
46
+
47
+ extend HelperMethods
48
+ include HelperMethods
49
+
50
+ attr_accessor :validation_context
51
+ private :validation_context=
52
+ define_callbacks :validate, scope: :name
53
+
54
+ class_attribute :_validators, instance_writer: false, default: Hash.new { |h, k| h[k] = [] }
55
+ end
56
+
57
+ module ClassMethods
58
+ # Validates each attribute against a block.
59
+ #
60
+ # class Person
61
+ # include ActiveModel::Validations
62
+ #
63
+ # attr_accessor :first_name, :last_name
64
+ #
65
+ # validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
66
+ # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
67
+ # end
68
+ # end
69
+ #
70
+ # Options:
71
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
72
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
73
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
74
+ # <tt>on: :custom_validation_context</tt> or
75
+ # <tt>on: [:create, :custom_validation_context]</tt>)
76
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
77
+ # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
78
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
79
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
80
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
81
+ # proc or string should return or evaluate to a +true+ or +false+ value.
82
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
83
+ # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
84
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
85
+ # method, proc or string should return or evaluate to a +true+ or +false+
86
+ # value.
87
+ def validates_each(*attr_names, &block)
88
+ validates_with BlockValidator, _merge_attributes(attr_names), &block
89
+ end
90
+
91
+ VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc:
92
+
93
+ # Adds a validation method or block to the class. This is useful when
94
+ # overriding the +validate+ instance method becomes too unwieldy and
95
+ # you're looking for more descriptive declaration of your validations.
96
+ #
97
+ # This can be done with a symbol pointing to a method:
98
+ #
99
+ # class Comment
100
+ # include ActiveModel::Validations
101
+ #
102
+ # validate :must_be_friends
103
+ #
104
+ # def must_be_friends
105
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
106
+ # end
107
+ # end
108
+ #
109
+ # With a block which is passed with the current record to be validated:
110
+ #
111
+ # class Comment
112
+ # include ActiveModel::Validations
113
+ #
114
+ # validate do |comment|
115
+ # comment.must_be_friends
116
+ # end
117
+ #
118
+ # def must_be_friends
119
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
120
+ # end
121
+ # end
122
+ #
123
+ # Or with a block where self points to the current record to be validated:
124
+ #
125
+ # class Comment
126
+ # include ActiveModel::Validations
127
+ #
128
+ # validate do
129
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
130
+ # end
131
+ # end
132
+ #
133
+ # Note that the return value of validation methods is not relevant.
134
+ # It's not possible to halt the validate callback chain.
135
+ #
136
+ # Options:
137
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
138
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
139
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
140
+ # <tt>on: :custom_validation_context</tt> or
141
+ # <tt>on: [:create, :custom_validation_context]</tt>)
142
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
143
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
144
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
145
+ # proc or string should return or evaluate to a +true+ or +false+ value.
146
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
147
+ # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
148
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
149
+ # method, proc or string should return or evaluate to a +true+ or +false+
150
+ # value.
151
+ #
152
+ # NOTE: Calling +validate+ multiple times on the same method will overwrite previous definitions.
153
+ #
154
+ def validate(*args, &block)
155
+ options = args.extract_options!
156
+
157
+ if args.all? { |arg| arg.is_a?(Symbol) }
158
+ options.each_key do |k|
159
+ unless VALID_OPTIONS_FOR_VALIDATE.include?(k)
160
+ 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`?")
161
+ end
162
+ end
163
+ end
164
+
165
+ if options.key?(:on)
166
+ options = options.dup
167
+ options[:on] = Array(options[:on])
168
+ options[:if] = Array(options[:if])
169
+ options[:if].unshift ->(o) {
170
+ !(options[:on] & Array(o.validation_context)).empty?
171
+ }
172
+ end
173
+
174
+ set_callback(:validate, *args, options, &block)
175
+ end
176
+
177
+ # List all validators that are being used to validate the model using
178
+ # +validates_with+ method.
179
+ #
180
+ # class Person
181
+ # include ActiveModel::Validations
182
+ #
183
+ # validates_with MyValidator
184
+ # validates_with OtherValidator, on: :create
185
+ # validates_with StrictValidator, strict: true
186
+ # end
187
+ #
188
+ # Person.validators
189
+ # # => [
190
+ # # #<MyValidator:0x007fbff403e808 @options={}>,
191
+ # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
192
+ # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
193
+ # # ]
194
+ def validators
195
+ _validators.values.flatten.uniq
196
+ end
197
+
198
+ # Clears all of the validators and validations.
199
+ #
200
+ # Note that this will clear anything that is being used to validate
201
+ # the model for both the +validates_with+ and +validate+ methods.
202
+ # It clears the validators that are created with an invocation of
203
+ # +validates_with+ and the callbacks that are set by an invocation
204
+ # of +validate+.
205
+ #
206
+ # class Person
207
+ # include ActiveModel::Validations
208
+ #
209
+ # validates_with MyValidator
210
+ # validates_with OtherValidator, on: :create
211
+ # validates_with StrictValidator, strict: true
212
+ # validate :cannot_be_robot
213
+ #
214
+ # def cannot_be_robot
215
+ # errors.add(:base, 'A person cannot be a robot') if person_is_robot
216
+ # end
217
+ # end
218
+ #
219
+ # Person.validators
220
+ # # => [
221
+ # # #<MyValidator:0x007fbff403e808 @options={}>,
222
+ # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
223
+ # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
224
+ # # ]
225
+ #
226
+ # If one runs <tt>Person.clear_validators!</tt> and then checks to see what
227
+ # validators this class has, you would obtain:
228
+ #
229
+ # Person.validators # => []
230
+ #
231
+ # Also, the callback set by <tt>validate :cannot_be_robot</tt> will be erased
232
+ # so that:
233
+ #
234
+ # Person._validate_callbacks.empty? # => true
235
+ #
236
+ def clear_validators!
237
+ reset_callbacks(:validate)
238
+ _validators.clear
239
+ end
240
+
241
+ # List all validators that are being used to validate a specific attribute.
242
+ #
243
+ # class Person
244
+ # include ActiveModel::Validations
245
+ #
246
+ # attr_accessor :name , :age
247
+ #
248
+ # validates_presence_of :name
249
+ # validates_inclusion_of :age, in: 0..99
250
+ # end
251
+ #
252
+ # Person.validators_on(:name)
253
+ # # => [
254
+ # # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
255
+ # # ]
256
+ def validators_on(*attributes)
257
+ attributes.flat_map do |attribute|
258
+ _validators[attribute.to_sym]
259
+ end
260
+ end
261
+
262
+ # Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
263
+ #
264
+ # class Person
265
+ # include ActiveModel::Validations
266
+ #
267
+ # attr_accessor :name
268
+ # end
269
+ #
270
+ # User.attribute_method?(:name) # => true
271
+ # User.attribute_method?(:age) # => false
272
+ def attribute_method?(attribute)
273
+ method_defined?(attribute)
274
+ end
275
+
276
+ # Copy validators on inheritance.
277
+ def inherited(base) #:nodoc:
278
+ dup = _validators.dup
279
+ base._validators = dup.each { |k, v| dup[k] = v.dup }
280
+ super
281
+ end
282
+ end
283
+
284
+ # Clean the +Errors+ object if instance is duped.
285
+ def initialize_dup(other) #:nodoc:
286
+ @errors = nil
287
+ super
288
+ end
289
+
290
+ # Returns the +Errors+ object that holds all information about attribute
291
+ # error messages.
292
+ #
293
+ # class Person
294
+ # include ActiveModel::Validations
295
+ #
296
+ # attr_accessor :name
297
+ # validates_presence_of :name
298
+ # end
299
+ #
300
+ # person = Person.new
301
+ # person.valid? # => false
302
+ # person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
303
+ def errors
304
+ @errors ||= Errors.new(self)
305
+ end
306
+
307
+ # Runs all the specified validations and returns +true+ if no errors were
308
+ # added otherwise +false+.
309
+ #
310
+ # class Person
311
+ # include ActiveModel::Validations
312
+ #
313
+ # attr_accessor :name
314
+ # validates_presence_of :name
315
+ # end
316
+ #
317
+ # person = Person.new
318
+ # person.name = ''
319
+ # person.valid? # => false
320
+ # person.name = 'david'
321
+ # person.valid? # => true
322
+ #
323
+ # Context can optionally be supplied to define which callbacks to test
324
+ # against (the context is defined on the validations using <tt>:on</tt>).
325
+ #
326
+ # class Person
327
+ # include ActiveModel::Validations
328
+ #
329
+ # attr_accessor :name
330
+ # validates_presence_of :name, on: :new
331
+ # end
332
+ #
333
+ # person = Person.new
334
+ # person.valid? # => true
335
+ # person.valid?(:new) # => false
336
+ def valid?(context = nil)
337
+ current_context, self.validation_context = validation_context, context
338
+ errors.clear
339
+ run_validations!
340
+ ensure
341
+ self.validation_context = current_context
342
+ end
343
+
344
+ alias_method :validate, :valid?
345
+
346
+ # Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
347
+ # added, +false+ otherwise.
348
+ #
349
+ # class Person
350
+ # include ActiveModel::Validations
351
+ #
352
+ # attr_accessor :name
353
+ # validates_presence_of :name
354
+ # end
355
+ #
356
+ # person = Person.new
357
+ # person.name = ''
358
+ # person.invalid? # => true
359
+ # person.name = 'david'
360
+ # person.invalid? # => false
361
+ #
362
+ # Context can optionally be supplied to define which callbacks to test
363
+ # against (the context is defined on the validations using <tt>:on</tt>).
364
+ #
365
+ # class Person
366
+ # include ActiveModel::Validations
367
+ #
368
+ # attr_accessor :name
369
+ # validates_presence_of :name, on: :new
370
+ # end
371
+ #
372
+ # person = Person.new
373
+ # person.invalid? # => false
374
+ # person.invalid?(:new) # => true
375
+ def invalid?(context = nil)
376
+ !valid?(context)
377
+ end
378
+
379
+ # Runs all the validations within the specified context. Returns +true+ if
380
+ # no errors are found, raises +ValidationError+ otherwise.
381
+ #
382
+ # Validations with no <tt>:on</tt> option will run no matter the context. Validations with
383
+ # some <tt>:on</tt> option will only run in the specified context.
384
+ def validate!(context = nil)
385
+ valid?(context) || raise_validation_error
386
+ end
387
+
388
+ # Hook method defining how an attribute value should be retrieved. By default
389
+ # this is assumed to be an instance named after the attribute. Override this
390
+ # method in subclasses should you need to retrieve the value for a given
391
+ # attribute differently:
392
+ #
393
+ # class MyClass
394
+ # include ActiveModel::Validations
395
+ #
396
+ # def initialize(data = {})
397
+ # @data = data
398
+ # end
399
+ #
400
+ # def read_attribute_for_validation(key)
401
+ # @data[key]
402
+ # end
403
+ # end
404
+ alias :read_attribute_for_validation :send
405
+
406
+ private
407
+
408
+ def run_validations!
409
+ _run_validate_callbacks
410
+ errors.empty?
411
+ end
412
+
413
+ def raise_validation_error # :doc:
414
+ raise(ValidationError.new(self))
415
+ end
416
+ end
417
+
418
+ # = Active Model ValidationError
419
+ #
420
+ # Raised by <tt>validate!</tt> when the model is invalid. Use the
421
+ # +model+ method to retrieve the record which did not validate.
422
+ #
423
+ # begin
424
+ # complex_operation_that_internally_calls_validate!
425
+ # rescue ActiveModel::ValidationError => invalid
426
+ # puts invalid.model.errors
427
+ # end
428
+ class ValidationError < StandardError
429
+ attr_reader :model
430
+
431
+ def initialize(model)
432
+ @model = model
433
+ errors = @model.errors.full_messages.join(", ")
434
+ super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid"))
435
+ end
436
+ end
437
+ end
438
+
439
+ Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file }