omg-activemodel 8.0.0.alpha1

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.
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,547 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/conversions"
4
+ require "active_support/core_ext/string/inflections"
5
+ require "active_support/core_ext/object/deep_dup"
6
+ require "active_model/error"
7
+ require "active_model/nested_error"
8
+ require "forwardable"
9
+
10
+ module ActiveModel
11
+ # = Active \Model \Errors
12
+ #
13
+ # Provides error related functionalities you can include in your object
14
+ # for handling error messages and interacting with Action View helpers.
15
+ #
16
+ # A minimal implementation could be:
17
+ #
18
+ # class Person
19
+ # # Required dependency for ActiveModel::Errors
20
+ # extend ActiveModel::Naming
21
+ #
22
+ # def initialize
23
+ # @errors = ActiveModel::Errors.new(self)
24
+ # end
25
+ #
26
+ # attr_accessor :name
27
+ # attr_reader :errors
28
+ #
29
+ # def validate!
30
+ # errors.add(:name, :blank, message: "cannot be nil") if name.nil?
31
+ # end
32
+ #
33
+ # # The following methods are needed to be minimally implemented
34
+ #
35
+ # def read_attribute_for_validation(attr)
36
+ # send(attr)
37
+ # end
38
+ #
39
+ # def self.human_attribute_name(attr, options = {})
40
+ # attr
41
+ # end
42
+ #
43
+ # def self.lookup_ancestors
44
+ # [self]
45
+ # end
46
+ # end
47
+ #
48
+ # The last three methods are required in your object for +Errors+ to be
49
+ # able to generate error messages correctly and also handle multiple
50
+ # languages. Of course, if you extend your object with ActiveModel::Translation
51
+ # you will not need to implement the last two. Likewise, using
52
+ # ActiveModel::Validations will handle the validation related methods
53
+ # for you.
54
+ #
55
+ # The above allows you to do:
56
+ #
57
+ # person = Person.new
58
+ # person.validate! # => ["cannot be nil"]
59
+ # person.errors.full_messages # => ["name cannot be nil"]
60
+ # # etc..
61
+ class Errors
62
+ include Enumerable
63
+
64
+ extend Forwardable
65
+
66
+ ##
67
+ # :method: each
68
+ #
69
+ # :call-seq: each(&block)
70
+ #
71
+ # Iterates through each error object.
72
+ #
73
+ # person.errors.add(:name, :too_short, count: 2)
74
+ # person.errors.each do |error|
75
+ # # Will yield <#ActiveModel::Error attribute=name, type=too_short,
76
+ # options={:count=>3}>
77
+ # end
78
+
79
+ ##
80
+ # :method: clear
81
+ #
82
+ # :call-seq: clear
83
+ #
84
+ # Clears all errors. Clearing the errors does not, however, make the model
85
+ # valid. The next time the validations are run (for example, via
86
+ # ActiveRecord::Validations#valid?), the errors collection will be filled
87
+ # again if any validations fail.
88
+
89
+ ##
90
+ # :method: empty?
91
+ #
92
+ # :call-seq: empty?
93
+ #
94
+ # Returns true if there are no errors.
95
+
96
+ ##
97
+ # :method: size
98
+ #
99
+ # :call-seq: size
100
+ #
101
+ # Returns number of errors.
102
+
103
+ def_delegators :@errors, :each, :clear, :empty?, :size, :uniq!
104
+
105
+ # The actual array of +Error+ objects
106
+ # This method is aliased to <tt>objects</tt>.
107
+ attr_reader :errors
108
+ alias :objects :errors
109
+
110
+ # Pass in the instance of the object that is using the errors object.
111
+ #
112
+ # class Person
113
+ # def initialize
114
+ # @errors = ActiveModel::Errors.new(self)
115
+ # end
116
+ # end
117
+ def initialize(base)
118
+ @base = base
119
+ @errors = []
120
+ end
121
+
122
+ def initialize_dup(other) # :nodoc:
123
+ @errors = other.errors.deep_dup
124
+ super
125
+ end
126
+
127
+ # Copies the errors from <tt>other</tt>.
128
+ # For copying errors but keep <tt>@base</tt> as is.
129
+ #
130
+ # ==== Parameters
131
+ #
132
+ # * +other+ - The ActiveModel::Errors instance.
133
+ #
134
+ # ==== Examples
135
+ #
136
+ # person.errors.copy!(other)
137
+ #
138
+ def copy!(other) # :nodoc:
139
+ @errors = other.errors.deep_dup
140
+ @errors.each { |error|
141
+ error.instance_variable_set(:@base, @base)
142
+ }
143
+ end
144
+
145
+ # Imports one error.
146
+ # Imported errors are wrapped as a NestedError,
147
+ # providing access to original error object.
148
+ # If attribute or type needs to be overridden, use +override_options+.
149
+ #
150
+ # ==== Options
151
+ #
152
+ # * +:attribute+ - Override the attribute the error belongs to.
153
+ # * +:type+ - Override type of the error.
154
+ def import(error, override_options = {})
155
+ [:attribute, :type].each do |key|
156
+ if override_options.key?(key)
157
+ override_options[key] = override_options[key].to_sym
158
+ end
159
+ end
160
+ @errors.append(NestedError.new(@base, error, override_options))
161
+ end
162
+
163
+ # Merges the errors from <tt>other</tt>,
164
+ # each Error wrapped as NestedError.
165
+ #
166
+ # ==== Parameters
167
+ #
168
+ # * +other+ - The ActiveModel::Errors instance.
169
+ #
170
+ # ==== Examples
171
+ #
172
+ # person.errors.merge!(other)
173
+ #
174
+ def merge!(other)
175
+ return errors if equal?(other)
176
+
177
+ other.errors.each { |error|
178
+ import(error)
179
+ }
180
+ end
181
+
182
+ # Search for errors matching +attribute+, +type+, or +options+.
183
+ #
184
+ # Only supplied params will be matched.
185
+ #
186
+ # person.errors.where(:name) # => all name errors.
187
+ # person.errors.where(:name, :too_short) # => all name errors being too short
188
+ # person.errors.where(:name, :too_short, minimum: 2) # => all name errors being too short and minimum is 2
189
+ def where(attribute, type = nil, **options)
190
+ attribute, type, options = normalize_arguments(attribute, type, **options)
191
+ @errors.select { |error|
192
+ error.match?(attribute, type, **options)
193
+ }
194
+ end
195
+
196
+ # Returns +true+ if the error messages include an error for the given key
197
+ # +attribute+, +false+ otherwise.
198
+ #
199
+ # person.errors.messages # => {:name=>["cannot be nil"]}
200
+ # person.errors.include?(:name) # => true
201
+ # person.errors.include?(:age) # => false
202
+ def include?(attribute)
203
+ @errors.any? { |error|
204
+ error.match?(attribute.to_sym)
205
+ }
206
+ end
207
+ alias :has_key? :include?
208
+ alias :key? :include?
209
+
210
+ # Delete messages for +key+. Returns the deleted messages.
211
+ #
212
+ # person.errors[:name] # => ["cannot be nil"]
213
+ # person.errors.delete(:name) # => ["cannot be nil"]
214
+ # person.errors[:name] # => []
215
+ def delete(attribute, type = nil, **options)
216
+ attribute, type, options = normalize_arguments(attribute, type, **options)
217
+ matches = where(attribute, type, **options)
218
+ matches.each do |error|
219
+ @errors.delete(error)
220
+ end
221
+ matches.map(&:message).presence
222
+ end
223
+
224
+ # When passed a symbol or a name of a method, returns an array of errors
225
+ # for the method.
226
+ #
227
+ # person.errors[:name] # => ["cannot be nil"]
228
+ # person.errors['name'] # => ["cannot be nil"]
229
+ def [](attribute)
230
+ messages_for(attribute)
231
+ end
232
+
233
+ # Returns all error attribute names
234
+ #
235
+ # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
236
+ # person.errors.attribute_names # => [:name]
237
+ def attribute_names
238
+ @errors.map(&:attribute).uniq.freeze
239
+ end
240
+
241
+ # Returns a Hash that can be used as the JSON representation for this
242
+ # object. You can pass the <tt>:full_messages</tt> option. This determines
243
+ # if the JSON object should contain full messages or not (false by default).
244
+ #
245
+ # person.errors.as_json # => {:name=>["cannot be nil"]}
246
+ # person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}
247
+ def as_json(options = nil)
248
+ to_hash(options && options[:full_messages])
249
+ end
250
+
251
+ # Returns a Hash of attributes with their error messages. If +full_messages+
252
+ # is +true+, it will contain full messages (see +full_message+).
253
+ #
254
+ # person.errors.to_hash # => {:name=>["cannot be nil"]}
255
+ # person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
256
+ def to_hash(full_messages = false)
257
+ message_method = full_messages ? :full_message : :message
258
+ group_by_attribute.transform_values do |errors|
259
+ errors.map(&message_method)
260
+ end
261
+ end
262
+
263
+ undef :to_h
264
+
265
+ EMPTY_ARRAY = [].freeze # :nodoc:
266
+
267
+ # Returns a Hash of attributes with an array of their error messages.
268
+ def messages
269
+ hash = to_hash
270
+ hash.default = EMPTY_ARRAY
271
+ hash.freeze
272
+ hash
273
+ end
274
+
275
+ # Returns a Hash of attributes with an array of their error details.
276
+ def details
277
+ hash = group_by_attribute.transform_values do |errors|
278
+ errors.map(&:details)
279
+ end
280
+ hash.default = EMPTY_ARRAY
281
+ hash.freeze
282
+ hash
283
+ end
284
+
285
+ # Returns a Hash of attributes with an array of their Error objects.
286
+ #
287
+ # person.errors.group_by_attribute
288
+ # # => {:name=>[<#ActiveModel::Error>, <#ActiveModel::Error>]}
289
+ def group_by_attribute
290
+ @errors.group_by(&:attribute)
291
+ end
292
+
293
+ # Adds a new error of +type+ on +attribute+.
294
+ # More than one error can be added to the same +attribute+.
295
+ # If no +type+ is supplied, <tt>:invalid</tt> is assumed.
296
+ #
297
+ # person.errors.add(:name)
298
+ # # Adds <#ActiveModel::Error attribute=name, type=invalid>
299
+ # person.errors.add(:name, :not_implemented, message: "must be implemented")
300
+ # # Adds <#ActiveModel::Error attribute=name, type=not_implemented,
301
+ # options={:message=>"must be implemented"}>
302
+ #
303
+ # person.errors.messages
304
+ # # => {:name=>["is invalid", "must be implemented"]}
305
+ #
306
+ # If +type+ is a string, it will be used as error message.
307
+ #
308
+ # If +type+ is a symbol, it will be translated using the appropriate
309
+ # scope (see +generate_message+).
310
+ #
311
+ # person.errors.add(:name, :blank)
312
+ # person.errors.messages
313
+ # # => {:name=>["can't be blank"]}
314
+ #
315
+ # person.errors.add(:name, :too_long, count: 25)
316
+ # person.errors.messages
317
+ # # => ["is too long (maximum is 25 characters)"]
318
+ #
319
+ # If +type+ is a proc, it will be called, allowing for things like
320
+ # <tt>Time.now</tt> to be used within an error.
321
+ #
322
+ # If the <tt>:strict</tt> option is set to +true+, it will raise
323
+ # ActiveModel::StrictValidationFailed instead of adding the error.
324
+ # <tt>:strict</tt> option can also be set to any other exception.
325
+ #
326
+ # person.errors.add(:name, :invalid, strict: true)
327
+ # # => ActiveModel::StrictValidationFailed: Name is invalid
328
+ # person.errors.add(:name, :invalid, strict: NameIsInvalid)
329
+ # # => NameIsInvalid: Name is invalid
330
+ #
331
+ # person.errors.messages # => {}
332
+ #
333
+ # +attribute+ should be set to <tt>:base</tt> if the error is not
334
+ # directly associated with a single attribute.
335
+ #
336
+ # person.errors.add(:base, :name_or_email_blank,
337
+ # message: "either name or email must be present")
338
+ # person.errors.messages
339
+ # # => {:base=>["either name or email must be present"]}
340
+ # person.errors.details
341
+ # # => {:base=>[{error: :name_or_email_blank}]}
342
+ def add(attribute, type = :invalid, **options)
343
+ attribute, type, options = normalize_arguments(attribute, type, **options)
344
+ error = Error.new(@base, attribute, type, **options)
345
+
346
+ if exception = options[:strict]
347
+ exception = ActiveModel::StrictValidationFailed if exception == true
348
+ raise exception, error.full_message
349
+ end
350
+
351
+ @errors.append(error)
352
+
353
+ error
354
+ end
355
+
356
+ # Returns +true+ if an error matches provided +attribute+ and +type+,
357
+ # or +false+ otherwise. +type+ is treated the same as for +add+.
358
+ #
359
+ # person.errors.add :name, :blank
360
+ # person.errors.added? :name, :blank # => true
361
+ # person.errors.added? :name, "can't be blank" # => true
362
+ #
363
+ # If the error requires options, then it returns +true+ with
364
+ # the correct options, or +false+ with incorrect or missing options.
365
+ #
366
+ # person.errors.add :name, :too_long, count: 25
367
+ # person.errors.added? :name, :too_long, count: 25 # => true
368
+ # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
369
+ # person.errors.added? :name, :too_long, count: 24 # => false
370
+ # person.errors.added? :name, :too_long # => false
371
+ # person.errors.added? :name, "is too long" # => false
372
+ def added?(attribute, type = :invalid, options = {})
373
+ attribute, type, options = normalize_arguments(attribute, type, **options)
374
+
375
+ if type.is_a? Symbol
376
+ @errors.any? { |error|
377
+ error.strict_match?(attribute, type, **options)
378
+ }
379
+ else
380
+ messages_for(attribute).include?(type)
381
+ end
382
+ end
383
+
384
+ # Returns +true+ if an error on the attribute with the given type is
385
+ # present, or +false+ otherwise. +type+ is treated the same as for +add+.
386
+ #
387
+ # person.errors.add :age
388
+ # person.errors.add :name, :too_long, count: 25
389
+ # person.errors.of_kind? :age # => true
390
+ # person.errors.of_kind? :name # => false
391
+ # person.errors.of_kind? :name, :too_long # => true
392
+ # person.errors.of_kind? :name, "is too long (maximum is 25 characters)" # => true
393
+ # person.errors.of_kind? :name, :not_too_long # => false
394
+ # person.errors.of_kind? :name, "is too long" # => false
395
+ def of_kind?(attribute, type = :invalid)
396
+ attribute, type = normalize_arguments(attribute, type)
397
+
398
+ if type.is_a? Symbol
399
+ !where(attribute, type).empty?
400
+ else
401
+ messages_for(attribute).include?(type)
402
+ end
403
+ end
404
+
405
+ # Returns all the full error messages in an array.
406
+ #
407
+ # class Person
408
+ # validates_presence_of :name, :address, :email
409
+ # validates_length_of :name, in: 5..30
410
+ # end
411
+ #
412
+ # person = Person.create(address: '123 First St.')
413
+ # person.errors.full_messages
414
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
415
+ def full_messages
416
+ @errors.map(&:full_message)
417
+ end
418
+ alias :to_a :full_messages
419
+
420
+ # Returns all the full error messages for a given attribute in an array.
421
+ #
422
+ # class Person
423
+ # validates_presence_of :name, :email
424
+ # validates_length_of :name, in: 5..30
425
+ # end
426
+ #
427
+ # person = Person.create()
428
+ # person.errors.full_messages_for(:name)
429
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
430
+ def full_messages_for(attribute)
431
+ where(attribute).map(&:full_message).freeze
432
+ end
433
+
434
+ # Returns all the error messages for a given attribute in an array.
435
+ #
436
+ # class Person
437
+ # validates_presence_of :name, :email
438
+ # validates_length_of :name, in: 5..30
439
+ # end
440
+ #
441
+ # person = Person.create()
442
+ # person.errors.messages_for(:name)
443
+ # # => ["is too short (minimum is 5 characters)", "can't be blank"]
444
+ def messages_for(attribute)
445
+ where(attribute).map(&:message)
446
+ end
447
+
448
+ # Returns a full message for a given attribute.
449
+ #
450
+ # person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
451
+ def full_message(attribute, message)
452
+ Error.full_message(attribute, message, @base)
453
+ end
454
+
455
+ # Translates an error message in its default scope
456
+ # (<tt>activemodel.errors.messages</tt>).
457
+ #
458
+ # Error messages are first looked up in <tt>activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
459
+ # if it's not there, it's looked up in <tt>activemodel.errors.models.MODEL.MESSAGE</tt> and if
460
+ # that is not there also, it returns the translation of the default message
461
+ # (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
462
+ # name, translated attribute name, and the value are available for
463
+ # interpolation.
464
+ #
465
+ # When using inheritance in your models, it will check all the inherited
466
+ # models too, but only if the model itself hasn't been found. Say you have
467
+ # <tt>class Admin < User; end</tt> and you wanted the translation for
468
+ # the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
469
+ # it looks for these translations:
470
+ #
471
+ # * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
472
+ # * <tt>activemodel.errors.models.admin.blank</tt>
473
+ # * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
474
+ # * <tt>activemodel.errors.models.user.blank</tt>
475
+ # * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
476
+ # * <tt>activemodel.errors.messages.blank</tt>
477
+ # * <tt>errors.attributes.title.blank</tt>
478
+ # * <tt>errors.messages.blank</tt>
479
+ def generate_message(attribute, type = :invalid, options = {})
480
+ Error.generate_message(attribute, type, @base, options)
481
+ end
482
+
483
+ def inspect # :nodoc:
484
+ inspection = @errors.inspect
485
+
486
+ "#<#{self.class.name} #{inspection}>"
487
+ end
488
+
489
+ private
490
+ def normalize_arguments(attribute, type, **options)
491
+ # Evaluate proc first
492
+ if type.respond_to?(:call)
493
+ type = type.call(@base, options)
494
+ end
495
+
496
+ [attribute.to_sym, type, options]
497
+ end
498
+ end
499
+
500
+ # = Active \Model \StrictValidationFailed
501
+ #
502
+ # Raised when a validation cannot be corrected by end users and are considered
503
+ # exceptional.
504
+ #
505
+ # class Person
506
+ # include ActiveModel::Validations
507
+ #
508
+ # attr_accessor :name
509
+ #
510
+ # validates_presence_of :name, strict: true
511
+ # end
512
+ #
513
+ # person = Person.new
514
+ # person.name = nil
515
+ # person.valid?
516
+ # # => ActiveModel::StrictValidationFailed: Name can't be blank
517
+ class StrictValidationFailed < StandardError
518
+ end
519
+
520
+ # = Active \Model \RangeError
521
+ #
522
+ # Raised when attribute values are out of range.
523
+ class RangeError < ::RangeError
524
+ end
525
+
526
+ # = Active \Model \UnknownAttributeError
527
+ #
528
+ # Raised when unknown attributes are supplied via mass assignment.
529
+ #
530
+ # class Person
531
+ # include ActiveModel::AttributeAssignment
532
+ # include ActiveModel::Validations
533
+ # end
534
+ #
535
+ # person = Person.new
536
+ # person.assign_attributes(name: 'Gorby')
537
+ # # => ActiveModel::UnknownAttributeError: unknown attribute 'name' for Person.
538
+ class UnknownAttributeError < NoMethodError
539
+ attr_reader :record, :attribute
540
+
541
+ def initialize(record, attribute)
542
+ @record = record
543
+ @attribute = attribute
544
+ super("unknown attribute '#{attribute}' for #{@record.class}.")
545
+ end
546
+ end
547
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ # = Active \Model \ForbiddenAttributesError
5
+ #
6
+ # Raised when forbidden attributes are used for mass assignment.
7
+ #
8
+ # class Person < ActiveRecord::Base
9
+ # end
10
+ #
11
+ # params = ActionController::Parameters.new(name: 'Bob')
12
+ # Person.new(params)
13
+ # # => ActiveModel::ForbiddenAttributesError
14
+ #
15
+ # params.permit!
16
+ # Person.new(params)
17
+ # # => #<Person id: nil, name: "Bob">
18
+ class ForbiddenAttributesError < StandardError
19
+ end
20
+
21
+ module ForbiddenAttributesProtection # :nodoc:
22
+ private
23
+ def sanitize_for_mass_assignment(attributes)
24
+ if attributes.respond_to?(:permitted?)
25
+ raise ActiveModel::ForbiddenAttributesError if !attributes.permitted?
26
+ attributes.to_h
27
+ else
28
+ attributes
29
+ end
30
+ end
31
+ alias :sanitize_forbidden_attributes :sanitize_for_mass_assignment
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ # Returns the currently loaded version of \Active \Model as a +Gem::Version+.
5
+ def self.gem_version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 8
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = "alpha1"
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end