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,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