activemodel 4.2.0 → 6.1.0
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +49 -37
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -22
- data/lib/active_model/attribute/user_provided_default.rb +51 -0
- data/lib/active_model/attribute.rb +248 -0
- data/lib/active_model/attribute_assignment.rb +55 -0
- data/lib/active_model/attribute_methods.rb +150 -73
- data/lib/active_model/attribute_mutation_tracker.rb +181 -0
- data/lib/active_model/attribute_set/builder.rb +191 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +106 -0
- data/lib/active_model/attributes.rb +132 -0
- data/lib/active_model/callbacks.rb +31 -25
- data/lib/active_model/conversion.rb +20 -9
- data/lib/active_model/dirty.rb +142 -116
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +436 -202
- data/lib/active_model/forbidden_attributes_protection.rb +6 -3
- data/lib/active_model/gem_version.rb +5 -3
- data/lib/active_model/lint.rb +47 -42
- data/lib/active_model/locale/en.yml +2 -1
- data/lib/active_model/model.rb +7 -7
- data/lib/active_model/naming.rb +36 -18
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +8 -0
- data/lib/active_model/secure_password.rb +61 -67
- data/lib/active_model/serialization.rb +48 -17
- data/lib/active_model/serializers/json.rb +22 -13
- data/lib/active_model/translation.rb +5 -4
- data/lib/active_model/type/big_integer.rb +14 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +46 -0
- data/lib/active_model/type/date.rb +52 -0
- data/lib/active_model/type/date_time.rb +46 -0
- data/lib/active_model/type/decimal.rb +69 -0
- data/lib/active_model/type/float.rb +35 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +49 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +48 -0
- data/lib/active_model/type/helpers/time_value.rb +90 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +35 -0
- data/lib/active_model/type/integer.rb +67 -0
- data/lib/active_model/type/registry.rb +70 -0
- data/lib/active_model/type/string.rb +35 -0
- data/lib/active_model/type/time.rb +46 -0
- data/lib/active_model/type/value.rb +133 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/validations/absence.rb +6 -4
- data/lib/active_model/validations/acceptance.rb +72 -14
- data/lib/active_model/validations/callbacks.rb +23 -19
- data/lib/active_model/validations/clusivity.rb +18 -12
- data/lib/active_model/validations/confirmation.rb +27 -14
- data/lib/active_model/validations/exclusion.rb +7 -4
- data/lib/active_model/validations/format.rb +27 -27
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +8 -7
- data/lib/active_model/validations/length.rb +35 -32
- data/lib/active_model/validations/numericality.rb +72 -34
- data/lib/active_model/validations/presence.rb +3 -3
- data/lib/active_model/validations/validates.rb +17 -15
- data/lib/active_model/validations/with.rb +6 -12
- data/lib/active_model/validations.rb +58 -23
- data/lib/active_model/validator.rb +23 -17
- data/lib/active_model/version.rb +4 -2
- data/lib/active_model.rb +18 -11
- metadata +44 -25
- data/lib/active_model/serializers/xml.rb +0 -238
- data/lib/active_model/test_case.rb +0 -4
data/lib/active_model/dirty.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/attribute_mutation_tracker"
|
4
4
|
|
5
5
|
module ActiveModel
|
6
6
|
# == Active \Model \Dirty
|
@@ -13,7 +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>
|
16
|
+
# * Call <tt>[attr_name]_will_change!</tt> before each change to the tracked
|
17
17
|
# attribute.
|
18
18
|
# * Call <tt>changes_applied</tt> after the changes are persisted.
|
19
19
|
# * Call <tt>clear_changes_information</tt> when you want to reset the changes
|
@@ -27,6 +27,10 @@ module ActiveModel
|
|
27
27
|
#
|
28
28
|
# define_attribute_methods :name
|
29
29
|
#
|
30
|
+
# def initialize
|
31
|
+
# @name = nil
|
32
|
+
# end
|
33
|
+
#
|
30
34
|
# def name
|
31
35
|
# @name
|
32
36
|
# end
|
@@ -53,80 +57,106 @@ module ActiveModel
|
|
53
57
|
# end
|
54
58
|
# end
|
55
59
|
#
|
56
|
-
# A newly instantiated object is unchanged:
|
60
|
+
# A newly instantiated +Person+ object is unchanged:
|
57
61
|
#
|
58
|
-
# person = Person.
|
59
|
-
# person.changed?
|
62
|
+
# person = Person.new
|
63
|
+
# person.changed? # => false
|
60
64
|
#
|
61
65
|
# Change the name:
|
62
66
|
#
|
63
67
|
# person.name = 'Bob'
|
64
68
|
# person.changed? # => true
|
65
69
|
# person.name_changed? # => true
|
66
|
-
# person.name_changed?(from:
|
67
|
-
# person.name_was # =>
|
68
|
-
# person.name_change # => [
|
70
|
+
# person.name_changed?(from: nil, to: "Bob") # => true
|
71
|
+
# person.name_was # => nil
|
72
|
+
# person.name_change # => [nil, "Bob"]
|
69
73
|
# person.name = 'Bill'
|
70
|
-
# person.name_change # => [
|
74
|
+
# person.name_change # => [nil, "Bill"]
|
71
75
|
#
|
72
76
|
# Save the changes:
|
73
77
|
#
|
74
78
|
# person.save
|
75
|
-
# person.changed?
|
76
|
-
# person.name_changed?
|
79
|
+
# person.changed? # => false
|
80
|
+
# person.name_changed? # => false
|
77
81
|
#
|
78
82
|
# Reset the changes:
|
79
83
|
#
|
80
|
-
# person.previous_changes
|
84
|
+
# person.previous_changes # => {"name" => [nil, "Bill"]}
|
85
|
+
# person.name_previously_changed? # => true
|
86
|
+
# person.name_previously_changed?(from: nil, to: "Bill") # => true
|
87
|
+
# person.name_previous_change # => [nil, "Bill"]
|
88
|
+
# person.name_previously_was # => nil
|
81
89
|
# person.reload!
|
82
|
-
# person.previous_changes
|
90
|
+
# person.previous_changes # => {}
|
83
91
|
#
|
84
92
|
# Rollback the changes:
|
85
93
|
#
|
86
94
|
# person.name = "Uncle Bob"
|
87
95
|
# person.rollback!
|
88
|
-
# person.name
|
89
|
-
# person.name_changed?
|
96
|
+
# person.name # => "Bill"
|
97
|
+
# person.name_changed? # => false
|
90
98
|
#
|
91
99
|
# Assigning the same value leaves the attribute unchanged:
|
92
100
|
#
|
93
101
|
# person.name = 'Bill'
|
94
|
-
# person.name_changed?
|
95
|
-
# person.name_change
|
102
|
+
# person.name_changed? # => false
|
103
|
+
# person.name_change # => nil
|
96
104
|
#
|
97
105
|
# Which attributes have changed?
|
98
106
|
#
|
99
107
|
# person.name = 'Bob'
|
100
|
-
# person.changed
|
101
|
-
# person.changes
|
108
|
+
# person.changed # => ["name"]
|
109
|
+
# person.changes # => {"name" => ["Bill", "Bob"]}
|
102
110
|
#
|
103
111
|
# If an attribute is modified in-place then make use of
|
104
|
-
#
|
105
|
-
# Otherwise Active Model can't track changes to in-place attributes. Note
|
112
|
+
# <tt>[attribute_name]_will_change!</tt> to mark that the attribute is changing.
|
113
|
+
# Otherwise \Active \Model can't track changes to in-place attributes. Note
|
106
114
|
# that Active Record can detect in-place modifications automatically. You do
|
107
|
-
# not need to call
|
115
|
+
# not need to call <tt>[attribute_name]_will_change!</tt> on Active Record models.
|
108
116
|
#
|
109
117
|
# person.name_will_change!
|
110
|
-
# person.name_change
|
118
|
+
# person.name_change # => ["Bill", "Bill"]
|
111
119
|
# person.name << 'y'
|
112
|
-
# person.name_change
|
120
|
+
# person.name_change # => ["Bill", "Billy"]
|
113
121
|
module Dirty
|
114
122
|
extend ActiveSupport::Concern
|
115
123
|
include ActiveModel::AttributeMethods
|
116
124
|
|
117
125
|
included do
|
118
|
-
attribute_method_suffix
|
119
|
-
|
120
|
-
attribute_method_affix prefix:
|
126
|
+
attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
|
127
|
+
attribute_method_suffix "_previously_changed?", "_previous_change", "_previously_was"
|
128
|
+
attribute_method_affix prefix: "restore_", suffix: "!"
|
129
|
+
attribute_method_affix prefix: "clear_", suffix: "_change"
|
130
|
+
end
|
131
|
+
|
132
|
+
def initialize_dup(other) # :nodoc:
|
133
|
+
super
|
134
|
+
if self.class.respond_to?(:_default_attributes)
|
135
|
+
@attributes = self.class._default_attributes.map do |attr|
|
136
|
+
attr.with_value_from_user(@attributes.fetch_value(attr.name))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
@mutations_from_database = nil
|
140
|
+
end
|
141
|
+
|
142
|
+
# Clears dirty data and moves +changes+ to +previous_changes+ and
|
143
|
+
# +mutations_from_database+ to +mutations_before_last_save+ respectively.
|
144
|
+
def changes_applied
|
145
|
+
unless defined?(@attributes)
|
146
|
+
mutations_from_database.finalize_changes
|
147
|
+
end
|
148
|
+
@mutations_before_last_save = mutations_from_database
|
149
|
+
forget_attribute_assignments
|
150
|
+
@mutations_from_database = nil
|
121
151
|
end
|
122
152
|
|
123
|
-
# Returns +true+ if any
|
153
|
+
# Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
|
124
154
|
#
|
125
155
|
# person.changed? # => false
|
126
156
|
# person.name = 'bob'
|
127
157
|
# person.changed? # => true
|
128
158
|
def changed?
|
129
|
-
|
159
|
+
mutations_from_database.any_changes?
|
130
160
|
end
|
131
161
|
|
132
162
|
# Returns an array with the name of the attributes with unsaved changes.
|
@@ -135,7 +165,55 @@ module ActiveModel
|
|
135
165
|
# person.name = 'bob'
|
136
166
|
# person.changed # => ["name"]
|
137
167
|
def changed
|
138
|
-
|
168
|
+
mutations_from_database.changed_attribute_names
|
169
|
+
end
|
170
|
+
|
171
|
+
# Dispatch target for <tt>*_changed?</tt> attribute methods.
|
172
|
+
def attribute_changed?(attr_name, **options) # :nodoc:
|
173
|
+
mutations_from_database.changed?(attr_name.to_s, **options)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Dispatch target for <tt>*_was</tt> attribute methods.
|
177
|
+
def attribute_was(attr_name) # :nodoc:
|
178
|
+
mutations_from_database.original_value(attr_name.to_s)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Dispatch target for <tt>*_previously_changed?</tt> attribute methods.
|
182
|
+
def attribute_previously_changed?(attr_name, **options) # :nodoc:
|
183
|
+
mutations_before_last_save.changed?(attr_name.to_s, **options)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Dispatch target for <tt>*_previously_was</tt> attribute methods.
|
187
|
+
def attribute_previously_was(attr_name) # :nodoc:
|
188
|
+
mutations_before_last_save.original_value(attr_name.to_s)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Restore all previous data of the provided attributes.
|
192
|
+
def restore_attributes(attr_names = changed)
|
193
|
+
attr_names.each { |attr_name| restore_attribute!(attr_name) }
|
194
|
+
end
|
195
|
+
|
196
|
+
# Clears all dirty data: current changes and previous changes.
|
197
|
+
def clear_changes_information
|
198
|
+
@mutations_before_last_save = nil
|
199
|
+
forget_attribute_assignments
|
200
|
+
@mutations_from_database = nil
|
201
|
+
end
|
202
|
+
|
203
|
+
def clear_attribute_changes(attr_names)
|
204
|
+
attr_names.each do |attr_name|
|
205
|
+
clear_attribute_change(attr_name)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Returns a hash of the attributes with unsaved changes indicating their original
|
210
|
+
# values like <tt>attr => original value</tt>.
|
211
|
+
#
|
212
|
+
# person.name # => "bob"
|
213
|
+
# person.name = 'robert'
|
214
|
+
# person.changed_attributes # => {"name" => "bob"}
|
215
|
+
def changed_attributes
|
216
|
+
mutations_from_database.changed_values
|
139
217
|
end
|
140
218
|
|
141
219
|
# Returns a hash of changed attributes indicating their original
|
@@ -145,7 +223,7 @@ module ActiveModel
|
|
145
223
|
# person.name = 'bob'
|
146
224
|
# person.changes # => { "name" => ["bill", "bob"] }
|
147
225
|
def changes
|
148
|
-
|
226
|
+
mutations_from_database.changes
|
149
227
|
end
|
150
228
|
|
151
229
|
# Returns a hash of attributes that were changed before the model was saved.
|
@@ -155,108 +233,56 @@ module ActiveModel
|
|
155
233
|
# person.save
|
156
234
|
# person.previous_changes # => {"name" => ["bob", "robert"]}
|
157
235
|
def previous_changes
|
158
|
-
|
159
|
-
end
|
160
|
-
|
161
|
-
# Returns a hash of the attributes with unsaved changes indicating their original
|
162
|
-
# values like <tt>attr => original value</tt>.
|
163
|
-
#
|
164
|
-
# person.name # => "bob"
|
165
|
-
# person.name = 'robert'
|
166
|
-
# person.changed_attributes # => {"name" => "bob"}
|
167
|
-
def changed_attributes
|
168
|
-
@changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
|
169
|
-
end
|
170
|
-
|
171
|
-
# Handle <tt>*_changed?</tt> for +method_missing+.
|
172
|
-
def attribute_changed?(attr, options = {}) #:nodoc:
|
173
|
-
result = changed_attributes.include?(attr)
|
174
|
-
result &&= options[:to] == __send__(attr) if options.key?(:to)
|
175
|
-
result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
|
176
|
-
result
|
236
|
+
mutations_before_last_save.changes
|
177
237
|
end
|
178
238
|
|
179
|
-
|
180
|
-
|
181
|
-
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
|
182
|
-
end
|
183
|
-
|
184
|
-
# Restore all previous data of the provided attributes.
|
185
|
-
def restore_attributes(attributes = changed)
|
186
|
-
attributes.each { |attr| restore_attribute! attr }
|
239
|
+
def attribute_changed_in_place?(attr_name) # :nodoc:
|
240
|
+
mutations_from_database.changed_in_place?(attr_name.to_s)
|
187
241
|
end
|
188
242
|
|
189
243
|
private
|
190
|
-
|
191
|
-
|
192
|
-
def changes_applied # :doc:
|
193
|
-
@previously_changed = changes
|
194
|
-
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
244
|
+
def clear_attribute_change(attr_name)
|
245
|
+
mutations_from_database.forget_change(attr_name.to_s)
|
195
246
|
end
|
196
247
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
248
|
+
def mutations_from_database
|
249
|
+
@mutations_from_database ||= if defined?(@attributes)
|
250
|
+
ActiveModel::AttributeMutationTracker.new(@attributes)
|
251
|
+
else
|
252
|
+
ActiveModel::ForcedMutationTracker.new(self)
|
253
|
+
end
|
201
254
|
end
|
202
255
|
|
203
|
-
def
|
204
|
-
|
205
|
-
`#reset_changes` is deprecated and will be removed on Rails 5.
|
206
|
-
Please use `#clear_changes_information` instead.
|
207
|
-
MSG
|
208
|
-
|
209
|
-
clear_changes_information
|
256
|
+
def forget_attribute_assignments
|
257
|
+
@attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes)
|
210
258
|
end
|
211
259
|
|
212
|
-
|
213
|
-
|
214
|
-
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
|
260
|
+
def mutations_before_last_save
|
261
|
+
@mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
|
215
262
|
end
|
216
263
|
|
217
|
-
#
|
218
|
-
def
|
219
|
-
|
220
|
-
|
221
|
-
begin
|
222
|
-
value = __send__(attr)
|
223
|
-
value = value.duplicable? ? value.clone : value
|
224
|
-
rescue TypeError, NoMethodError
|
225
|
-
end
|
226
|
-
|
227
|
-
set_attribute_was(attr, value)
|
264
|
+
# Dispatch target for <tt>*_change</tt> attribute methods.
|
265
|
+
def attribute_change(attr_name)
|
266
|
+
mutations_from_database.change_to_attribute(attr_name.to_s)
|
228
267
|
end
|
229
268
|
|
230
|
-
#
|
231
|
-
def
|
232
|
-
|
233
|
-
`#reset_#{attr}!` is deprecated and will be removed on Rails 5.
|
234
|
-
Please use `#restore_#{attr}!` instead.
|
235
|
-
MSG
|
236
|
-
|
237
|
-
restore_attribute!(attr)
|
269
|
+
# Dispatch target for <tt>*_previous_change</tt> attribute methods.
|
270
|
+
def attribute_previous_change(attr_name)
|
271
|
+
mutations_before_last_save.change_to_attribute(attr_name.to_s)
|
238
272
|
end
|
239
273
|
|
240
|
-
#
|
241
|
-
def
|
242
|
-
|
243
|
-
__send__("#{attr}=", changed_attributes[attr])
|
244
|
-
clear_attribute_changes([attr])
|
245
|
-
end
|
274
|
+
# Dispatch target for <tt>*_will_change!</tt> attribute methods.
|
275
|
+
def attribute_will_change!(attr_name)
|
276
|
+
mutations_from_database.force_change(attr_name.to_s)
|
246
277
|
end
|
247
278
|
|
248
|
-
#
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
end
|
256
|
-
|
257
|
-
# Remove changes information for the provided attributes.
|
258
|
-
def clear_attribute_changes(attributes) # :doc:
|
259
|
-
attributes_changed_by_setter.except!(*attributes)
|
279
|
+
# Dispatch target for <tt>restore_*!</tt> attribute methods.
|
280
|
+
def restore_attribute!(attr_name)
|
281
|
+
attr_name = attr_name.to_s
|
282
|
+
if attribute_changed?(attr_name)
|
283
|
+
__send__("#{attr_name}=", attribute_was(attr_name))
|
284
|
+
clear_attribute_change(attr_name)
|
285
|
+
end
|
260
286
|
end
|
261
287
|
end
|
262
288
|
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/class/attribute"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
# == Active \Model \Error
|
7
|
+
#
|
8
|
+
# Represents one single error
|
9
|
+
class Error
|
10
|
+
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
|
11
|
+
MESSAGE_OPTIONS = [:message]
|
12
|
+
|
13
|
+
class_attribute :i18n_customize_full_message, default: false
|
14
|
+
|
15
|
+
def self.full_message(attribute, message, base) # :nodoc:
|
16
|
+
return message if attribute == :base
|
17
|
+
|
18
|
+
base_class = base.class
|
19
|
+
attribute = attribute.to_s
|
20
|
+
|
21
|
+
if i18n_customize_full_message && base_class.respond_to?(:i18n_scope)
|
22
|
+
attribute = attribute.remove(/\[\d+\]/)
|
23
|
+
parts = attribute.split(".")
|
24
|
+
attribute_name = parts.pop
|
25
|
+
namespace = parts.join("/") unless parts.empty?
|
26
|
+
attributes_scope = "#{base_class.i18n_scope}.errors.models"
|
27
|
+
|
28
|
+
if namespace
|
29
|
+
defaults = base_class.lookup_ancestors.map do |klass|
|
30
|
+
[
|
31
|
+
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.attributes.#{attribute_name}.format",
|
32
|
+
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.format",
|
33
|
+
]
|
34
|
+
end
|
35
|
+
else
|
36
|
+
defaults = base_class.lookup_ancestors.map do |klass|
|
37
|
+
[
|
38
|
+
:"#{attributes_scope}.#{klass.model_name.i18n_key}.attributes.#{attribute_name}.format",
|
39
|
+
:"#{attributes_scope}.#{klass.model_name.i18n_key}.format",
|
40
|
+
]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
defaults.flatten!
|
45
|
+
else
|
46
|
+
defaults = []
|
47
|
+
end
|
48
|
+
|
49
|
+
defaults << :"errors.format"
|
50
|
+
defaults << "%{attribute} %{message}"
|
51
|
+
|
52
|
+
attr_name = attribute.tr(".", "_").humanize
|
53
|
+
attr_name = base_class.human_attribute_name(attribute, {
|
54
|
+
default: attr_name,
|
55
|
+
base: base,
|
56
|
+
})
|
57
|
+
|
58
|
+
I18n.t(defaults.shift,
|
59
|
+
default: defaults,
|
60
|
+
attribute: attr_name,
|
61
|
+
message: message)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.generate_message(attribute, type, base, options) # :nodoc:
|
65
|
+
type = options.delete(:message) if options[:message].is_a?(Symbol)
|
66
|
+
value = (attribute != :base ? base.read_attribute_for_validation(attribute) : nil)
|
67
|
+
|
68
|
+
options = {
|
69
|
+
model: base.model_name.human,
|
70
|
+
attribute: base.class.human_attribute_name(attribute, { base: base }),
|
71
|
+
value: value,
|
72
|
+
object: base
|
73
|
+
}.merge!(options)
|
74
|
+
|
75
|
+
if base.class.respond_to?(:i18n_scope)
|
76
|
+
i18n_scope = base.class.i18n_scope.to_s
|
77
|
+
attribute = attribute.to_s.remove(/\[\d+\]/)
|
78
|
+
|
79
|
+
defaults = base.class.lookup_ancestors.flat_map do |klass|
|
80
|
+
[ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
|
81
|
+
:"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
|
82
|
+
end
|
83
|
+
defaults << :"#{i18n_scope}.errors.messages.#{type}"
|
84
|
+
|
85
|
+
catch(:exception) do
|
86
|
+
translation = I18n.translate(defaults.first, **options.merge(default: defaults.drop(1), throw: true))
|
87
|
+
return translation unless translation.nil?
|
88
|
+
end unless options[:message]
|
89
|
+
else
|
90
|
+
defaults = []
|
91
|
+
end
|
92
|
+
|
93
|
+
defaults << :"errors.attributes.#{attribute}.#{type}"
|
94
|
+
defaults << :"errors.messages.#{type}"
|
95
|
+
|
96
|
+
key = defaults.shift
|
97
|
+
defaults = options.delete(:message) if options[:message]
|
98
|
+
options[:default] = defaults
|
99
|
+
|
100
|
+
I18n.translate(key, **options)
|
101
|
+
end
|
102
|
+
|
103
|
+
def initialize(base, attribute, type = :invalid, **options)
|
104
|
+
@base = base
|
105
|
+
@attribute = attribute
|
106
|
+
@raw_type = type
|
107
|
+
@type = type || :invalid
|
108
|
+
@options = options
|
109
|
+
end
|
110
|
+
|
111
|
+
def initialize_dup(other) # :nodoc:
|
112
|
+
@attribute = @attribute.dup
|
113
|
+
@raw_type = @raw_type.dup
|
114
|
+
@type = @type.dup
|
115
|
+
@options = @options.deep_dup
|
116
|
+
end
|
117
|
+
|
118
|
+
# The object which the error belongs to
|
119
|
+
attr_reader :base
|
120
|
+
# The attribute of +base+ which the error belongs to
|
121
|
+
attr_reader :attribute
|
122
|
+
# The type of error, defaults to `:invalid` unless specified
|
123
|
+
attr_reader :type
|
124
|
+
# The raw value provided as the second parameter when calling `errors#add`
|
125
|
+
attr_reader :raw_type
|
126
|
+
# The options provided when calling `errors#add`
|
127
|
+
attr_reader :options
|
128
|
+
|
129
|
+
# Returns the error message.
|
130
|
+
#
|
131
|
+
# error = ActiveModel::Error.new(person, :name, :too_short, count: 5)
|
132
|
+
# error.message
|
133
|
+
# # => "is too short (minimum is 5 characters)"
|
134
|
+
def message
|
135
|
+
case raw_type
|
136
|
+
when Symbol
|
137
|
+
self.class.generate_message(attribute, raw_type, @base, options.except(*CALLBACKS_OPTIONS))
|
138
|
+
else
|
139
|
+
raw_type
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the error details.
|
144
|
+
#
|
145
|
+
# error = ActiveModel::Error.new(person, :name, :too_short, count: 5)
|
146
|
+
# error.details
|
147
|
+
# # => { error: :too_short, count: 5 }
|
148
|
+
def details
|
149
|
+
{ error: raw_type }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS))
|
150
|
+
end
|
151
|
+
alias_method :detail, :details
|
152
|
+
|
153
|
+
# Returns the full error message.
|
154
|
+
#
|
155
|
+
# error = ActiveModel::Error.new(person, :name, :too_short, count: 5)
|
156
|
+
# error.full_message
|
157
|
+
# # => "Name is too short (minimum is 5 characters)"
|
158
|
+
def full_message
|
159
|
+
self.class.full_message(attribute, message, @base)
|
160
|
+
end
|
161
|
+
|
162
|
+
# See if error matches provided +attribute+, +type+ and +options+.
|
163
|
+
#
|
164
|
+
# Omitted params are not checked for a match.
|
165
|
+
def match?(attribute, type = nil, **options)
|
166
|
+
if @attribute != attribute || (type && @type != type)
|
167
|
+
return false
|
168
|
+
end
|
169
|
+
|
170
|
+
options.each do |key, value|
|
171
|
+
if @options[key] != value
|
172
|
+
return false
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
true
|
177
|
+
end
|
178
|
+
|
179
|
+
# See if error matches provided +attribute+, +type+ and +options+ exactly.
|
180
|
+
#
|
181
|
+
# All params must be equal to Error's own attributes to be considered a
|
182
|
+
# strict match.
|
183
|
+
def strict_match?(attribute, type, **options)
|
184
|
+
return false unless match?(attribute, type)
|
185
|
+
|
186
|
+
options == @options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS)
|
187
|
+
end
|
188
|
+
|
189
|
+
def ==(other) # :nodoc:
|
190
|
+
other.is_a?(self.class) && attributes_for_hash == other.attributes_for_hash
|
191
|
+
end
|
192
|
+
alias eql? ==
|
193
|
+
|
194
|
+
def hash # :nodoc:
|
195
|
+
attributes_for_hash.hash
|
196
|
+
end
|
197
|
+
|
198
|
+
def inspect # :nodoc:
|
199
|
+
"#<#{self.class.name} attribute=#{@attribute}, type=#{@type}, options=#{@options.inspect}>"
|
200
|
+
end
|
201
|
+
|
202
|
+
protected
|
203
|
+
def attributes_for_hash
|
204
|
+
[@base, @attribute, @raw_type, @options.except(*CALLBACKS_OPTIONS)]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|