activemodel 4.2.11.3 → 5.0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +149 -56
- data/MIT-LICENSE +1 -1
- data/README.rdoc +8 -16
- data/lib/active_model/attribute_assignment.rb +52 -0
- data/lib/active_model/attribute_methods.rb +16 -16
- data/lib/active_model/callbacks.rb +3 -3
- data/lib/active_model/conversion.rb +5 -5
- data/lib/active_model/dirty.rb +41 -40
- data/lib/active_model/errors.rb +175 -68
- data/lib/active_model/forbidden_attributes_protection.rb +3 -2
- data/lib/active_model/gem_version.rb +5 -5
- data/lib/active_model/lint.rb +32 -28
- data/lib/active_model/locale/en.yml +2 -1
- data/lib/active_model/model.rb +3 -4
- data/lib/active_model/naming.rb +5 -4
- data/lib/active_model/secure_password.rb +2 -9
- data/lib/active_model/serialization.rb +36 -9
- data/lib/active_model/type/big_integer.rb +13 -0
- data/lib/active_model/type/binary.rb +50 -0
- data/lib/active_model/type/boolean.rb +21 -0
- data/lib/active_model/type/date.rb +54 -0
- data/lib/active_model/type/date_time.rb +44 -0
- data/lib/active_model/type/decimal.rb +66 -0
- data/lib/active_model/type/decimal_without_scale.rb +11 -0
- data/lib/active_model/type/float.rb +25 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +35 -0
- data/lib/active_model/type/helpers/mutable.rb +18 -0
- data/lib/active_model/type/helpers/numeric.rb +34 -0
- data/lib/active_model/type/helpers/time_value.rb +77 -0
- data/lib/active_model/type/helpers.rb +4 -0
- data/lib/active_model/type/immutable_string.rb +29 -0
- data/lib/active_model/type/integer.rb +66 -0
- data/lib/active_model/type/registry.rb +64 -0
- data/lib/active_model/type/string.rb +24 -0
- data/lib/active_model/type/text.rb +11 -0
- data/lib/active_model/type/time.rb +42 -0
- data/lib/active_model/type/unsigned_integer.rb +15 -0
- data/lib/active_model/type/value.rb +116 -0
- data/lib/active_model/type.rb +59 -0
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +57 -9
- data/lib/active_model/validations/callbacks.rb +3 -3
- data/lib/active_model/validations/clusivity.rb +4 -3
- data/lib/active_model/validations/confirmation.rb +16 -4
- data/lib/active_model/validations/exclusion.rb +3 -1
- data/lib/active_model/validations/format.rb +1 -1
- data/lib/active_model/validations/helper_methods.rb +13 -0
- data/lib/active_model/validations/inclusion.rb +3 -3
- data/lib/active_model/validations/length.rb +49 -18
- data/lib/active_model/validations/numericality.rb +20 -11
- data/lib/active_model/validations/validates.rb +1 -1
- data/lib/active_model/validations/with.rb +0 -10
- data/lib/active_model/validations.rb +34 -1
- data/lib/active_model/validator.rb +5 -5
- data/lib/active_model/version.rb +1 -1
- data/lib/active_model.rb +4 -2
- metadata +31 -22
- data/lib/active_model/serializers/xml.rb +0 -238
data/lib/active_model/dirty.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'active_support/hash_with_indifferent_access'
|
2
2
|
require 'active_support/core_ext/object/duplicable'
|
3
|
-
require 'active_support/core_ext/string/filters'
|
4
3
|
|
5
4
|
module ActiveModel
|
6
5
|
# == Active \Model \Dirty
|
@@ -13,7 +12,7 @@ module ActiveModel
|
|
13
12
|
# * <tt>include ActiveModel::Dirty</tt> in your object.
|
14
13
|
# * Call <tt>define_attribute_methods</tt> passing each method you want to
|
15
14
|
# track.
|
16
|
-
# * Call <tt>
|
15
|
+
# * Call <tt>[attr_name]_will_change!</tt> before each change to the tracked
|
17
16
|
# 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
|
@@ -81,9 +80,11 @@ module ActiveModel
|
|
81
80
|
#
|
82
81
|
# Reset the changes:
|
83
82
|
#
|
84
|
-
# person.previous_changes
|
83
|
+
# person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
|
84
|
+
# person.name_previously_changed? # => true
|
85
|
+
# person.name_previous_change # => ["Uncle Bob", "Bill"]
|
85
86
|
# person.reload!
|
86
|
-
# person.previous_changes
|
87
|
+
# person.previous_changes # => {}
|
87
88
|
#
|
88
89
|
# Rollback the changes:
|
89
90
|
#
|
@@ -105,10 +106,10 @@ module ActiveModel
|
|
105
106
|
# person.changes # => {"name" => ["Bill", "Bob"]}
|
106
107
|
#
|
107
108
|
# If an attribute is modified in-place then make use of
|
108
|
-
#
|
109
|
-
# Otherwise Active Model can't track changes to in-place attributes. Note
|
109
|
+
# <tt>[attribute_name]_will_change!</tt> to mark that the attribute is changing.
|
110
|
+
# Otherwise \Active \Model can't track changes to in-place attributes. Note
|
110
111
|
# that Active Record can detect in-place modifications automatically. You do
|
111
|
-
# not need to call
|
112
|
+
# not need to call <tt>[attribute_name]_will_change!</tt> on Active Record models.
|
112
113
|
#
|
113
114
|
# person.name_will_change!
|
114
115
|
# person.name_change # => ["Bill", "Bill"]
|
@@ -118,13 +119,16 @@ module ActiveModel
|
|
118
119
|
extend ActiveSupport::Concern
|
119
120
|
include ActiveModel::AttributeMethods
|
120
121
|
|
122
|
+
OPTION_NOT_GIVEN = Object.new # :nodoc:
|
123
|
+
private_constant :OPTION_NOT_GIVEN
|
124
|
+
|
121
125
|
included do
|
122
126
|
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
|
123
|
-
|
127
|
+
attribute_method_suffix '_previously_changed?', '_previous_change'
|
124
128
|
attribute_method_affix prefix: 'restore_', suffix: '!'
|
125
129
|
end
|
126
130
|
|
127
|
-
# Returns +true+ if any
|
131
|
+
# Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
|
128
132
|
#
|
129
133
|
# person.changed? # => false
|
130
134
|
# person.name = 'bob'
|
@@ -172,19 +176,23 @@ module ActiveModel
|
|
172
176
|
@changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
|
173
177
|
end
|
174
178
|
|
175
|
-
#
|
176
|
-
def attribute_changed?(attr,
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
result
|
179
|
+
# Handles <tt>*_changed?</tt> for +method_missing+.
|
180
|
+
def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
|
181
|
+
!!changes_include?(attr) &&
|
182
|
+
(to == OPTION_NOT_GIVEN || to == __send__(attr)) &&
|
183
|
+
(from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
|
181
184
|
end
|
182
185
|
|
183
|
-
#
|
186
|
+
# Handles <tt>*_was</tt> for +method_missing+.
|
184
187
|
def attribute_was(attr) # :nodoc:
|
185
188
|
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
|
186
189
|
end
|
187
190
|
|
191
|
+
# Handles <tt>*_previously_changed?</tt> for +method_missing+.
|
192
|
+
def attribute_previously_changed?(attr) #:nodoc:
|
193
|
+
previous_changes_include?(attr)
|
194
|
+
end
|
195
|
+
|
188
196
|
# Restore all previous data of the provided attributes.
|
189
197
|
def restore_attributes(attributes = changed)
|
190
198
|
attributes.each { |attr| restore_attribute! attr }
|
@@ -192,38 +200,41 @@ module ActiveModel
|
|
192
200
|
|
193
201
|
private
|
194
202
|
|
203
|
+
# Returns +true+ if attr_name is changed, +false+ otherwise.
|
195
204
|
def changes_include?(attr_name)
|
196
205
|
attributes_changed_by_setter.include?(attr_name)
|
197
206
|
end
|
198
207
|
alias attribute_changed_by_setter? changes_include?
|
199
208
|
|
209
|
+
# Returns +true+ if attr_name were changed before the model was saved,
|
210
|
+
# +false+ otherwise.
|
211
|
+
def previous_changes_include?(attr_name)
|
212
|
+
previous_changes.include?(attr_name)
|
213
|
+
end
|
214
|
+
|
200
215
|
# Removes current changes and makes them accessible through +previous_changes+.
|
201
216
|
def changes_applied # :doc:
|
202
217
|
@previously_changed = changes
|
203
218
|
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
204
219
|
end
|
205
220
|
|
206
|
-
#
|
221
|
+
# Clears all dirty data: current changes and previous changes.
|
207
222
|
def clear_changes_information # :doc:
|
208
223
|
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
209
224
|
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
210
225
|
end
|
211
226
|
|
212
|
-
|
213
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
214
|
-
`#reset_changes` is deprecated and will be removed on Rails 5.
|
215
|
-
Please use `#clear_changes_information` instead.
|
216
|
-
MSG
|
217
|
-
|
218
|
-
clear_changes_information
|
219
|
-
end
|
220
|
-
|
221
|
-
# Handle <tt>*_change</tt> for +method_missing+.
|
227
|
+
# Handles <tt>*_change</tt> for +method_missing+.
|
222
228
|
def attribute_change(attr)
|
223
229
|
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
|
224
230
|
end
|
225
231
|
|
226
|
-
#
|
232
|
+
# Handles <tt>*_previous_change</tt> for +method_missing+.
|
233
|
+
def attribute_previous_change(attr)
|
234
|
+
previous_changes[attr] if attribute_previously_changed?(attr)
|
235
|
+
end
|
236
|
+
|
237
|
+
# Handles <tt>*_will_change!</tt> for +method_missing+.
|
227
238
|
def attribute_will_change!(attr)
|
228
239
|
return if attribute_changed?(attr)
|
229
240
|
|
@@ -236,17 +247,7 @@ module ActiveModel
|
|
236
247
|
set_attribute_was(attr, value)
|
237
248
|
end
|
238
249
|
|
239
|
-
#
|
240
|
-
def reset_attribute!(attr)
|
241
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
242
|
-
`#reset_#{attr}!` is deprecated and will be removed on Rails 5.
|
243
|
-
Please use `#restore_#{attr}!` instead.
|
244
|
-
MSG
|
245
|
-
|
246
|
-
restore_attribute!(attr)
|
247
|
-
end
|
248
|
-
|
249
|
-
# Handle <tt>restore_*!</tt> for +method_missing+.
|
250
|
+
# Handles <tt>restore_*!</tt> for +method_missing+.
|
250
251
|
def restore_attribute!(attr)
|
251
252
|
if attribute_changed?(attr)
|
252
253
|
__send__("#{attr}=", changed_attributes[attr])
|
@@ -255,7 +256,7 @@ module ActiveModel
|
|
255
256
|
end
|
256
257
|
|
257
258
|
# This is necessary because `changed_attributes` might be overridden in
|
258
|
-
# other
|
259
|
+
# other implementations (e.g. in `ActiveRecord`)
|
259
260
|
alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc:
|
260
261
|
|
261
262
|
# Force an attribute to have a particular "before" value
|
data/lib/active_model/errors.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
|
3
1
|
require 'active_support/core_ext/array/conversions'
|
4
2
|
require 'active_support/core_ext/string/inflections'
|
3
|
+
require 'active_support/core_ext/object/deep_dup'
|
4
|
+
require 'active_support/core_ext/string/filters'
|
5
5
|
|
6
6
|
module ActiveModel
|
7
7
|
# == Active \Model \Errors
|
@@ -23,7 +23,7 @@ module ActiveModel
|
|
23
23
|
# attr_reader :errors
|
24
24
|
#
|
25
25
|
# def validate!
|
26
|
-
# errors.add(:name, "cannot be nil") if name.nil?
|
26
|
+
# errors.add(:name, :blank, message: "cannot be nil") if name.nil?
|
27
27
|
# end
|
28
28
|
#
|
29
29
|
# # The following methods are needed to be minimally implemented
|
@@ -32,20 +32,20 @@ module ActiveModel
|
|
32
32
|
# send(attr)
|
33
33
|
# end
|
34
34
|
#
|
35
|
-
# def
|
35
|
+
# def self.human_attribute_name(attr, options = {})
|
36
36
|
# attr
|
37
37
|
# end
|
38
38
|
#
|
39
|
-
# def
|
39
|
+
# def self.lookup_ancestors
|
40
40
|
# [self]
|
41
41
|
# end
|
42
42
|
# end
|
43
43
|
#
|
44
|
-
# The last three methods are required in your object for Errors to be
|
44
|
+
# The last three methods are required in your object for +Errors+ to be
|
45
45
|
# able to generate error messages correctly and also handle multiple
|
46
|
-
# languages. Of course, if you extend your object with ActiveModel::Translation
|
46
|
+
# languages. Of course, if you extend your object with <tt>ActiveModel::Translation</tt>
|
47
47
|
# you will not need to implement the last two. Likewise, using
|
48
|
-
# ActiveModel::Validations will handle the validation related methods
|
48
|
+
# <tt>ActiveModel::Validations</tt> will handle the validation related methods
|
49
49
|
# for you.
|
50
50
|
#
|
51
51
|
# The above allows you to do:
|
@@ -58,8 +58,9 @@ module ActiveModel
|
|
58
58
|
include Enumerable
|
59
59
|
|
60
60
|
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
|
61
|
+
MESSAGE_OPTIONS = [:message]
|
61
62
|
|
62
|
-
attr_reader :messages
|
63
|
+
attr_reader :messages, :details
|
63
64
|
|
64
65
|
# Pass in the instance of the object that is using the errors object.
|
65
66
|
#
|
@@ -70,14 +71,28 @@ module ActiveModel
|
|
70
71
|
# end
|
71
72
|
def initialize(base)
|
72
73
|
@base = base
|
73
|
-
@messages = {}
|
74
|
+
@messages = apply_default_array({})
|
75
|
+
@details = apply_default_array({})
|
74
76
|
end
|
75
77
|
|
76
78
|
def initialize_dup(other) # :nodoc:
|
77
79
|
@messages = other.messages.dup
|
80
|
+
@details = other.details.deep_dup
|
78
81
|
super
|
79
82
|
end
|
80
83
|
|
84
|
+
# Copies the errors from <tt>other</tt>.
|
85
|
+
#
|
86
|
+
# other - The ActiveModel::Errors instance.
|
87
|
+
#
|
88
|
+
# Examples
|
89
|
+
#
|
90
|
+
# person.errors.copy!(other)
|
91
|
+
def copy!(other) # :nodoc:
|
92
|
+
@messages = other.messages.dup
|
93
|
+
@details = other.details.dup
|
94
|
+
end
|
95
|
+
|
81
96
|
# Clear the error messages.
|
82
97
|
#
|
83
98
|
# person.errors.full_messages # => ["name cannot be nil"]
|
@@ -85,6 +100,7 @@ module ActiveModel
|
|
85
100
|
# person.errors.full_messages # => []
|
86
101
|
def clear
|
87
102
|
messages.clear
|
103
|
+
details.clear
|
88
104
|
end
|
89
105
|
|
90
106
|
# Returns +true+ if the error messages include an error for the given key
|
@@ -94,37 +110,48 @@ module ActiveModel
|
|
94
110
|
# person.errors.include?(:name) # => true
|
95
111
|
# person.errors.include?(:age) # => false
|
96
112
|
def include?(attribute)
|
97
|
-
messages[attribute].present?
|
113
|
+
messages.key?(attribute) && messages[attribute].present?
|
98
114
|
end
|
99
|
-
# aliases include?
|
100
115
|
alias :has_key? :include?
|
101
|
-
# aliases include?
|
102
116
|
alias :key? :include?
|
103
117
|
|
104
118
|
# Get messages for +key+.
|
105
119
|
#
|
106
120
|
# person.errors.messages # => {:name=>["cannot be nil"]}
|
107
121
|
# person.errors.get(:name) # => ["cannot be nil"]
|
108
|
-
# person.errors.get(:age) # =>
|
122
|
+
# person.errors.get(:age) # => []
|
109
123
|
def get(key)
|
124
|
+
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
|
125
|
+
ActiveModel::Errors#get is deprecated and will be removed in Rails 5.1.
|
126
|
+
|
127
|
+
To achieve the same use model.errors[:#{key}].
|
128
|
+
MESSAGE
|
129
|
+
|
110
130
|
messages[key]
|
111
131
|
end
|
112
132
|
|
113
133
|
# Set messages for +key+ to +value+.
|
114
134
|
#
|
115
|
-
# person.errors
|
135
|
+
# person.errors[:name] # => ["cannot be nil"]
|
116
136
|
# person.errors.set(:name, ["can't be nil"])
|
117
|
-
# person.errors
|
137
|
+
# person.errors[:name] # => ["can't be nil"]
|
118
138
|
def set(key, value)
|
139
|
+
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
|
140
|
+
ActiveModel::Errors#set is deprecated and will be removed in Rails 5.1.
|
141
|
+
|
142
|
+
Use model.errors.add(:#{key}, #{value.inspect}) instead.
|
143
|
+
MESSAGE
|
144
|
+
|
119
145
|
messages[key] = value
|
120
146
|
end
|
121
147
|
|
122
148
|
# Delete messages for +key+. Returns the deleted messages.
|
123
149
|
#
|
124
|
-
# person.errors
|
150
|
+
# person.errors[:name] # => ["cannot be nil"]
|
125
151
|
# person.errors.delete(:name) # => ["cannot be nil"]
|
126
|
-
# person.errors
|
152
|
+
# person.errors[:name] # => []
|
127
153
|
def delete(key)
|
154
|
+
details.delete(key)
|
128
155
|
messages.delete(key)
|
129
156
|
end
|
130
157
|
|
@@ -133,8 +160,17 @@ module ActiveModel
|
|
133
160
|
#
|
134
161
|
# person.errors[:name] # => ["cannot be nil"]
|
135
162
|
# person.errors['name'] # => ["cannot be nil"]
|
163
|
+
#
|
164
|
+
# Note that, if you try to get errors of an attribute which has
|
165
|
+
# no errors associated with it, this method will instantiate
|
166
|
+
# an empty error list for it and +keys+ will return an array
|
167
|
+
# of error keys which includes this attribute.
|
168
|
+
#
|
169
|
+
# person.errors.keys # => []
|
170
|
+
# person.errors[:name] # => []
|
171
|
+
# person.errors.keys # => [:name]
|
136
172
|
def [](attribute)
|
137
|
-
|
173
|
+
messages[attribute.to_sym]
|
138
174
|
end
|
139
175
|
|
140
176
|
# Adds to the supplied attribute the supplied error message.
|
@@ -142,38 +178,45 @@ module ActiveModel
|
|
142
178
|
# person.errors[:name] = "must be set"
|
143
179
|
# person.errors[:name] # => ['must be set']
|
144
180
|
def []=(attribute, error)
|
145
|
-
|
181
|
+
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
|
182
|
+
ActiveModel::Errors#[]= is deprecated and will be removed in Rails 5.1.
|
183
|
+
|
184
|
+
Use model.errors.add(:#{attribute}, #{error.inspect}) instead.
|
185
|
+
MESSAGE
|
186
|
+
|
187
|
+
messages[attribute.to_sym] << error
|
146
188
|
end
|
147
189
|
|
148
190
|
# Iterates through each error key, value pair in the error messages hash.
|
149
191
|
# Yields the attribute and the error for that attribute. If the attribute
|
150
192
|
# has more than one error message, yields once for each error message.
|
151
193
|
#
|
152
|
-
# person.errors.add(:name, "can't be blank")
|
194
|
+
# person.errors.add(:name, :blank, message: "can't be blank")
|
153
195
|
# person.errors.each do |attribute, error|
|
154
196
|
# # Will yield :name and "can't be blank"
|
155
197
|
# end
|
156
198
|
#
|
157
|
-
# person.errors.add(:name, "must be specified")
|
199
|
+
# person.errors.add(:name, :not_specified, message: "must be specified")
|
158
200
|
# person.errors.each do |attribute, error|
|
159
201
|
# # Will yield :name and "can't be blank"
|
160
202
|
# # then yield :name and "must be specified"
|
161
203
|
# end
|
162
204
|
def each
|
163
205
|
messages.each_key do |attribute|
|
164
|
-
|
206
|
+
messages[attribute].each { |error| yield attribute, error }
|
165
207
|
end
|
166
208
|
end
|
167
209
|
|
168
210
|
# Returns the number of error messages.
|
169
211
|
#
|
170
|
-
# person.errors.add(:name, "can't be blank")
|
212
|
+
# person.errors.add(:name, :blank, message: "can't be blank")
|
171
213
|
# person.errors.size # => 1
|
172
|
-
# person.errors.add(:name, "must be specified")
|
214
|
+
# person.errors.add(:name, :not_specified, message: "must be specified")
|
173
215
|
# person.errors.size # => 2
|
174
216
|
def size
|
175
217
|
values.flatten.size
|
176
218
|
end
|
219
|
+
alias :count :size
|
177
220
|
|
178
221
|
# Returns all message values.
|
179
222
|
#
|
@@ -191,40 +234,20 @@ module ActiveModel
|
|
191
234
|
messages.keys
|
192
235
|
end
|
193
236
|
|
194
|
-
# Returns an array of error messages, with the attribute name included.
|
195
|
-
#
|
196
|
-
# person.errors.add(:name, "can't be blank")
|
197
|
-
# person.errors.add(:name, "must be specified")
|
198
|
-
# person.errors.to_a # => ["name can't be blank", "name must be specified"]
|
199
|
-
def to_a
|
200
|
-
full_messages
|
201
|
-
end
|
202
|
-
|
203
|
-
# Returns the number of error messages.
|
204
|
-
#
|
205
|
-
# person.errors.add(:name, "can't be blank")
|
206
|
-
# person.errors.count # => 1
|
207
|
-
# person.errors.add(:name, "must be specified")
|
208
|
-
# person.errors.count # => 2
|
209
|
-
def count
|
210
|
-
to_a.size
|
211
|
-
end
|
212
|
-
|
213
237
|
# Returns +true+ if no errors are found, +false+ otherwise.
|
214
238
|
# If the error message is a string it can be empty.
|
215
239
|
#
|
216
240
|
# person.errors.full_messages # => ["name cannot be nil"]
|
217
241
|
# person.errors.empty? # => false
|
218
242
|
def empty?
|
219
|
-
|
243
|
+
size.zero?
|
220
244
|
end
|
221
|
-
|
222
|
-
alias_method :blank?, :empty?
|
245
|
+
alias :blank? :empty?
|
223
246
|
|
224
247
|
# Returns an xml formatted representation of the Errors hash.
|
225
248
|
#
|
226
|
-
# person.errors.add(:name, "can't be blank")
|
227
|
-
# person.errors.add(:name, "must be specified")
|
249
|
+
# person.errors.add(:name, :blank, message: "can't be blank")
|
250
|
+
# person.errors.add(:name, :not_specified, message: "must be specified")
|
228
251
|
# person.errors.to_xml
|
229
252
|
# # =>
|
230
253
|
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
@@ -257,21 +280,24 @@ module ActiveModel
|
|
257
280
|
messages[attribute] = array.map { |message| full_message(attribute, message) }
|
258
281
|
end
|
259
282
|
else
|
260
|
-
self.messages
|
283
|
+
without_default_proc(self.messages)
|
261
284
|
end
|
262
285
|
end
|
263
286
|
|
264
|
-
# Adds +message+ to the error messages on +attribute+.
|
265
|
-
# can be added to the same +attribute+.
|
266
|
-
# <tt>:invalid</tt> is assumed.
|
287
|
+
# Adds +message+ to the error messages and used validator type to +details+ on +attribute+.
|
288
|
+
# More than one error can be added to the same +attribute+.
|
289
|
+
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
|
267
290
|
#
|
268
291
|
# person.errors.add(:name)
|
269
292
|
# # => ["is invalid"]
|
270
|
-
# person.errors.add(:name,
|
293
|
+
# person.errors.add(:name, :not_implemented, message: "must be implemented")
|
271
294
|
# # => ["is invalid", "must be implemented"]
|
272
295
|
#
|
273
296
|
# person.errors.messages
|
274
|
-
# # => {:name=>["
|
297
|
+
# # => {:name=>["is invalid", "must be implemented"]}
|
298
|
+
#
|
299
|
+
# person.errors.details
|
300
|
+
# # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
|
275
301
|
#
|
276
302
|
# If +message+ is a symbol, it will be translated using the appropriate
|
277
303
|
# scope (see +generate_message+).
|
@@ -283,9 +309,9 @@ module ActiveModel
|
|
283
309
|
# ActiveModel::StrictValidationFailed instead of adding the error.
|
284
310
|
# <tt>:strict</tt> option can also be set to any other exception.
|
285
311
|
#
|
286
|
-
# person.errors.add(:name,
|
312
|
+
# person.errors.add(:name, :invalid, strict: true)
|
287
313
|
# # => ActiveModel::StrictValidationFailed: name is invalid
|
288
|
-
# person.errors.add(:name,
|
314
|
+
# person.errors.add(:name, :invalid, strict: NameIsInvalid)
|
289
315
|
# # => NameIsInvalid: name is invalid
|
290
316
|
#
|
291
317
|
# person.errors.messages # => {}
|
@@ -293,17 +319,23 @@ module ActiveModel
|
|
293
319
|
# +attribute+ should be set to <tt>:base</tt> if the error is not
|
294
320
|
# directly associated with a single attribute.
|
295
321
|
#
|
296
|
-
# person.errors.add(:base,
|
322
|
+
# person.errors.add(:base, :name_or_email_blank,
|
323
|
+
# message: "either name or email must be present")
|
297
324
|
# person.errors.messages
|
298
325
|
# # => {:base=>["either name or email must be present"]}
|
326
|
+
# person.errors.details
|
327
|
+
# # => {:base=>[{error: :name_or_email_blank}]}
|
299
328
|
def add(attribute, message = :invalid, options = {})
|
329
|
+
message = message.call if message.respond_to?(:call)
|
330
|
+
detail = normalize_detail(message, options)
|
300
331
|
message = normalize_message(attribute, message, options)
|
301
332
|
if exception = options[:strict]
|
302
333
|
exception = ActiveModel::StrictValidationFailed if exception == true
|
303
334
|
raise exception, full_message(attribute, message)
|
304
335
|
end
|
305
336
|
|
306
|
-
|
337
|
+
details[attribute.to_sym] << detail
|
338
|
+
messages[attribute.to_sym] << message
|
307
339
|
end
|
308
340
|
|
309
341
|
# Will add an error message to each of the attributes in +attributes+
|
@@ -313,6 +345,14 @@ module ActiveModel
|
|
313
345
|
# person.errors.messages
|
314
346
|
# # => {:name=>["can't be empty"]}
|
315
347
|
def add_on_empty(attributes, options = {})
|
348
|
+
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
|
349
|
+
ActiveModel::Errors#add_on_empty is deprecated and will be removed in Rails 5.1.
|
350
|
+
|
351
|
+
To achieve the same use:
|
352
|
+
|
353
|
+
errors.add(attribute, :empty, options) if value.nil? || value.empty?
|
354
|
+
MESSAGE
|
355
|
+
|
316
356
|
Array(attributes).each do |attribute|
|
317
357
|
value = @base.send(:read_attribute_for_validation, attribute)
|
318
358
|
is_empty = value.respond_to?(:empty?) ? value.empty? : false
|
@@ -327,6 +367,14 @@ module ActiveModel
|
|
327
367
|
# person.errors.messages
|
328
368
|
# # => {:name=>["can't be blank"]}
|
329
369
|
def add_on_blank(attributes, options = {})
|
370
|
+
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
|
371
|
+
ActiveModel::Errors#add_on_blank is deprecated and will be removed in Rails 5.1.
|
372
|
+
|
373
|
+
To achieve the same use:
|
374
|
+
|
375
|
+
errors.add(attribute, :blank, options) if value.blank?
|
376
|
+
MESSAGE
|
377
|
+
|
330
378
|
Array(attributes).each do |attribute|
|
331
379
|
value = @base.send(:read_attribute_for_validation, attribute)
|
332
380
|
add(attribute, :blank, options) if value.blank?
|
@@ -334,11 +382,23 @@ module ActiveModel
|
|
334
382
|
end
|
335
383
|
|
336
384
|
# Returns +true+ if an error on the attribute with the given message is
|
337
|
-
# present, +false+ otherwise. +message+ is treated the same as for +add+.
|
385
|
+
# present, or +false+ otherwise. +message+ is treated the same as for +add+.
|
338
386
|
#
|
339
387
|
# person.errors.add :name, :blank
|
340
|
-
# person.errors.added? :name, :blank
|
388
|
+
# person.errors.added? :name, :blank # => true
|
389
|
+
# person.errors.added? :name, "can't be blank" # => true
|
390
|
+
#
|
391
|
+
# If the error message requires an option, then it returns +true+ with
|
392
|
+
# the correct option, or +false+ with an incorrect or missing option.
|
393
|
+
#
|
394
|
+
# person.errors.add :name, :too_long, { count: 25 }
|
395
|
+
# person.errors.added? :name, :too_long, count: 25 # => true
|
396
|
+
# person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
|
397
|
+
# person.errors.added? :name, :too_long, count: 24 # => false
|
398
|
+
# person.errors.added? :name, :too_long # => false
|
399
|
+
# person.errors.added? :name, "is too long" # => false
|
341
400
|
def added?(attribute, message = :invalid, options = {})
|
401
|
+
message = message.call if message.respond_to?(:call)
|
342
402
|
message = normalize_message(attribute, message, options)
|
343
403
|
self[attribute].include? message
|
344
404
|
end
|
@@ -356,6 +416,7 @@ module ActiveModel
|
|
356
416
|
def full_messages
|
357
417
|
map { |attribute, message| full_message(attribute, message) }
|
358
418
|
end
|
419
|
+
alias :to_a :full_messages
|
359
420
|
|
360
421
|
# Returns all the full error messages for a given attribute in an array.
|
361
422
|
#
|
@@ -368,7 +429,7 @@ module ActiveModel
|
|
368
429
|
# person.errors.full_messages_for(:name)
|
369
430
|
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
|
370
431
|
def full_messages_for(attribute)
|
371
|
-
|
432
|
+
messages[attribute].map { |message| full_message(attribute, message) }
|
372
433
|
end
|
373
434
|
|
374
435
|
# Returns a full message for a given attribute.
|
@@ -388,8 +449,8 @@ module ActiveModel
|
|
388
449
|
# Translates an error message in its default scope
|
389
450
|
# (<tt>activemodel.errors.messages</tt>).
|
390
451
|
#
|
391
|
-
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
|
392
|
-
# if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if
|
452
|
+
# Error messages are first looked up in <tt>activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
|
453
|
+
# if it's not there, it's looked up in <tt>activemodel.errors.models.MODEL.MESSAGE</tt> and if
|
393
454
|
# that is not there also, it returns the translation of the default message
|
394
455
|
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
|
395
456
|
# name, translated attribute name and the value are available for
|
@@ -421,7 +482,6 @@ module ActiveModel
|
|
421
482
|
defaults = []
|
422
483
|
end
|
423
484
|
|
424
|
-
defaults << options.delete(:message)
|
425
485
|
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
|
426
486
|
defaults << :"errors.attributes.#{attribute}.#{type}"
|
427
487
|
defaults << :"errors.messages.#{type}"
|
@@ -430,29 +490,61 @@ module ActiveModel
|
|
430
490
|
defaults.flatten!
|
431
491
|
|
432
492
|
key = defaults.shift
|
493
|
+
defaults = options.delete(:message) if options[:message]
|
433
494
|
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
|
434
495
|
|
435
496
|
options = {
|
436
497
|
default: defaults,
|
437
498
|
model: @base.model_name.human,
|
438
499
|
attribute: @base.class.human_attribute_name(attribute),
|
439
|
-
value: value
|
500
|
+
value: value,
|
501
|
+
object: @base
|
440
502
|
}.merge!(options)
|
441
503
|
|
442
504
|
I18n.translate(key, options)
|
443
505
|
end
|
444
506
|
|
507
|
+
def marshal_dump # :nodoc:
|
508
|
+
[@base, without_default_proc(@messages), without_default_proc(@details)]
|
509
|
+
end
|
510
|
+
|
511
|
+
def marshal_load(array) # :nodoc:
|
512
|
+
@base, @messages, @details = array
|
513
|
+
apply_default_array(@messages)
|
514
|
+
apply_default_array(@details)
|
515
|
+
end
|
516
|
+
|
517
|
+
def init_with(coder) # :nodoc:
|
518
|
+
coder.map.each { |k, v| instance_variable_set(:"@#{k}", v) }
|
519
|
+
@details ||= {}
|
520
|
+
apply_default_array(@messages)
|
521
|
+
apply_default_array(@details)
|
522
|
+
end
|
523
|
+
|
445
524
|
private
|
446
525
|
def normalize_message(attribute, message, options)
|
447
526
|
case message
|
448
527
|
when Symbol
|
449
528
|
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
|
450
|
-
when Proc
|
451
|
-
message.call
|
452
529
|
else
|
453
530
|
message
|
454
531
|
end
|
455
532
|
end
|
533
|
+
|
534
|
+
def normalize_detail(message, options)
|
535
|
+
{ error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS))
|
536
|
+
end
|
537
|
+
|
538
|
+
def without_default_proc(hash)
|
539
|
+
hash.dup.tap do |new_h|
|
540
|
+
new_h.default_proc = nil
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
def apply_default_array(hash)
|
545
|
+
hash.default_proc = proc { |h, key| h[key] = [] }
|
546
|
+
hash
|
547
|
+
end
|
456
548
|
end
|
457
549
|
|
458
550
|
# Raised when a validation cannot be corrected by end users and are considered
|
@@ -472,4 +564,19 @@ module ActiveModel
|
|
472
564
|
# # => ActiveModel::StrictValidationFailed: Name can't be blank
|
473
565
|
class StrictValidationFailed < StandardError
|
474
566
|
end
|
567
|
+
|
568
|
+
# Raised when attribute values are out of range.
|
569
|
+
class RangeError < ::RangeError
|
570
|
+
end
|
571
|
+
|
572
|
+
# Raised when unknown attributes are supplied via mass assignment.
|
573
|
+
class UnknownAttributeError < NoMethodError
|
574
|
+
attr_reader :record, :attribute
|
575
|
+
|
576
|
+
def initialize(record, attribute)
|
577
|
+
@record = record
|
578
|
+
@attribute = attribute
|
579
|
+
super("unknown attribute '#{attribute}' for #{@record.class}.")
|
580
|
+
end
|
581
|
+
end
|
475
582
|
end
|