activemodel 6.0.3.3 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +49 -183
- 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/callbacks.rb +1 -1
- 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/callbacks.rb +15 -15
- 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
|
@@ -147,7 +147,7 @@ module ActiveModel
|
|
147
147
|
conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
|
148
148
|
v != false
|
149
149
|
}
|
150
|
-
options[:if] = Array(options[:if])
|
150
|
+
options[:if] = Array(options[:if]) + [conditional]
|
151
151
|
set_callback(:"#{callback}", :after, *args, options, &block)
|
152
152
|
end
|
153
153
|
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
|