activemodel 5.1.7 → 5.2.0.beta1
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 +32 -93
- data/README.rdoc +1 -1
- data/lib/active_model.rb +6 -1
- data/lib/active_model/attribute.rb +243 -0
- data/lib/active_model/attribute/user_provided_default.rb +30 -0
- data/lib/active_model/attribute_assignment.rb +8 -5
- data/lib/active_model/attribute_methods.rb +12 -10
- data/lib/active_model/attribute_mutation_tracker.rb +116 -0
- data/lib/active_model/attribute_set.rb +113 -0
- data/lib/active_model/attribute_set/builder.rb +124 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
- data/lib/active_model/attributes.rb +108 -0
- data/lib/active_model/callbacks.rb +7 -2
- data/lib/active_model/conversion.rb +2 -0
- data/lib/active_model/dirty.rb +124 -57
- data/lib/active_model/errors.rb +32 -21
- data/lib/active_model/forbidden_attributes_protection.rb +2 -0
- data/lib/active_model/gem_version.rb +5 -3
- data/lib/active_model/lint.rb +2 -0
- data/lib/active_model/model.rb +2 -0
- data/lib/active_model/naming.rb +5 -3
- data/lib/active_model/railtie.rb +2 -0
- data/lib/active_model/secure_password.rb +5 -3
- data/lib/active_model/serialization.rb +2 -0
- data/lib/active_model/serializers/json.rb +3 -2
- data/lib/active_model/translation.rb +2 -0
- data/lib/active_model/type.rb +6 -0
- data/lib/active_model/type/big_integer.rb +2 -0
- data/lib/active_model/type/binary.rb +2 -0
- data/lib/active_model/type/boolean.rb +2 -0
- data/lib/active_model/type/date.rb +2 -0
- data/lib/active_model/type/date_time.rb +6 -0
- data/lib/active_model/type/decimal.rb +2 -0
- data/lib/active_model/type/float.rb +2 -0
- data/lib/active_model/type/helpers.rb +2 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +6 -0
- data/lib/active_model/type/helpers/mutable.rb +2 -0
- data/lib/active_model/type/helpers/numeric.rb +2 -0
- data/lib/active_model/type/helpers/time_value.rb +2 -1
- data/lib/active_model/type/immutable_string.rb +2 -0
- data/lib/active_model/type/integer.rb +3 -1
- data/lib/active_model/type/registry.rb +2 -0
- data/lib/active_model/type/string.rb +2 -0
- data/lib/active_model/type/time.rb +8 -4
- data/lib/active_model/type/value.rb +3 -1
- data/lib/active_model/validations.rb +7 -3
- data/lib/active_model/validations/absence.rb +2 -0
- data/lib/active_model/validations/acceptance.rb +2 -0
- data/lib/active_model/validations/callbacks.rb +11 -13
- data/lib/active_model/validations/clusivity.rb +2 -0
- data/lib/active_model/validations/confirmation.rb +3 -1
- data/lib/active_model/validations/exclusion.rb +2 -0
- data/lib/active_model/validations/format.rb +1 -0
- data/lib/active_model/validations/helper_methods.rb +2 -0
- data/lib/active_model/validations/inclusion.rb +2 -0
- data/lib/active_model/validations/length.rb +10 -2
- data/lib/active_model/validations/numericality.rb +3 -1
- data/lib/active_model/validations/presence.rb +1 -0
- data/lib/active_model/validations/validates.rb +4 -3
- data/lib/active_model/validations/with.rb +2 -0
- data/lib/active_model/validator.rb +6 -4
- data/lib/active_model/version.rb +2 -0
- metadata +17 -9
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/deep_dup"
|
4
|
+
require "active_model/attribute_set"
|
5
|
+
require "active_model/attribute/user_provided_default"
|
6
|
+
|
7
|
+
module ActiveModel
|
8
|
+
module Attributes #:nodoc:
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
include ActiveModel::AttributeMethods
|
11
|
+
|
12
|
+
included do
|
13
|
+
attribute_method_suffix "="
|
14
|
+
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
|
15
|
+
self.attribute_types = Hash.new(Type.default_value)
|
16
|
+
self._default_attributes = AttributeSet.new({})
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def attribute(name, type = Type::Value.new, **options)
|
21
|
+
name = name.to_s
|
22
|
+
if type.is_a?(Symbol)
|
23
|
+
type = ActiveModel::Type.lookup(type, **options.except(:default))
|
24
|
+
end
|
25
|
+
self.attribute_types = attribute_types.merge(name => type)
|
26
|
+
define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
|
27
|
+
define_attribute_methods(name)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def define_method_attribute=(name)
|
33
|
+
safe_name = name.unpack("h*".freeze).first
|
34
|
+
ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
35
|
+
|
36
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
37
|
+
def __temp__#{safe_name}=(value)
|
38
|
+
name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
39
|
+
write_attribute(name, value)
|
40
|
+
end
|
41
|
+
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
42
|
+
undef_method :__temp__#{safe_name}=
|
43
|
+
STR
|
44
|
+
end
|
45
|
+
|
46
|
+
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
47
|
+
private_constant :NO_DEFAULT_PROVIDED
|
48
|
+
|
49
|
+
def define_default_attribute(name, value, type)
|
50
|
+
self._default_attributes = _default_attributes.deep_dup
|
51
|
+
if value == NO_DEFAULT_PROVIDED
|
52
|
+
default_attribute = _default_attributes[name].with_type(type)
|
53
|
+
else
|
54
|
+
default_attribute = Attribute::UserProvidedDefault.new(
|
55
|
+
name,
|
56
|
+
value,
|
57
|
+
type,
|
58
|
+
_default_attributes.fetch(name.to_s) { nil },
|
59
|
+
)
|
60
|
+
end
|
61
|
+
_default_attributes[name] = default_attribute
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize(*)
|
66
|
+
@attributes = self.class._default_attributes.deep_dup
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def write_attribute(attr_name, value)
|
73
|
+
name = if self.class.attribute_alias?(attr_name)
|
74
|
+
self.class.attribute_alias(attr_name).to_s
|
75
|
+
else
|
76
|
+
attr_name.to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
@attributes.write_from_user(name, value)
|
80
|
+
value
|
81
|
+
end
|
82
|
+
|
83
|
+
def attribute(attr_name)
|
84
|
+
name = if self.class.attribute_alias?(attr_name)
|
85
|
+
self.class.attribute_alias(attr_name).to_s
|
86
|
+
else
|
87
|
+
attr_name.to_s
|
88
|
+
end
|
89
|
+
@attributes.fetch_value(name)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Handle *= for method_missing.
|
93
|
+
def attribute=(attribute_name, value)
|
94
|
+
write_attribute(attribute_name, value)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
module AttributeMethods #:nodoc:
|
99
|
+
AttrNames = Module.new {
|
100
|
+
def self.set_name_cache(name, value)
|
101
|
+
const_name = "ATTR_#{name}"
|
102
|
+
unless const_defined? const_name
|
103
|
+
const_set const_name, value.dup.freeze
|
104
|
+
end
|
105
|
+
end
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_support/core_ext/array/extract_options"
|
2
4
|
|
3
5
|
module ActiveModel
|
@@ -56,6 +58,9 @@ module ActiveModel
|
|
56
58
|
#
|
57
59
|
# Would only create the +after_create+ and +before_create+ callback methods in
|
58
60
|
# your class.
|
61
|
+
#
|
62
|
+
# NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
|
63
|
+
#
|
59
64
|
module Callbacks
|
60
65
|
def self.extended(base) #:nodoc:
|
61
66
|
base.class_eval do
|
@@ -98,8 +103,8 @@ module ActiveModel
|
|
98
103
|
# end
|
99
104
|
# end
|
100
105
|
#
|
101
|
-
# NOTE: +method_name+ passed to
|
102
|
-
#
|
106
|
+
# NOTE: +method_name+ passed to define_model_callbacks must not end with
|
107
|
+
# <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
|
103
108
|
def define_model_callbacks(*callbacks)
|
104
109
|
options = callbacks.extract_options!
|
105
110
|
options = {
|
data/lib/active_model/dirty.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_support/hash_with_indifferent_access"
|
2
4
|
require "active_support/core_ext/object/duplicable"
|
5
|
+
require "active_model/attribute_mutation_tracker"
|
3
6
|
|
4
7
|
module ActiveModel
|
5
8
|
# == Active \Model \Dirty
|
@@ -128,6 +131,24 @@ module ActiveModel
|
|
128
131
|
attribute_method_affix prefix: "restore_", suffix: "!"
|
129
132
|
end
|
130
133
|
|
134
|
+
def initialize_dup(other) # :nodoc:
|
135
|
+
super
|
136
|
+
if self.class.respond_to?(:_default_attributes)
|
137
|
+
@attributes = self.class._default_attributes.map do |attr|
|
138
|
+
attr.with_value_from_user(@attributes.fetch_value(attr.name))
|
139
|
+
end
|
140
|
+
end
|
141
|
+
@mutations_from_database = nil
|
142
|
+
end
|
143
|
+
|
144
|
+
def changes_applied # :nodoc:
|
145
|
+
@previously_changed = changes
|
146
|
+
@mutations_before_last_save = mutations_from_database
|
147
|
+
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
148
|
+
forget_attribute_assignments
|
149
|
+
@mutations_from_database = nil
|
150
|
+
end
|
151
|
+
|
131
152
|
# Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
|
132
153
|
#
|
133
154
|
# person.changed? # => false
|
@@ -146,6 +167,60 @@ module ActiveModel
|
|
146
167
|
changed_attributes.keys
|
147
168
|
end
|
148
169
|
|
170
|
+
# Handles <tt>*_changed?</tt> for +method_missing+.
|
171
|
+
def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
|
172
|
+
!!changes_include?(attr) &&
|
173
|
+
(to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) &&
|
174
|
+
(from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
|
175
|
+
end
|
176
|
+
|
177
|
+
# Handles <tt>*_was</tt> for +method_missing+.
|
178
|
+
def attribute_was(attr) # :nodoc:
|
179
|
+
attribute_changed?(attr) ? changed_attributes[attr] : _read_attribute(attr)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Handles <tt>*_previously_changed?</tt> for +method_missing+.
|
183
|
+
def attribute_previously_changed?(attr) #:nodoc:
|
184
|
+
previous_changes_include?(attr)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Restore all previous data of the provided attributes.
|
188
|
+
def restore_attributes(attributes = changed)
|
189
|
+
attributes.each { |attr| restore_attribute! attr }
|
190
|
+
end
|
191
|
+
|
192
|
+
# Clears all dirty data: current changes and previous changes.
|
193
|
+
def clear_changes_information
|
194
|
+
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
195
|
+
@mutations_before_last_save = nil
|
196
|
+
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
197
|
+
forget_attribute_assignments
|
198
|
+
@mutations_from_database = nil
|
199
|
+
end
|
200
|
+
|
201
|
+
def clear_attribute_changes(attr_names)
|
202
|
+
attributes_changed_by_setter.except!(*attr_names)
|
203
|
+
attr_names.each do |attr_name|
|
204
|
+
clear_attribute_change(attr_name)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Returns a hash of the attributes with unsaved changes indicating their original
|
209
|
+
# values like <tt>attr => original value</tt>.
|
210
|
+
#
|
211
|
+
# person.name # => "bob"
|
212
|
+
# person.name = 'robert'
|
213
|
+
# person.changed_attributes # => {"name" => "bob"}
|
214
|
+
def changed_attributes
|
215
|
+
# This should only be set by methods which will call changed_attributes
|
216
|
+
# multiple times when it is known that the computed value cannot change.
|
217
|
+
if defined?(@cached_changed_attributes)
|
218
|
+
@cached_changed_attributes
|
219
|
+
else
|
220
|
+
attributes_changed_by_setter.reverse_merge(mutations_from_database.changed_values).freeze
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
149
224
|
# Returns a hash of changed attributes indicating their original
|
150
225
|
# and new values like <tt>attr => [original value, new value]</tt>.
|
151
226
|
#
|
@@ -153,7 +228,9 @@ module ActiveModel
|
|
153
228
|
# person.name = 'bob'
|
154
229
|
# person.changes # => { "name" => ["bill", "bob"] }
|
155
230
|
def changes
|
156
|
-
|
231
|
+
cache_changed_attributes do
|
232
|
+
ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
|
233
|
+
end
|
157
234
|
end
|
158
235
|
|
159
236
|
# Returns a hash of attributes that were changed before the model was saved.
|
@@ -164,45 +241,51 @@ module ActiveModel
|
|
164
241
|
# person.previous_changes # => {"name" => ["bob", "robert"]}
|
165
242
|
def previous_changes
|
166
243
|
@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
|
244
|
+
@previously_changed.merge(mutations_before_last_save.changes)
|
167
245
|
end
|
168
246
|
|
169
|
-
|
170
|
-
|
171
|
-
#
|
172
|
-
# person.name # => "bob"
|
173
|
-
# person.name = 'robert'
|
174
|
-
# person.changed_attributes # => {"name" => "bob"}
|
175
|
-
def changed_attributes
|
176
|
-
@changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
|
247
|
+
def attribute_changed_in_place?(attr_name) # :nodoc:
|
248
|
+
mutations_from_database.changed_in_place?(attr_name)
|
177
249
|
end
|
178
250
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
(from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
|
184
|
-
end
|
251
|
+
private
|
252
|
+
def clear_attribute_change(attr_name)
|
253
|
+
mutations_from_database.forget_change(attr_name)
|
254
|
+
end
|
185
255
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
256
|
+
def mutations_from_database
|
257
|
+
unless defined?(@mutations_from_database)
|
258
|
+
@mutations_from_database = nil
|
259
|
+
end
|
260
|
+
@mutations_from_database ||= if defined?(@attributes)
|
261
|
+
ActiveModel::AttributeMutationTracker.new(@attributes)
|
262
|
+
else
|
263
|
+
NullMutationTracker.instance
|
264
|
+
end
|
265
|
+
end
|
190
266
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
end
|
267
|
+
def forget_attribute_assignments
|
268
|
+
@attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes)
|
269
|
+
end
|
195
270
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
end
|
271
|
+
def mutations_before_last_save
|
272
|
+
@mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
|
273
|
+
end
|
200
274
|
|
201
|
-
|
275
|
+
def cache_changed_attributes
|
276
|
+
@cached_changed_attributes = changed_attributes
|
277
|
+
yield
|
278
|
+
ensure
|
279
|
+
clear_changed_attributes_cache
|
280
|
+
end
|
281
|
+
|
282
|
+
def clear_changed_attributes_cache
|
283
|
+
remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
|
284
|
+
end
|
202
285
|
|
203
286
|
# Returns +true+ if attr_name is changed, +false+ otherwise.
|
204
287
|
def changes_include?(attr_name)
|
205
|
-
attributes_changed_by_setter.include?(attr_name)
|
288
|
+
attributes_changed_by_setter.include?(attr_name) || mutations_from_database.changed?(attr_name)
|
206
289
|
end
|
207
290
|
alias attribute_changed_by_setter? changes_include?
|
208
291
|
|
@@ -212,21 +295,9 @@ module ActiveModel
|
|
212
295
|
previous_changes.include?(attr_name)
|
213
296
|
end
|
214
297
|
|
215
|
-
# Removes current changes and makes them accessible through +previous_changes+.
|
216
|
-
def changes_applied # :doc:
|
217
|
-
@previously_changed = changes
|
218
|
-
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
219
|
-
end
|
220
|
-
|
221
|
-
# Clears all dirty data: current changes and previous changes.
|
222
|
-
def clear_changes_information # :doc:
|
223
|
-
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
224
|
-
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
225
|
-
end
|
226
|
-
|
227
298
|
# Handles <tt>*_change</tt> for +method_missing+.
|
228
299
|
def attribute_change(attr)
|
229
|
-
[changed_attributes[attr],
|
300
|
+
[changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
|
230
301
|
end
|
231
302
|
|
232
303
|
# Handles <tt>*_previous_change</tt> for +method_missing+.
|
@@ -236,15 +307,16 @@ module ActiveModel
|
|
236
307
|
|
237
308
|
# Handles <tt>*_will_change!</tt> for +method_missing+.
|
238
309
|
def attribute_will_change!(attr)
|
239
|
-
|
310
|
+
unless attribute_changed?(attr)
|
311
|
+
begin
|
312
|
+
value = _read_attribute(attr)
|
313
|
+
value = value.duplicable? ? value.clone : value
|
314
|
+
rescue TypeError, NoMethodError
|
315
|
+
end
|
240
316
|
|
241
|
-
|
242
|
-
value = __send__(attr)
|
243
|
-
value = value.duplicable? ? value.clone : value
|
244
|
-
rescue TypeError, NoMethodError
|
317
|
+
set_attribute_was(attr, value)
|
245
318
|
end
|
246
|
-
|
247
|
-
set_attribute_was(attr, value)
|
319
|
+
mutations_from_database.force_change(attr)
|
248
320
|
end
|
249
321
|
|
250
322
|
# Handles <tt>restore_*!</tt> for +method_missing+.
|
@@ -255,18 +327,13 @@ module ActiveModel
|
|
255
327
|
end
|
256
328
|
end
|
257
329
|
|
258
|
-
|
259
|
-
|
260
|
-
|
330
|
+
def attributes_changed_by_setter
|
331
|
+
@attributes_changed_by_setter ||= ActiveSupport::HashWithIndifferentAccess.new
|
332
|
+
end
|
261
333
|
|
262
334
|
# Force an attribute to have a particular "before" value
|
263
335
|
def set_attribute_was(attr, old_value)
|
264
336
|
attributes_changed_by_setter[attr] = old_value
|
265
337
|
end
|
266
|
-
|
267
|
-
# Remove changes information for the provided attributes.
|
268
|
-
def clear_attribute_changes(attributes) # :doc:
|
269
|
-
attributes_changed_by_setter.except!(*attributes)
|
270
|
-
end
|
271
338
|
end
|
272
339
|
end
|
data/lib/active_model/errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_support/core_ext/array/conversions"
|
2
4
|
require "active_support/core_ext/string/inflections"
|
3
5
|
require "active_support/core_ext/object/deep_dup"
|
@@ -93,6 +95,18 @@ module ActiveModel
|
|
93
95
|
@details = other.details.dup
|
94
96
|
end
|
95
97
|
|
98
|
+
# Merges the errors from <tt>other</tt>.
|
99
|
+
#
|
100
|
+
# other - The ActiveModel::Errors instance.
|
101
|
+
#
|
102
|
+
# Examples
|
103
|
+
#
|
104
|
+
# person.errors.merge!(other)
|
105
|
+
def merge!(other)
|
106
|
+
@messages.merge!(other.messages) { |_, ary1, ary2| ary1 + ary2 }
|
107
|
+
@details.merge!(other.details) { |_, ary1, ary2| ary1 + ary2 }
|
108
|
+
end
|
109
|
+
|
96
110
|
# Clear the error messages.
|
97
111
|
#
|
98
112
|
# person.errors.full_messages # => ["name cannot be nil"]
|
@@ -132,15 +146,6 @@ module ActiveModel
|
|
132
146
|
#
|
133
147
|
# person.errors[:name] # => ["cannot be nil"]
|
134
148
|
# person.errors['name'] # => ["cannot be nil"]
|
135
|
-
#
|
136
|
-
# Note that, if you try to get errors of an attribute which has
|
137
|
-
# no errors associated with it, this method will instantiate
|
138
|
-
# an empty error list for it and +keys+ will return an array
|
139
|
-
# of error keys which includes this attribute.
|
140
|
-
#
|
141
|
-
# person.errors.keys # => []
|
142
|
-
# person.errors[:name] # => []
|
143
|
-
# person.errors.keys # => [:name]
|
144
149
|
def [](attribute)
|
145
150
|
messages[attribute.to_sym]
|
146
151
|
end
|
@@ -181,7 +186,9 @@ module ActiveModel
|
|
181
186
|
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
|
182
187
|
# person.errors.values # => [["cannot be nil", "must be specified"]]
|
183
188
|
def values
|
184
|
-
messages.
|
189
|
+
messages.select do |key, value|
|
190
|
+
!value.empty?
|
191
|
+
end.values
|
185
192
|
end
|
186
193
|
|
187
194
|
# Returns all message keys.
|
@@ -189,7 +196,9 @@ module ActiveModel
|
|
189
196
|
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
|
190
197
|
# person.errors.keys # => [:name]
|
191
198
|
def keys
|
192
|
-
messages.
|
199
|
+
messages.select do |key, value|
|
200
|
+
!value.empty?
|
201
|
+
end.keys
|
193
202
|
end
|
194
203
|
|
195
204
|
# Returns +true+ if no errors are found, +false+ otherwise.
|
@@ -313,9 +322,13 @@ module ActiveModel
|
|
313
322
|
# person.errors.added? :name, :too_long # => false
|
314
323
|
# person.errors.added? :name, "is too long" # => false
|
315
324
|
def added?(attribute, message = :invalid, options = {})
|
316
|
-
|
317
|
-
|
318
|
-
|
325
|
+
if message.is_a? Symbol
|
326
|
+
self.details[attribute].map { |e| e[:error] }.include? message
|
327
|
+
else
|
328
|
+
message = message.call if message.respond_to?(:call)
|
329
|
+
message = normalize_message(attribute, message, options)
|
330
|
+
self[attribute].include? message
|
331
|
+
end
|
319
332
|
end
|
320
333
|
|
321
334
|
# Returns all the full error messages in an array.
|
@@ -389,21 +402,19 @@ module ActiveModel
|
|
389
402
|
type = options.delete(:message) if options[:message].is_a?(Symbol)
|
390
403
|
|
391
404
|
if @base.class.respond_to?(:i18n_scope)
|
392
|
-
|
393
|
-
|
394
|
-
|
405
|
+
i18n_scope = @base.class.i18n_scope.to_s
|
406
|
+
defaults = @base.class.lookup_ancestors.flat_map do |klass|
|
407
|
+
[ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
|
408
|
+
:"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
|
395
409
|
end
|
410
|
+
defaults << :"#{i18n_scope}.errors.messages.#{type}"
|
396
411
|
else
|
397
412
|
defaults = []
|
398
413
|
end
|
399
414
|
|
400
|
-
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
|
401
415
|
defaults << :"errors.attributes.#{attribute}.#{type}"
|
402
416
|
defaults << :"errors.messages.#{type}"
|
403
417
|
|
404
|
-
defaults.compact!
|
405
|
-
defaults.flatten!
|
406
|
-
|
407
418
|
key = defaults.shift
|
408
419
|
defaults = options.delete(:message) if options[:message]
|
409
420
|
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
|