activemodel 6.0.3.2 → 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 +4 -4
- data/CHANGELOG.md +48 -182
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/active_model.rb +2 -1
- data/lib/active_model/attribute.rb +15 -14
- data/lib/active_model/attribute_assignment.rb +3 -4
- data/lib/active_model/attribute_methods.rb +74 -38
- data/lib/active_model/attribute_mutation_tracker.rb +8 -5
- data/lib/active_model/attribute_set.rb +18 -16
- data/lib/active_model/attribute_set/builder.rb +80 -13
- data/lib/active_model/attributes.rb +20 -24
- data/lib/active_model/dirty.rb +12 -4
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +316 -208
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/naming.rb +2 -2
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +1 -1
- data/lib/active_model/secure_password.rb +14 -14
- data/lib/active_model/serialization.rb +9 -6
- data/lib/active_model/serializers/json.rb +7 -0
- data/lib/active_model/type/date_time.rb +2 -2
- data/lib/active_model/type/float.rb +2 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +11 -7
- data/lib/active_model/type/helpers/numeric.rb +8 -3
- data/lib/active_model/type/helpers/time_value.rb +27 -17
- data/lib/active_model/type/helpers/timezone.rb +1 -1
- data/lib/active_model/type/immutable_string.rb +14 -10
- data/lib/active_model/type/integer.rb +11 -2
- data/lib/active_model/type/registry.rb +11 -4
- data/lib/active_model/type/string.rb +12 -2
- data/lib/active_model/type/value.rb +9 -1
- data/lib/active_model/validations.rb +6 -6
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/clusivity.rb +5 -1
- data/lib/active_model/validations/confirmation.rb +2 -2
- data/lib/active_model/validations/exclusion.rb +1 -1
- data/lib/active_model/validations/format.rb +2 -2
- data/lib/active_model/validations/inclusion.rb +1 -1
- data/lib/active_model/validations/length.rb +2 -2
- data/lib/active_model/validations/numericality.rb +48 -41
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/validates.rb +6 -4
- data/lib/active_model/validator.rb +7 -1
- metadata +13 -11
@@ -7,9 +7,8 @@ module ActiveModel
|
|
7
7
|
class AttributeMutationTracker # :nodoc:
|
8
8
|
OPTION_NOT_GIVEN = Object.new
|
9
9
|
|
10
|
-
def initialize(attributes
|
10
|
+
def initialize(attributes)
|
11
11
|
@attributes = attributes
|
12
|
-
@forced_changes = forced_changes
|
13
12
|
end
|
14
13
|
|
15
14
|
def changed_attribute_names
|
@@ -62,11 +61,15 @@ module ActiveModel
|
|
62
61
|
end
|
63
62
|
|
64
63
|
def force_change(attr_name)
|
65
|
-
forced_changes
|
64
|
+
forced_changes[attr_name] = fetch_value(attr_name)
|
66
65
|
end
|
67
66
|
|
68
67
|
private
|
69
|
-
attr_reader :attributes
|
68
|
+
attr_reader :attributes
|
69
|
+
|
70
|
+
def forced_changes
|
71
|
+
@forced_changes ||= {}
|
72
|
+
end
|
70
73
|
|
71
74
|
def attr_names
|
72
75
|
attributes.keys
|
@@ -82,7 +85,7 @@ module ActiveModel
|
|
82
85
|
end
|
83
86
|
|
84
87
|
class ForcedMutationTracker < AttributeMutationTracker # :nodoc:
|
85
|
-
def initialize(attributes
|
88
|
+
def initialize(attributes)
|
86
89
|
super
|
87
90
|
@finalized_changes = nil
|
88
91
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
3
4
|
require "active_support/core_ext/object/deep_dup"
|
4
5
|
require "active_model/attribute_set/builder"
|
5
6
|
require "active_model/attribute_set/yaml_encoder"
|
@@ -13,11 +14,11 @@ module ActiveModel
|
|
13
14
|
end
|
14
15
|
|
15
16
|
def [](name)
|
16
|
-
attributes[name] ||
|
17
|
+
@attributes[name] || default_attribute(name)
|
17
18
|
end
|
18
19
|
|
19
20
|
def []=(name, value)
|
20
|
-
attributes[name] = value
|
21
|
+
@attributes[name] = value
|
21
22
|
end
|
22
23
|
|
23
24
|
def values_before_type_cast
|
@@ -25,9 +26,9 @@ module ActiveModel
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def to_hash
|
28
|
-
|
29
|
+
keys.index_with { |name| self[name].value }
|
29
30
|
end
|
30
|
-
|
31
|
+
alias :to_h :to_hash
|
31
32
|
|
32
33
|
def key?(name)
|
33
34
|
attributes.key?(name) && self[name].initialized?
|
@@ -42,35 +43,36 @@ module ActiveModel
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def write_from_database(name, value)
|
45
|
-
attributes[name] = self[name].with_value_from_database(value)
|
46
|
+
@attributes[name] = self[name].with_value_from_database(value)
|
46
47
|
end
|
47
48
|
|
48
49
|
def write_from_user(name, value)
|
49
|
-
attributes
|
50
|
+
raise FrozenError, "can't modify frozen attributes" if frozen?
|
51
|
+
@attributes[name] = self[name].with_value_from_user(value)
|
52
|
+
value
|
50
53
|
end
|
51
54
|
|
52
55
|
def write_cast_value(name, value)
|
53
|
-
attributes[name] = self[name].with_cast_value(value)
|
56
|
+
@attributes[name] = self[name].with_cast_value(value)
|
57
|
+
value
|
54
58
|
end
|
55
59
|
|
56
60
|
def freeze
|
57
|
-
|
61
|
+
attributes.freeze
|
58
62
|
super
|
59
63
|
end
|
60
64
|
|
61
65
|
def deep_dup
|
62
|
-
|
63
|
-
copy.instance_variable_set(:@attributes, attributes.deep_dup)
|
64
|
-
end
|
66
|
+
AttributeSet.new(attributes.deep_dup)
|
65
67
|
end
|
66
68
|
|
67
69
|
def initialize_dup(_)
|
68
|
-
@attributes = attributes.dup
|
70
|
+
@attributes = @attributes.dup
|
69
71
|
super
|
70
72
|
end
|
71
73
|
|
72
74
|
def initialize_clone(_)
|
73
|
-
@attributes = attributes.clone
|
75
|
+
@attributes = @attributes.clone
|
74
76
|
super
|
75
77
|
end
|
76
78
|
|
@@ -81,7 +83,7 @@ module ActiveModel
|
|
81
83
|
end
|
82
84
|
|
83
85
|
def accessed
|
84
|
-
attributes.select { |
|
86
|
+
attributes.each_key.select { |name| self[name].has_been_read? }
|
85
87
|
end
|
86
88
|
|
87
89
|
def map(&block)
|
@@ -97,8 +99,8 @@ module ActiveModel
|
|
97
99
|
attr_reader :attributes
|
98
100
|
|
99
101
|
private
|
100
|
-
def
|
101
|
-
|
102
|
+
def default_attribute(name)
|
103
|
+
Attribute.null(name)
|
102
104
|
end
|
103
105
|
end
|
104
106
|
end
|
@@ -13,14 +13,86 @@ module ActiveModel
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def build_from_database(values = {}, additional_types = {})
|
16
|
-
|
17
|
-
AttributeSet.new(attributes)
|
16
|
+
LazyAttributeSet.new(values, types, additional_types, default_attributes)
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
21
|
+
class LazyAttributeSet < AttributeSet # :nodoc:
|
22
|
+
def initialize(values, types, additional_types, default_attributes, attributes = {})
|
23
|
+
super(attributes)
|
24
|
+
@values = values
|
25
|
+
@types = types
|
26
|
+
@additional_types = additional_types
|
27
|
+
@default_attributes = default_attributes
|
28
|
+
@casted_values = {}
|
29
|
+
@materialized = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def key?(name)
|
33
|
+
(values.key?(name) || types.key?(name) || @attributes.key?(name)) && self[name].initialized?
|
34
|
+
end
|
35
|
+
|
36
|
+
def keys
|
37
|
+
keys = values.keys | types.keys | @attributes.keys
|
38
|
+
keys.keep_if { |name| self[name].initialized? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch_value(name, &block)
|
42
|
+
if attr = @attributes[name]
|
43
|
+
return attr.value(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
@casted_values.fetch(name) do
|
47
|
+
value_present = true
|
48
|
+
value = values.fetch(name) { value_present = false }
|
49
|
+
|
50
|
+
if value_present
|
51
|
+
type = additional_types.fetch(name, types[name])
|
52
|
+
@casted_values[name] = type.deserialize(value)
|
53
|
+
else
|
54
|
+
attr = default_attribute(name, value_present, value)
|
55
|
+
attr.value(&block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
def attributes
|
62
|
+
unless @materialized
|
63
|
+
values.each_key { |key| self[key] }
|
64
|
+
types.each_key { |key| self[key] }
|
65
|
+
@materialized = true
|
66
|
+
end
|
67
|
+
@attributes
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
attr_reader :values, :types, :additional_types, :default_attributes
|
72
|
+
|
73
|
+
def default_attribute(
|
74
|
+
name,
|
75
|
+
value_present = true,
|
76
|
+
value = values.fetch(name) { value_present = false }
|
77
|
+
)
|
78
|
+
type = additional_types.fetch(name, types[name])
|
79
|
+
|
80
|
+
if value_present
|
81
|
+
@attributes[name] = Attribute.from_database(name, value, type, @casted_values[name])
|
82
|
+
elsif types.key?(name)
|
83
|
+
if attr = default_attributes[name]
|
84
|
+
@attributes[name] = attr.dup
|
85
|
+
else
|
86
|
+
@attributes[name] = Attribute.uninitialized(name, type)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
Attribute.null(name)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
22
94
|
class LazyAttributeHash # :nodoc:
|
23
|
-
delegate :transform_values, :
|
95
|
+
delegate :transform_values, :each_value, :fetch, :except, to: :materialize
|
24
96
|
|
25
97
|
def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
|
26
98
|
@types = types
|
@@ -40,9 +112,6 @@ module ActiveModel
|
|
40
112
|
end
|
41
113
|
|
42
114
|
def []=(key, value)
|
43
|
-
if frozen?
|
44
|
-
raise RuntimeError, "Can't modify frozen hash"
|
45
|
-
end
|
46
115
|
delegate_hash[key] = value
|
47
116
|
end
|
48
117
|
|
@@ -57,14 +126,9 @@ module ActiveModel
|
|
57
126
|
super
|
58
127
|
end
|
59
128
|
|
60
|
-
def
|
129
|
+
def each_key(&block)
|
61
130
|
keys = types.keys | values.keys | delegate_hash.keys
|
62
|
-
keys.
|
63
|
-
attribute = self[key]
|
64
|
-
if yield(key, attribute)
|
65
|
-
hash[key] = attribute
|
66
|
-
end
|
67
|
-
end
|
131
|
+
keys.each(&block)
|
68
132
|
end
|
69
133
|
|
70
134
|
def ==(other)
|
@@ -81,6 +145,9 @@ module ActiveModel
|
|
81
145
|
|
82
146
|
def marshal_load(values)
|
83
147
|
if values.is_a?(Hash)
|
148
|
+
ActiveSupport::Deprecation.warn(<<~MSG)
|
149
|
+
Marshalling load from legacy attributes format is deprecated and will be removed in Rails 6.2.
|
150
|
+
MSG
|
84
151
|
empty_hash = {}.freeze
|
85
152
|
initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
|
86
153
|
@materialized = true
|
@@ -42,16 +42,14 @@ module ActiveModel
|
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
45
|
-
def define_method_attribute=(name)
|
45
|
+
def define_method_attribute=(name, owner:)
|
46
46
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
47
|
-
|
47
|
+
owner, name, writer: true,
|
48
48
|
) do |temp_method_name, attr_name_expr|
|
49
|
-
|
50
|
-
def #{temp_method_name}(value)
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
RUBY
|
49
|
+
owner <<
|
50
|
+
"def #{temp_method_name}(value)" <<
|
51
|
+
" _write_attribute(#{attr_name_expr}, value)" <<
|
52
|
+
"end"
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
@@ -79,10 +77,14 @@ module ActiveModel
|
|
79
77
|
super
|
80
78
|
end
|
81
79
|
|
80
|
+
def initialize_dup(other) # :nodoc:
|
81
|
+
@attributes = @attributes.deep_dup
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
82
85
|
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
83
86
|
#
|
84
87
|
# class Person
|
85
|
-
# include ActiveModel::Model
|
86
88
|
# include ActiveModel::Attributes
|
87
89
|
#
|
88
90
|
# attribute :name, :string
|
@@ -112,25 +114,19 @@ module ActiveModel
|
|
112
114
|
@attributes.keys
|
113
115
|
end
|
114
116
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
117
|
+
def freeze
|
118
|
+
@attributes = @attributes.clone.freeze unless frozen?
|
119
|
+
super
|
120
|
+
end
|
119
121
|
|
120
|
-
|
121
|
-
|
122
|
+
private
|
123
|
+
def _write_attribute(attr_name, value)
|
124
|
+
@attributes.write_from_user(attr_name, value)
|
122
125
|
end
|
126
|
+
alias :attribute= :_write_attribute
|
123
127
|
|
124
128
|
def attribute(attr_name)
|
125
|
-
|
126
|
-
name = self.class.attribute_aliases[name] || name
|
127
|
-
|
128
|
-
@attributes.fetch_value(name)
|
129
|
-
end
|
130
|
-
|
131
|
-
# Dispatch target for <tt>*=</tt> attribute methods.
|
132
|
-
def attribute=(attribute_name, value)
|
133
|
-
write_attribute(attribute_name, value)
|
129
|
+
@attributes.fetch_value(attr_name)
|
134
130
|
end
|
135
131
|
end
|
136
132
|
end
|
data/lib/active_model/dirty.rb
CHANGED
@@ -83,7 +83,9 @@ module ActiveModel
|
|
83
83
|
#
|
84
84
|
# person.previous_changes # => {"name" => [nil, "Bill"]}
|
85
85
|
# person.name_previously_changed? # => true
|
86
|
+
# person.name_previously_changed?(from: nil, to: "Bill") # => true
|
86
87
|
# person.name_previous_change # => [nil, "Bill"]
|
88
|
+
# person.name_previously_was # => nil
|
87
89
|
# person.reload!
|
88
90
|
# person.previous_changes # => {}
|
89
91
|
#
|
@@ -122,8 +124,9 @@ module ActiveModel
|
|
122
124
|
|
123
125
|
included do
|
124
126
|
attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
|
125
|
-
attribute_method_suffix "_previously_changed?", "_previous_change"
|
127
|
+
attribute_method_suffix "_previously_changed?", "_previous_change", "_previously_was"
|
126
128
|
attribute_method_affix prefix: "restore_", suffix: "!"
|
129
|
+
attribute_method_affix prefix: "clear_", suffix: "_change"
|
127
130
|
end
|
128
131
|
|
129
132
|
def initialize_dup(other) # :nodoc:
|
@@ -136,7 +139,7 @@ module ActiveModel
|
|
136
139
|
@mutations_from_database = nil
|
137
140
|
end
|
138
141
|
|
139
|
-
# Clears dirty data and moves +changes+ to +
|
142
|
+
# Clears dirty data and moves +changes+ to +previous_changes+ and
|
140
143
|
# +mutations_from_database+ to +mutations_before_last_save+ respectively.
|
141
144
|
def changes_applied
|
142
145
|
unless defined?(@attributes)
|
@@ -176,8 +179,13 @@ module ActiveModel
|
|
176
179
|
end
|
177
180
|
|
178
181
|
# Dispatch target for <tt>*_previously_changed?</tt> attribute methods.
|
179
|
-
def attribute_previously_changed?(attr_name) # :nodoc:
|
180
|
-
mutations_before_last_save.changed?(attr_name.to_s)
|
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)
|
181
189
|
end
|
182
190
|
|
183
191
|
# Restore all previous data of the provided attributes.
|
@@ -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
|