activemodel 7.0.8 → 7.1.3.4

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -157
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -9
  5. data/lib/active_model/access.rb +16 -0
  6. data/lib/active_model/api.rb +5 -5
  7. data/lib/active_model/attribute/user_provided_default.rb +4 -0
  8. data/lib/active_model/attribute.rb +26 -1
  9. data/lib/active_model/attribute_assignment.rb +1 -1
  10. data/lib/active_model/attribute_methods.rb +102 -63
  11. data/lib/active_model/attribute_registration.rb +77 -0
  12. data/lib/active_model/attribute_set.rb +10 -1
  13. data/lib/active_model/attributes.rb +62 -45
  14. data/lib/active_model/callbacks.rb +5 -5
  15. data/lib/active_model/conversion.rb +14 -4
  16. data/lib/active_model/deprecator.rb +7 -0
  17. data/lib/active_model/dirty.rb +134 -13
  18. data/lib/active_model/error.rb +4 -3
  19. data/lib/active_model/errors.rb +37 -6
  20. data/lib/active_model/forbidden_attributes_protection.rb +2 -0
  21. data/lib/active_model/gem_version.rb +4 -4
  22. data/lib/active_model/lint.rb +1 -1
  23. data/lib/active_model/locale/en.yml +1 -0
  24. data/lib/active_model/model.rb +34 -2
  25. data/lib/active_model/naming.rb +29 -10
  26. data/lib/active_model/railtie.rb +4 -0
  27. data/lib/active_model/secure_password.rb +61 -23
  28. data/lib/active_model/serialization.rb +3 -3
  29. data/lib/active_model/serializers/json.rb +1 -1
  30. data/lib/active_model/translation.rb +18 -16
  31. data/lib/active_model/type/big_integer.rb +23 -1
  32. data/lib/active_model/type/binary.rb +7 -1
  33. data/lib/active_model/type/boolean.rb +11 -9
  34. data/lib/active_model/type/date.rb +28 -2
  35. data/lib/active_model/type/date_time.rb +45 -3
  36. data/lib/active_model/type/decimal.rb +39 -1
  37. data/lib/active_model/type/float.rb +30 -1
  38. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +5 -1
  39. data/lib/active_model/type/helpers/numeric.rb +6 -1
  40. data/lib/active_model/type/helpers/time_value.rb +28 -12
  41. data/lib/active_model/type/immutable_string.rb +37 -1
  42. data/lib/active_model/type/integer.rb +44 -1
  43. data/lib/active_model/type/serialize_cast_value.rb +47 -0
  44. data/lib/active_model/type/string.rb +9 -1
  45. data/lib/active_model/type/time.rb +48 -7
  46. data/lib/active_model/type/value.rb +17 -1
  47. data/lib/active_model/type.rb +1 -0
  48. data/lib/active_model/validations/absence.rb +1 -1
  49. data/lib/active_model/validations/acceptance.rb +1 -1
  50. data/lib/active_model/validations/callbacks.rb +4 -4
  51. data/lib/active_model/validations/clusivity.rb +5 -8
  52. data/lib/active_model/validations/comparability.rb +0 -11
  53. data/lib/active_model/validations/comparison.rb +15 -7
  54. data/lib/active_model/validations/format.rb +6 -7
  55. data/lib/active_model/validations/length.rb +10 -8
  56. data/lib/active_model/validations/numericality.rb +35 -23
  57. data/lib/active_model/validations/presence.rb +1 -1
  58. data/lib/active_model/validations/resolve_value.rb +26 -0
  59. data/lib/active_model/validations/validates.rb +4 -4
  60. data/lib/active_model/validations/with.rb +9 -2
  61. data/lib/active_model/validations.rb +44 -9
  62. data/lib/active_model/validator.rb +7 -5
  63. data/lib/active_model/version.rb +1 -1
  64. data/lib/active_model.rb +5 -1
  65. metadata +13 -8
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveModel
4
- # == Active \Model \Conversion
4
+ # = Active \Model \Conversion
5
5
  #
6
6
  # Handles default conversions: to_model, to_key, to_param, and to_partial_path.
7
7
  #
@@ -24,6 +24,14 @@ module ActiveModel
24
24
  module Conversion
25
25
  extend ActiveSupport::Concern
26
26
 
27
+ included do
28
+ ##
29
+ # :singleton-method:
30
+ #
31
+ # Accepts a string that will be used as a delimiter of object's key values in the `to_param` method.
32
+ class_attribute :param_delimiter, instance_reader: false, default: "-"
33
+ end
34
+
27
35
  # If your object is already designed to implement all of the \Active \Model
28
36
  # you can use the default <tt>:to_model</tt> implementation, which simply
29
37
  # returns +self+.
@@ -58,7 +66,7 @@ module ActiveModel
58
66
  # person.to_key # => [1]
59
67
  def to_key
60
68
  key = respond_to?(:id) && id
61
- key ? [key] : nil
69
+ key ? Array(key) : nil
62
70
  end
63
71
 
64
72
  # Returns a +string+ representing the object's key suitable for use in URLs,
@@ -80,7 +88,7 @@ module ActiveModel
80
88
  # person = Person.new(1)
81
89
  # person.to_param # => "1"
82
90
  def to_param
83
- (persisted? && key = to_key) ? key.join("-") : nil
91
+ (persisted? && (key = to_key) && key.all?) ? key.join(self.class.param_delimiter) : nil
84
92
  end
85
93
 
86
94
  # Returns a +string+ identifying the path associated with the object.
@@ -100,7 +108,9 @@ module ActiveModel
100
108
  # Provide a class level cache for #to_partial_path. This is an
101
109
  # internal method and should not be accessed directly.
102
110
  def _to_partial_path # :nodoc:
103
- @_to_partial_path ||= begin
111
+ @_to_partial_path ||= if respond_to?(:model_name)
112
+ "#{model_name.collection}/#{model_name.element}"
113
+ else
104
114
  element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
105
115
  collection = ActiveSupport::Inflector.tableize(name)
106
116
  "#{collection}/#{element}"
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ def self.deprecator # :nodoc:
5
+ @deprecator ||= ActiveSupport::Deprecation.new
6
+ end
7
+ end
@@ -3,7 +3,7 @@
3
3
  require "active_model/attribute_mutation_tracker"
4
4
 
5
5
  module ActiveModel
6
- # == Active \Model \Dirty
6
+ # = Active \Model \Dirty
7
7
  #
8
8
  # Provides a way to track changes in your object in the same way as
9
9
  # Active Record does.
@@ -13,8 +13,7 @@ module ActiveModel
13
13
  # * <tt>include ActiveModel::Dirty</tt> in your object.
14
14
  # * Call <tt>define_attribute_methods</tt> passing each method you want to
15
15
  # track.
16
- # * Call <tt>[attr_name]_will_change!</tt> before each change to the tracked
17
- # attribute.
16
+ # * Call <tt>*_will_change!</tt> before each change to the tracked attribute.
18
17
  # * Call <tt>changes_applied</tt> after the changes are persisted.
19
18
  # * Call <tt>clear_changes_information</tt> when you want to reset the changes
20
19
  # information.
@@ -109,20 +108,136 @@ module ActiveModel
109
108
  # person.changes # => {"name" => ["Bill", "Bob"]}
110
109
  #
111
110
  # If an attribute is modified in-place then make use of
112
- # <tt>[attribute_name]_will_change!</tt> to mark that the attribute is changing.
111
+ # {*_will_change!}[rdoc-label:method-i-2A_will_change-21] to mark that the attribute is changing.
113
112
  # Otherwise \Active \Model can't track changes to in-place attributes. Note
114
113
  # that Active Record can detect in-place modifications automatically. You do
115
- # not need to call <tt>[attribute_name]_will_change!</tt> on Active Record models.
114
+ # not need to call <tt>*_will_change!</tt> on Active Record models.
116
115
  #
117
116
  # person.name_will_change!
118
117
  # person.name_change # => ["Bill", "Bill"]
119
118
  # person.name << 'y'
120
119
  # person.name_change # => ["Bill", "Billy"]
120
+ #
121
+ # Methods can be invoked as +name_changed?+ or by passing an argument to the
122
+ # generic method <tt>attribute_changed?("name")</tt>.
121
123
  module Dirty
122
124
  extend ActiveSupport::Concern
123
125
  include ActiveModel::AttributeMethods
124
126
 
125
127
  included do
128
+ ##
129
+ # :method: *_previously_changed?
130
+ #
131
+ # :call-seq: *_previously_changed?(**options)
132
+ #
133
+ # This method is generated for each attribute.
134
+ #
135
+ # Returns true if the attribute previously had unsaved changes.
136
+ #
137
+ # person = Person.new
138
+ # person.name = 'Britanny'
139
+ # person.save
140
+ # person.name_previously_changed? # => true
141
+ # person.name_previously_changed?(from: nil, to: 'Britanny') # => true
142
+
143
+ ##
144
+ # :method: *_changed?
145
+ #
146
+ # This method is generated for each attribute.
147
+ #
148
+ # Returns true if the attribute has unsaved changes.
149
+ #
150
+ # person = Person.new
151
+ # person.name = 'Andrew'
152
+ # person.name_changed? # => true
153
+
154
+ ##
155
+ # :method: *_change
156
+ #
157
+ # This method is generated for each attribute.
158
+ #
159
+ # Returns the old and the new value of the attribute.
160
+ #
161
+ # person = Person.new
162
+ # person.name = 'Nick'
163
+ # person.name_change # => [nil, 'Nick']
164
+
165
+ ##
166
+ # :method: *_will_change!
167
+ #
168
+ # This method is generated for each attribute.
169
+ #
170
+ # If an attribute is modified in-place then make use of
171
+ # <tt>*_will_change!</tt> to mark that the attribute is changing.
172
+ # Otherwise Active Model can’t track changes to in-place attributes. Note
173
+ # that Active Record can detect in-place modifications automatically. You
174
+ # do not need to call <tt>*_will_change!</tt> on Active Record
175
+ # models.
176
+ #
177
+ # person = Person.new('Sandy')
178
+ # person.name_will_change!
179
+ # person.name_change # => ['Sandy', 'Sandy']
180
+
181
+ ##
182
+ # :method: *_was
183
+ #
184
+ # This method is generated for each attribute.
185
+ #
186
+ # Returns the old value of the attribute.
187
+ #
188
+ # person = Person.new(name: 'Steph')
189
+ # person.name = 'Stephanie'
190
+ # person.name_was # => 'Steph'
191
+
192
+ ##
193
+ # :method: *_previous_change
194
+ #
195
+ # This method is generated for each attribute.
196
+ #
197
+ # Returns the old and the new value of the attribute before the last save.
198
+ #
199
+ # person = Person.new
200
+ # person.name = 'Emmanuel'
201
+ # person.save
202
+ # person.name_previous_change # => [nil, 'Emmanuel']
203
+
204
+ ##
205
+ # :method: *_previously_was
206
+ #
207
+ # This method is generated for each attribute.
208
+ #
209
+ # Returns the old value of the attribute before the last save.
210
+ #
211
+ # person = Person.new
212
+ # person.name = 'Sage'
213
+ # person.save
214
+ # person.name_previously_was # => nil
215
+
216
+ ##
217
+ # :method: restore_*!
218
+ #
219
+ # This method is generated for each attribute.
220
+ #
221
+ # Restores the attribute to the old value.
222
+ #
223
+ # person = Person.new
224
+ # person.name = 'Amanda'
225
+ # person.restore_name!
226
+ # person.name # => nil
227
+
228
+ ##
229
+ # :method: clear_*_change
230
+ #
231
+ # This method is generated for each attribute.
232
+ #
233
+ # Clears all dirty data of the attribute: current changes and previous changes.
234
+ #
235
+ # person = Person.new(name: 'Chris')
236
+ # person.name = 'Jason'
237
+ # person.name_change # => ['Chris', 'Jason']
238
+ # person.clear_name_change
239
+ # person.name_change # => nil
240
+
126
241
  attribute_method_suffix "_previously_changed?", "_changed?", parameters: "**options"
127
242
  attribute_method_suffix "_change", "_will_change!", "_was", parameters: false
128
243
  attribute_method_suffix "_previous_change", "_previously_was", parameters: false
@@ -174,23 +289,23 @@ module ActiveModel
174
289
  mutations_from_database.changed_attribute_names
175
290
  end
176
291
 
177
- # Dispatch target for <tt>*_changed?</tt> attribute methods.
178
- def attribute_changed?(attr_name, **options) # :nodoc:
292
+ # Dispatch target for {*_changed?}[rdoc-label:method-i-2A_changed-3F] attribute methods.
293
+ def attribute_changed?(attr_name, **options)
179
294
  mutations_from_database.changed?(attr_name.to_s, **options)
180
295
  end
181
296
 
182
- # Dispatch target for <tt>*_was</tt> attribute methods.
183
- def attribute_was(attr_name) # :nodoc:
297
+ # Dispatch target for {*_was}[rdoc-label:method-i-2A_was] attribute methods.
298
+ def attribute_was(attr_name)
184
299
  mutations_from_database.original_value(attr_name.to_s)
185
300
  end
186
301
 
187
- # Dispatch target for <tt>*_previously_changed?</tt> attribute methods.
188
- def attribute_previously_changed?(attr_name, **options) # :nodoc:
302
+ # Dispatch target for {*_previously_changed?}[rdoc-label:method-i-2A_previously_changed-3F] attribute methods.
303
+ def attribute_previously_changed?(attr_name, **options)
189
304
  mutations_before_last_save.changed?(attr_name.to_s, **options)
190
305
  end
191
306
 
192
- # Dispatch target for <tt>*_previously_was</tt> attribute methods.
193
- def attribute_previously_was(attr_name) # :nodoc:
307
+ # Dispatch target for {*_previously_was}[rdoc-label:method-i-2A_previously_was] attribute methods.
308
+ def attribute_previously_was(attr_name)
194
309
  mutations_before_last_save.original_value(attr_name.to_s)
195
310
  end
196
311
 
@@ -247,6 +362,12 @@ module ActiveModel
247
362
  end
248
363
 
249
364
  private
365
+ def init_internals
366
+ super
367
+ @mutations_before_last_save = nil
368
+ @mutations_from_database = nil
369
+ end
370
+
250
371
  def clear_attribute_change(attr_name)
251
372
  mutations_from_database.forget_change(attr_name.to_s)
252
373
  end
@@ -3,7 +3,7 @@
3
3
  require "active_support/core_ext/class/attribute"
4
4
 
5
5
  module ActiveModel
6
- # == Active \Model \Error
6
+ # = Active \Model \Error
7
7
  #
8
8
  # Represents one single error
9
9
  class Error
@@ -121,9 +121,10 @@ module ActiveModel
121
121
  attr_reader :attribute
122
122
  # The type of error, defaults to +:invalid+ unless specified
123
123
  attr_reader :type
124
- # The raw value provided as the second parameter when calling +errors#add+
124
+ # The raw value provided as the second parameter when calling
125
+ # <tt>errors#add</tt>
125
126
  attr_reader :raw_type
126
- # The options provided when calling +errors#add+
127
+ # The options provided when calling <tt>errors#add</tt>
127
128
  attr_reader :options
128
129
 
129
130
  # Returns the error message.
@@ -3,13 +3,12 @@
3
3
  require "active_support/core_ext/array/conversions"
4
4
  require "active_support/core_ext/string/inflections"
5
5
  require "active_support/core_ext/object/deep_dup"
6
- require "active_support/core_ext/string/filters"
7
6
  require "active_model/error"
8
7
  require "active_model/nested_error"
9
8
  require "forwardable"
10
9
 
11
10
  module ActiveModel
12
- # == Active \Model \Errors
11
+ # = Active \Model \Errors
13
12
  #
14
13
  # Provides error related functionalities you can include in your object
15
14
  # for handling error messages and interacting with Action View helpers.
@@ -64,6 +63,7 @@ module ActiveModel
64
63
 
65
64
  extend Forwardable
66
65
 
66
+ ##
67
67
  # :method: each
68
68
  #
69
69
  # :call-seq: each(&block)
@@ -75,6 +75,31 @@ module ActiveModel
75
75
  # # Will yield <#ActiveModel::Error attribute=name, type=too_short,
76
76
  # options={:count=>3}>
77
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
+
78
103
  def_delegators :@errors, :each, :clear, :empty?, :size, :uniq!
79
104
 
80
105
  # The actual array of +Error+ objects
@@ -215,7 +240,7 @@ module ActiveModel
215
240
 
216
241
  # Returns a Hash that can be used as the JSON representation for this
217
242
  # object. You can pass the <tt>:full_messages</tt> option. This determines
218
- # if the json object should contain full messages or not (false by default).
243
+ # if the JSON object should contain full messages or not (false by default).
219
244
  #
220
245
  # person.errors.as_json # => {:name=>["cannot be nil"]}
221
246
  # person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}
@@ -287,7 +312,7 @@ module ActiveModel
287
312
  # person.errors.messages
288
313
  # # => {:name=>["can't be blank"]}
289
314
  #
290
- # person.errors.add(:name, :too_long, { count: 25 })
315
+ # person.errors.add(:name, :too_long, count: 25)
291
316
  # person.errors.messages
292
317
  # # => ["is too long (maximum is 25 characters)"]
293
318
  #
@@ -338,7 +363,7 @@ module ActiveModel
338
363
  # If the error requires options, then it returns +true+ with
339
364
  # the correct options, or +false+ with incorrect or missing options.
340
365
  #
341
- # person.errors.add :name, :too_long, { count: 25 }
366
+ # person.errors.add :name, :too_long, count: 25
342
367
  # person.errors.added? :name, :too_long, count: 25 # => true
343
368
  # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
344
369
  # person.errors.added? :name, :too_long, count: 24 # => false
@@ -360,7 +385,7 @@ module ActiveModel
360
385
  # present, or +false+ otherwise. +type+ is treated the same as for +add+.
361
386
  #
362
387
  # person.errors.add :age
363
- # person.errors.add :name, :too_long, { count: 25 }
388
+ # person.errors.add :name, :too_long, count: 25
364
389
  # person.errors.of_kind? :age # => true
365
390
  # person.errors.of_kind? :name # => false
366
391
  # person.errors.of_kind? :name, :too_long # => true
@@ -472,6 +497,8 @@ module ActiveModel
472
497
  end
473
498
  end
474
499
 
500
+ # = Active \Model \StrictValidationFailed
501
+ #
475
502
  # Raised when a validation cannot be corrected by end users and are considered
476
503
  # exceptional.
477
504
  #
@@ -490,10 +517,14 @@ module ActiveModel
490
517
  class StrictValidationFailed < StandardError
491
518
  end
492
519
 
520
+ # = Active \Model \RangeError
521
+ #
493
522
  # Raised when attribute values are out of range.
494
523
  class RangeError < ::RangeError
495
524
  end
496
525
 
526
+ # = Active \Model \UnknownAttributeError
527
+ #
497
528
  # Raised when unknown attributes are supplied via mass assignment.
498
529
  #
499
530
  # class Person
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveModel
4
+ # = Active \Model \ForbiddenAttributesError
5
+ #
4
6
  # Raised when forbidden attributes are used for mass assignment.
5
7
  #
6
8
  # class Person < ActiveRecord::Base
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveModel
4
- # Returns the currently loaded version of \Active \Model as a <tt>Gem::Version</tt>.
4
+ # Returns the currently loaded version of \Active \Model as a +Gem::Version+.
5
5
  def self.gem_version
6
6
  Gem::Version.new VERSION::STRING
7
7
  end
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 7
11
- MINOR = 0
12
- TINY = 8
13
- PRE = nil
11
+ MINOR = 1
12
+ TINY = 3
13
+ PRE = "4"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -5,7 +5,7 @@ module ActiveModel
5
5
  # == Active \Model \Lint \Tests
6
6
  #
7
7
  # You can test whether an object is compliant with the Active \Model API by
8
- # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will
8
+ # including +ActiveModel::Lint::Tests+ in your TestCase. It will
9
9
  # include tests that tell you whether your object is fully compliant,
10
10
  # or if not, which aspects of the API are not implemented.
11
11
  #
@@ -18,6 +18,7 @@ en:
18
18
  too_long:
19
19
  one: "is too long (maximum is 1 character)"
20
20
  other: "is too long (maximum is %{count} characters)"
21
+ password_too_long: "is too long"
21
22
  too_short:
22
23
  one: "is too short (minimum is 1 character)"
23
24
  other: "is too short (minimum is %{count} characters)"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveModel
4
- # == Active \Model \Basic \Model
4
+ # = Active \Model \Basic \Model
5
5
  #
6
6
  # Allows implementing models similar to ActiveRecord::Base.
7
7
  # Includes ActiveModel::API for the required interface for an
@@ -37,10 +37,42 @@ module ActiveModel
37
37
  # person.omg # => true
38
38
  #
39
39
  # For more detailed information on other functionalities available, please
40
- # refer to the specific modules included in <tt>ActiveModel::Model</tt>
40
+ # refer to the specific modules included in +ActiveModel::Model+
41
41
  # (see below).
42
42
  module Model
43
43
  extend ActiveSupport::Concern
44
44
  include ActiveModel::API
45
+ include ActiveModel::Access
46
+
47
+ ##
48
+ # :method: slice
49
+ #
50
+ # :call-seq: slice(*methods)
51
+ #
52
+ # Returns a hash of the given methods with their names as keys and returned
53
+ # values as values.
54
+ #
55
+ # person = Person.new(id: 1, name: "bob")
56
+ # person.slice(:id, :name)
57
+ # => { "id" => 1, "name" => "bob" }
58
+ #
59
+ #--
60
+ # Implemented by ActiveModel::Access#slice.
61
+
62
+ ##
63
+ # :method: values_at
64
+ #
65
+ # :call-seq: values_at(*methods)
66
+ #
67
+ # Returns an array of the values returned by the given methods.
68
+ #
69
+ # person = Person.new(id: 1, name: "bob")
70
+ # person.values_at(:id, :name)
71
+ # => [1, "bob"]
72
+ #
73
+ #--
74
+ # Implemented by ActiveModel::Access#values_at.
45
75
  end
76
+
77
+ ActiveSupport.run_load_hooks(:active_model, Model)
46
78
  end
@@ -195,18 +195,15 @@ module ActiveModel
195
195
  #
196
196
  # Specify +options+ with additional translating options.
197
197
  def human(options = {})
198
- return @human unless @klass.respond_to?(:lookup_ancestors) &&
199
- @klass.respond_to?(:i18n_scope)
200
-
201
- defaults = @klass.lookup_ancestors.map do |klass|
202
- klass.model_name.i18n_key
203
- end
198
+ return @human if i18n_keys.empty? || i18n_scope.empty?
204
199
 
200
+ key, *defaults = i18n_keys
205
201
  defaults << options[:default] if options[:default]
206
- defaults << @human
202
+ defaults << MISSING_TRANSLATION
207
203
 
208
- options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default))
209
- I18n.translate(defaults.shift, **options)
204
+ translation = I18n.translate(key, scope: i18n_scope, count: 1, **options, default: defaults)
205
+ translation = @human if translation == MISSING_TRANSLATION
206
+ translation
210
207
  end
211
208
 
212
209
  def uncountable?
@@ -214,12 +211,26 @@ module ActiveModel
214
211
  end
215
212
 
216
213
  private
214
+ MISSING_TRANSLATION = -(2**60) # :nodoc:
215
+
217
216
  def _singularize(string)
218
217
  ActiveSupport::Inflector.underscore(string).tr("/", "_")
219
218
  end
219
+
220
+ def i18n_keys
221
+ @i18n_keys ||= if @klass.respond_to?(:lookup_ancestors)
222
+ @klass.lookup_ancestors.map { |klass| klass.model_name.i18n_key }
223
+ else
224
+ []
225
+ end
226
+ end
227
+
228
+ def i18n_scope
229
+ @i18n_scope ||= @klass.respond_to?(:i18n_scope) ? [@klass.i18n_scope, :models] : []
230
+ end
220
231
  end
221
232
 
222
- # == Active \Model \Naming
233
+ # = Active \Model \Naming
223
234
  #
224
235
  # Creates a +model_name+ method on your object.
225
236
  #
@@ -336,5 +347,13 @@ module ActiveModel
336
347
  end
337
348
  end
338
349
  private_class_method :model_name_from_record_or_class
350
+
351
+ private
352
+ def inherited(base)
353
+ super
354
+ base.class_eval do
355
+ @_model_name = nil
356
+ end
357
+ end
339
358
  end
340
359
  end
@@ -9,6 +9,10 @@ module ActiveModel
9
9
 
10
10
  config.active_model = ActiveSupport::OrderedOptions.new
11
11
 
12
+ initializer "active_model.deprecator", before: :load_environment_config do |app|
13
+ app.deprecators[:active_model] = ActiveModel.deprecator
14
+ end
15
+
12
16
  initializer "active_model.secure_password" do
13
17
  ActiveModel::SecurePassword.min_cost = Rails.env.test?
14
18
  end