activemodel 7.0.6 → 7.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 +149 -156
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -9
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +5 -5
- data/lib/active_model/attribute/user_provided_default.rb +4 -0
- data/lib/active_model/attribute.rb +26 -1
- data/lib/active_model/attribute_assignment.rb +1 -1
- data/lib/active_model/attribute_methods.rb +103 -64
- data/lib/active_model/attribute_registration.rb +77 -0
- data/lib/active_model/attribute_set.rb +9 -0
- data/lib/active_model/attributes.rb +62 -45
- data/lib/active_model/callbacks.rb +5 -5
- data/lib/active_model/conversion.rb +14 -4
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +134 -13
- data/lib/active_model/error.rb +5 -4
- data/lib/active_model/errors.rb +11 -6
- data/lib/active_model/forbidden_attributes_protection.rb +2 -0
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/locale/en.yml +1 -0
- data/lib/active_model/model.rb +26 -2
- data/lib/active_model/naming.rb +29 -10
- data/lib/active_model/railtie.rb +4 -0
- data/lib/active_model/secure_password.rb +61 -23
- data/lib/active_model/serialization.rb +3 -3
- data/lib/active_model/serializers/json.rb +1 -1
- data/lib/active_model/translation.rb +18 -16
- data/lib/active_model/type/big_integer.rb +23 -1
- data/lib/active_model/type/binary.rb +7 -1
- data/lib/active_model/type/boolean.rb +11 -9
- data/lib/active_model/type/date.rb +28 -2
- data/lib/active_model/type/date_time.rb +45 -3
- data/lib/active_model/type/decimal.rb +39 -1
- data/lib/active_model/type/float.rb +30 -1
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +5 -1
- data/lib/active_model/type/helpers/mutable.rb +4 -4
- data/lib/active_model/type/helpers/numeric.rb +4 -0
- data/lib/active_model/type/helpers/time_value.rb +28 -12
- data/lib/active_model/type/immutable_string.rb +37 -1
- data/lib/active_model/type/integer.rb +44 -1
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +9 -1
- data/lib/active_model/type/time.rb +48 -7
- data/lib/active_model/type/value.rb +23 -3
- data/lib/active_model/type.rb +1 -0
- 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 +4 -4
- data/lib/active_model/validations/clusivity.rb +5 -8
- data/lib/active_model/validations/comparability.rb +0 -11
- data/lib/active_model/validations/comparison.rb +15 -7
- data/lib/active_model/validations/format.rb +6 -7
- data/lib/active_model/validations/length.rb +10 -8
- data/lib/active_model/validations/numericality.rb +35 -23
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +3 -3
- data/lib/active_model/validations/with.rb +9 -2
- data/lib/active_model/validations.rb +44 -9
- data/lib/active_model/validator.rb +7 -5
- data/lib/active_model/version.rb +1 -1
- data/lib/active_model.rb +5 -1
- metadata +16 -11
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveModel
|
4
|
-
#
|
4
|
+
# = Active \Model \Conversion
|
5
5
|
#
|
6
6
|
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
|
7
7
|
#
|
@@ -24,6 +24,14 @@ module ActiveModel
|
|
24
24
|
module Conversion
|
25
25
|
extend ActiveSupport::Concern
|
26
26
|
|
27
|
+
included do
|
28
|
+
##
|
29
|
+
# :singleton-method:
|
30
|
+
#
|
31
|
+
# Accepts a string that will be used as a delimiter of object's key values in the `to_param` method.
|
32
|
+
class_attribute :param_delimiter, instance_reader: false, default: "-"
|
33
|
+
end
|
34
|
+
|
27
35
|
# If your object is already designed to implement all of the \Active \Model
|
28
36
|
# you can use the default <tt>:to_model</tt> implementation, which simply
|
29
37
|
# returns +self+.
|
@@ -58,7 +66,7 @@ module ActiveModel
|
|
58
66
|
# person.to_key # => [1]
|
59
67
|
def to_key
|
60
68
|
key = respond_to?(:id) && id
|
61
|
-
key ?
|
69
|
+
key ? Array(key) : nil
|
62
70
|
end
|
63
71
|
|
64
72
|
# Returns a +string+ representing the object's key suitable for use in URLs,
|
@@ -80,7 +88,7 @@ module ActiveModel
|
|
80
88
|
# person = Person.new(1)
|
81
89
|
# person.to_param # => "1"
|
82
90
|
def to_param
|
83
|
-
(persisted? && key = to_key) ? key.join(
|
91
|
+
(persisted? && (key = to_key) && key.all?) ? key.join(self.class.param_delimiter) : nil
|
84
92
|
end
|
85
93
|
|
86
94
|
# Returns a +string+ identifying the path associated with the object.
|
@@ -100,7 +108,9 @@ module ActiveModel
|
|
100
108
|
# Provide a class level cache for #to_partial_path. This is an
|
101
109
|
# internal method and should not be accessed directly.
|
102
110
|
def _to_partial_path # :nodoc:
|
103
|
-
@_to_partial_path ||=
|
111
|
+
@_to_partial_path ||= if respond_to?(:model_name)
|
112
|
+
"#{model_name.collection}/#{model_name.element}"
|
113
|
+
else
|
104
114
|
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
|
105
115
|
collection = ActiveSupport::Inflector.tableize(name)
|
106
116
|
"#{collection}/#{element}"
|
data/lib/active_model/dirty.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require "active_model/attribute_mutation_tracker"
|
4
4
|
|
5
5
|
module ActiveModel
|
6
|
-
#
|
6
|
+
# = Active \Model \Dirty
|
7
7
|
#
|
8
8
|
# Provides a way to track changes in your object in the same way as
|
9
9
|
# Active Record does.
|
@@ -13,8 +13,7 @@ module ActiveModel
|
|
13
13
|
# * <tt>include ActiveModel::Dirty</tt> in your object.
|
14
14
|
# * Call <tt>define_attribute_methods</tt> passing each method you want to
|
15
15
|
# track.
|
16
|
-
# * Call <tt
|
17
|
-
# attribute.
|
16
|
+
# * Call <tt>*_will_change!</tt> before each change to the tracked 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
|
20
19
|
# information.
|
@@ -109,20 +108,136 @@ module ActiveModel
|
|
109
108
|
# person.changes # => {"name" => ["Bill", "Bob"]}
|
110
109
|
#
|
111
110
|
# If an attribute is modified in-place then make use of
|
112
|
-
#
|
111
|
+
# {*_will_change!}[rdoc-label:method-i-2A_will_change-21] to mark that the attribute is changing.
|
113
112
|
# Otherwise \Active \Model can't track changes to in-place attributes. Note
|
114
113
|
# that Active Record can detect in-place modifications automatically. You do
|
115
|
-
# not need to call <tt
|
114
|
+
# not need to call <tt>*_will_change!</tt> on Active Record models.
|
116
115
|
#
|
117
116
|
# person.name_will_change!
|
118
117
|
# person.name_change # => ["Bill", "Bill"]
|
119
118
|
# person.name << 'y'
|
120
119
|
# person.name_change # => ["Bill", "Billy"]
|
120
|
+
#
|
121
|
+
# Methods can be invoked as +name_changed?+ or by passing an argument to the
|
122
|
+
# generic method <tt>attribute_changed?("name")</tt>.
|
121
123
|
module Dirty
|
122
124
|
extend ActiveSupport::Concern
|
123
125
|
include ActiveModel::AttributeMethods
|
124
126
|
|
125
127
|
included do
|
128
|
+
##
|
129
|
+
# :method: *_previously_changed?
|
130
|
+
#
|
131
|
+
# :call-seq: *_previously_changed?(**options)
|
132
|
+
#
|
133
|
+
# This method is generated for each attribute.
|
134
|
+
#
|
135
|
+
# Returns true if the attribute previously had unsaved changes.
|
136
|
+
#
|
137
|
+
# person = Person.new
|
138
|
+
# person.name = 'Britanny'
|
139
|
+
# person.save
|
140
|
+
# person.name_previously_changed? # => true
|
141
|
+
# person.name_previously_changed?(from: nil, to: 'Britanny') # => true
|
142
|
+
|
143
|
+
##
|
144
|
+
# :method: *_changed?
|
145
|
+
#
|
146
|
+
# This method is generated for each attribute.
|
147
|
+
#
|
148
|
+
# Returns true if the attribute has unsaved changes.
|
149
|
+
#
|
150
|
+
# person = Person.new
|
151
|
+
# person.name = 'Andrew'
|
152
|
+
# person.name_changed? # => true
|
153
|
+
|
154
|
+
##
|
155
|
+
# :method: *_change
|
156
|
+
#
|
157
|
+
# This method is generated for each attribute.
|
158
|
+
#
|
159
|
+
# Returns the old and the new value of the attribute.
|
160
|
+
#
|
161
|
+
# person = Person.new
|
162
|
+
# person.name = 'Nick'
|
163
|
+
# person.name_change # => [nil, 'Nick']
|
164
|
+
|
165
|
+
##
|
166
|
+
# :method: *_will_change!
|
167
|
+
#
|
168
|
+
# This method is generated for each attribute.
|
169
|
+
#
|
170
|
+
# If an attribute is modified in-place then make use of
|
171
|
+
# <tt>*_will_change!</tt> to mark that the attribute is changing.
|
172
|
+
# Otherwise Active Model can’t track changes to in-place attributes. Note
|
173
|
+
# that Active Record can detect in-place modifications automatically. You
|
174
|
+
# do not need to call <tt>*_will_change!</tt> on Active Record
|
175
|
+
# models.
|
176
|
+
#
|
177
|
+
# person = Person.new('Sandy')
|
178
|
+
# person.name_will_change!
|
179
|
+
# person.name_change # => ['Sandy', 'Sandy']
|
180
|
+
|
181
|
+
##
|
182
|
+
# :method: *_was
|
183
|
+
#
|
184
|
+
# This method is generated for each attribute.
|
185
|
+
#
|
186
|
+
# Returns the old value of the attribute.
|
187
|
+
#
|
188
|
+
# person = Person.new(name: 'Steph')
|
189
|
+
# person.name = 'Stephanie'
|
190
|
+
# person.name_change # => ['Steph', 'Stephanie']
|
191
|
+
|
192
|
+
##
|
193
|
+
# :method: *_previous_change
|
194
|
+
#
|
195
|
+
# This method is generated for each attribute.
|
196
|
+
#
|
197
|
+
# Returns the old and the new value of the attribute before the last save.
|
198
|
+
#
|
199
|
+
# person = Person.new
|
200
|
+
# person.name = 'Emmanuel'
|
201
|
+
# person.save
|
202
|
+
# person.name_previous_change # => [nil, 'Emmanuel']
|
203
|
+
|
204
|
+
##
|
205
|
+
# :method: *_previously_was
|
206
|
+
#
|
207
|
+
# This method is generated for each attribute.
|
208
|
+
#
|
209
|
+
# Returns the old value of the attribute before the last save.
|
210
|
+
#
|
211
|
+
# person = Person.new
|
212
|
+
# person.name = 'Sage'
|
213
|
+
# person.save
|
214
|
+
# person.name_previously_was # => nil
|
215
|
+
|
216
|
+
##
|
217
|
+
# :method: restore_*!
|
218
|
+
#
|
219
|
+
# This method is generated for each attribute.
|
220
|
+
#
|
221
|
+
# Restores the attribute to the old value.
|
222
|
+
#
|
223
|
+
# person = Person.new
|
224
|
+
# person.name = 'Amanda'
|
225
|
+
# person.restore_name!
|
226
|
+
# person.name # => nil
|
227
|
+
|
228
|
+
##
|
229
|
+
# :method: clear_*_change
|
230
|
+
#
|
231
|
+
# This method is generated for each attribute.
|
232
|
+
#
|
233
|
+
# Clears all dirty data of the attribute: current changes and previous changes.
|
234
|
+
#
|
235
|
+
# person = Person.new(name: 'Chris')
|
236
|
+
# person.name = 'Jason'
|
237
|
+
# person.name_change # => ['Chris', 'Jason']
|
238
|
+
# person.clear_name_change
|
239
|
+
# person.name_change # => nil
|
240
|
+
|
126
241
|
attribute_method_suffix "_previously_changed?", "_changed?", parameters: "**options"
|
127
242
|
attribute_method_suffix "_change", "_will_change!", "_was", parameters: false
|
128
243
|
attribute_method_suffix "_previous_change", "_previously_was", parameters: false
|
@@ -174,23 +289,23 @@ module ActiveModel
|
|
174
289
|
mutations_from_database.changed_attribute_names
|
175
290
|
end
|
176
291
|
|
177
|
-
# Dispatch target for
|
178
|
-
def attribute_changed?(attr_name, **options)
|
292
|
+
# Dispatch target for {*_changed?}[rdoc-label:method-i-2A_changed-3F] attribute methods.
|
293
|
+
def attribute_changed?(attr_name, **options)
|
179
294
|
mutations_from_database.changed?(attr_name.to_s, **options)
|
180
295
|
end
|
181
296
|
|
182
|
-
# Dispatch target for
|
183
|
-
def attribute_was(attr_name)
|
297
|
+
# Dispatch target for {*_was}[rdoc-label:method-i-2A_was] attribute methods.
|
298
|
+
def attribute_was(attr_name)
|
184
299
|
mutations_from_database.original_value(attr_name.to_s)
|
185
300
|
end
|
186
301
|
|
187
|
-
# Dispatch target for
|
188
|
-
def attribute_previously_changed?(attr_name, **options)
|
302
|
+
# Dispatch target for {*_previously_changed?}[rdoc-label:method-i-2A_previously_changed-3F] attribute methods.
|
303
|
+
def attribute_previously_changed?(attr_name, **options)
|
189
304
|
mutations_before_last_save.changed?(attr_name.to_s, **options)
|
190
305
|
end
|
191
306
|
|
192
|
-
# Dispatch target for
|
193
|
-
def attribute_previously_was(attr_name)
|
307
|
+
# Dispatch target for {*_previously_was}[rdoc-label:method-i-2A_previously_was] attribute methods.
|
308
|
+
def attribute_previously_was(attr_name)
|
194
309
|
mutations_before_last_save.original_value(attr_name.to_s)
|
195
310
|
end
|
196
311
|
|
@@ -247,6 +362,12 @@ module ActiveModel
|
|
247
362
|
end
|
248
363
|
|
249
364
|
private
|
365
|
+
def init_internals
|
366
|
+
super
|
367
|
+
@mutations_before_last_save = nil
|
368
|
+
@mutations_from_database = nil
|
369
|
+
end
|
370
|
+
|
250
371
|
def clear_attribute_change(attr_name)
|
251
372
|
mutations_from_database.forget_change(attr_name.to_s)
|
252
373
|
end
|
data/lib/active_model/error.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require "active_support/core_ext/class/attribute"
|
4
4
|
|
5
5
|
module ActiveModel
|
6
|
-
#
|
6
|
+
# = Active \Model \Error
|
7
7
|
#
|
8
8
|
# Represents one single error
|
9
9
|
class Error
|
@@ -49,7 +49,7 @@ module ActiveModel
|
|
49
49
|
defaults << :"errors.format"
|
50
50
|
defaults << "%{attribute} %{message}"
|
51
51
|
|
52
|
-
attr_name = attribute.tr(".", "_").humanize
|
52
|
+
attr_name = attribute.remove(/\.base\z/).tr(".", "_").humanize
|
53
53
|
attr_name = base_class.human_attribute_name(attribute, {
|
54
54
|
default: attr_name,
|
55
55
|
base: base,
|
@@ -121,9 +121,10 @@ module ActiveModel
|
|
121
121
|
attr_reader :attribute
|
122
122
|
# The type of error, defaults to +:invalid+ unless specified
|
123
123
|
attr_reader :type
|
124
|
-
# The raw value provided as the second parameter when calling
|
124
|
+
# The raw value provided as the second parameter when calling
|
125
|
+
# <tt>errors#add</tt>
|
125
126
|
attr_reader :raw_type
|
126
|
-
# The options provided when calling
|
127
|
+
# The options provided when calling <tt>errors#add</tt>
|
127
128
|
attr_reader :options
|
128
129
|
|
129
130
|
# Returns the error message.
|
data/lib/active_model/errors.rb
CHANGED
@@ -3,13 +3,12 @@
|
|
3
3
|
require "active_support/core_ext/array/conversions"
|
4
4
|
require "active_support/core_ext/string/inflections"
|
5
5
|
require "active_support/core_ext/object/deep_dup"
|
6
|
-
require "active_support/core_ext/string/filters"
|
7
6
|
require "active_model/error"
|
8
7
|
require "active_model/nested_error"
|
9
8
|
require "forwardable"
|
10
9
|
|
11
10
|
module ActiveModel
|
12
|
-
#
|
11
|
+
# = Active \Model \Errors
|
13
12
|
#
|
14
13
|
# Provides error related functionalities you can include in your object
|
15
14
|
# for handling error messages and interacting with Action View helpers.
|
@@ -215,7 +214,7 @@ module ActiveModel
|
|
215
214
|
|
216
215
|
# Returns a Hash that can be used as the JSON representation for this
|
217
216
|
# object. You can pass the <tt>:full_messages</tt> option. This determines
|
218
|
-
# if the
|
217
|
+
# if the JSON object should contain full messages or not (false by default).
|
219
218
|
#
|
220
219
|
# person.errors.as_json # => {:name=>["cannot be nil"]}
|
221
220
|
# person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}
|
@@ -287,7 +286,7 @@ module ActiveModel
|
|
287
286
|
# person.errors.messages
|
288
287
|
# # => {:name=>["can't be blank"]}
|
289
288
|
#
|
290
|
-
# person.errors.add(:name, :too_long,
|
289
|
+
# person.errors.add(:name, :too_long, count: 25)
|
291
290
|
# person.errors.messages
|
292
291
|
# # => ["is too long (maximum is 25 characters)"]
|
293
292
|
#
|
@@ -338,7 +337,7 @@ module ActiveModel
|
|
338
337
|
# If the error requires options, then it returns +true+ with
|
339
338
|
# the correct options, or +false+ with incorrect or missing options.
|
340
339
|
#
|
341
|
-
# person.errors.add :name, :too_long,
|
340
|
+
# person.errors.add :name, :too_long, count: 25
|
342
341
|
# person.errors.added? :name, :too_long, count: 25 # => true
|
343
342
|
# person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
|
344
343
|
# person.errors.added? :name, :too_long, count: 24 # => false
|
@@ -360,7 +359,7 @@ module ActiveModel
|
|
360
359
|
# present, or +false+ otherwise. +type+ is treated the same as for +add+.
|
361
360
|
#
|
362
361
|
# person.errors.add :age
|
363
|
-
# person.errors.add :name, :too_long,
|
362
|
+
# person.errors.add :name, :too_long, count: 25
|
364
363
|
# person.errors.of_kind? :age # => true
|
365
364
|
# person.errors.of_kind? :name # => false
|
366
365
|
# person.errors.of_kind? :name, :too_long # => true
|
@@ -472,6 +471,8 @@ module ActiveModel
|
|
472
471
|
end
|
473
472
|
end
|
474
473
|
|
474
|
+
# = Active \Model \StrictValidationFailed
|
475
|
+
#
|
475
476
|
# Raised when a validation cannot be corrected by end users and are considered
|
476
477
|
# exceptional.
|
477
478
|
#
|
@@ -490,10 +491,14 @@ module ActiveModel
|
|
490
491
|
class StrictValidationFailed < StandardError
|
491
492
|
end
|
492
493
|
|
494
|
+
# = Active \Model \RangeError
|
495
|
+
#
|
493
496
|
# Raised when attribute values are out of range.
|
494
497
|
class RangeError < ::RangeError
|
495
498
|
end
|
496
499
|
|
500
|
+
# = Active \Model \UnknownAttributeError
|
501
|
+
#
|
497
502
|
# Raised when unknown attributes are supplied via mass assignment.
|
498
503
|
#
|
499
504
|
# class Person
|
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveModel
|
4
|
-
# Returns the currently loaded version of \Active \Model as a
|
4
|
+
# Returns the currently loaded version of \Active \Model as a +Gem::Version+.
|
5
5
|
def self.gem_version
|
6
6
|
Gem::Version.new VERSION::STRING
|
7
7
|
end
|
8
8
|
|
9
9
|
module VERSION
|
10
10
|
MAJOR = 7
|
11
|
-
MINOR =
|
12
|
-
TINY =
|
11
|
+
MINOR = 1
|
12
|
+
TINY = 1
|
13
13
|
PRE = nil
|
14
14
|
|
15
15
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
data/lib/active_model/lint.rb
CHANGED
@@ -5,7 +5,7 @@ module ActiveModel
|
|
5
5
|
# == Active \Model \Lint \Tests
|
6
6
|
#
|
7
7
|
# You can test whether an object is compliant with the Active \Model API by
|
8
|
-
# including
|
8
|
+
# including +ActiveModel::Lint::Tests+ in your TestCase. It will
|
9
9
|
# include tests that tell you whether your object is fully compliant,
|
10
10
|
# or if not, which aspects of the API are not implemented.
|
11
11
|
#
|
@@ -18,6 +18,7 @@ en:
|
|
18
18
|
too_long:
|
19
19
|
one: "is too long (maximum is 1 character)"
|
20
20
|
other: "is too long (maximum is %{count} characters)"
|
21
|
+
password_too_long: "is too long"
|
21
22
|
too_short:
|
22
23
|
one: "is too short (minimum is 1 character)"
|
23
24
|
other: "is too short (minimum is %{count} characters)"
|
data/lib/active_model/model.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveModel
|
4
|
-
#
|
4
|
+
# = Active \Model \Basic \Model
|
5
5
|
#
|
6
6
|
# Allows implementing models similar to ActiveRecord::Base.
|
7
7
|
# Includes ActiveModel::API for the required interface for an
|
@@ -37,10 +37,34 @@ module ActiveModel
|
|
37
37
|
# person.omg # => true
|
38
38
|
#
|
39
39
|
# For more detailed information on other functionalities available, please
|
40
|
-
# refer to the specific modules included in
|
40
|
+
# refer to the specific modules included in +ActiveModel::Model+
|
41
41
|
# (see below).
|
42
42
|
module Model
|
43
43
|
extend ActiveSupport::Concern
|
44
44
|
include ActiveModel::API
|
45
|
+
include ActiveModel::Access
|
46
|
+
|
47
|
+
##
|
48
|
+
# :method: slice
|
49
|
+
#
|
50
|
+
# :call-seq: slice(*methods)
|
51
|
+
#
|
52
|
+
# Returns a hash of the given methods with their names as keys and returned
|
53
|
+
# values as values.
|
54
|
+
#
|
55
|
+
#--
|
56
|
+
# Implemented by ActiveModel::Access#slice.
|
57
|
+
|
58
|
+
##
|
59
|
+
# :method: values_at
|
60
|
+
#
|
61
|
+
# :call-seq: values_at(*methods)
|
62
|
+
#
|
63
|
+
# Returns an array of the values returned by the given methods.
|
64
|
+
#
|
65
|
+
#--
|
66
|
+
# Implemented by ActiveModel::Access#values_at.
|
45
67
|
end
|
68
|
+
|
69
|
+
ActiveSupport.run_load_hooks(:active_model, Model)
|
46
70
|
end
|
data/lib/active_model/naming.rb
CHANGED
@@ -195,18 +195,15 @@ module ActiveModel
|
|
195
195
|
#
|
196
196
|
# Specify +options+ with additional translating options.
|
197
197
|
def human(options = {})
|
198
|
-
return @human
|
199
|
-
@klass.respond_to?(:i18n_scope)
|
200
|
-
|
201
|
-
defaults = @klass.lookup_ancestors.map do |klass|
|
202
|
-
klass.model_name.i18n_key
|
203
|
-
end
|
198
|
+
return @human if i18n_keys.empty? || i18n_scope.empty?
|
204
199
|
|
200
|
+
key, *defaults = i18n_keys
|
205
201
|
defaults << options[:default] if options[:default]
|
206
|
-
defaults <<
|
202
|
+
defaults << MISSING_TRANSLATION
|
207
203
|
|
208
|
-
|
209
|
-
|
204
|
+
translation = I18n.translate(key, scope: i18n_scope, count: 1, **options, default: defaults)
|
205
|
+
translation = @human if translation == MISSING_TRANSLATION
|
206
|
+
translation
|
210
207
|
end
|
211
208
|
|
212
209
|
def uncountable?
|
@@ -214,12 +211,26 @@ module ActiveModel
|
|
214
211
|
end
|
215
212
|
|
216
213
|
private
|
214
|
+
MISSING_TRANSLATION = -(2**60) # :nodoc:
|
215
|
+
|
217
216
|
def _singularize(string)
|
218
217
|
ActiveSupport::Inflector.underscore(string).tr("/", "_")
|
219
218
|
end
|
219
|
+
|
220
|
+
def i18n_keys
|
221
|
+
@i18n_keys ||= if @klass.respond_to?(:lookup_ancestors)
|
222
|
+
@klass.lookup_ancestors.map { |klass| klass.model_name.i18n_key }
|
223
|
+
else
|
224
|
+
[]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def i18n_scope
|
229
|
+
@i18n_scope ||= @klass.respond_to?(:i18n_scope) ? [@klass.i18n_scope, :models] : []
|
230
|
+
end
|
220
231
|
end
|
221
232
|
|
222
|
-
#
|
233
|
+
# = Active \Model \Naming
|
223
234
|
#
|
224
235
|
# Creates a +model_name+ method on your object.
|
225
236
|
#
|
@@ -336,5 +347,13 @@ module ActiveModel
|
|
336
347
|
end
|
337
348
|
end
|
338
349
|
private_class_method :model_name_from_record_or_class
|
350
|
+
|
351
|
+
private
|
352
|
+
def inherited(base)
|
353
|
+
super
|
354
|
+
base.class_eval do
|
355
|
+
@_model_name = nil
|
356
|
+
end
|
357
|
+
end
|
339
358
|
end
|
340
359
|
end
|
data/lib/active_model/railtie.rb
CHANGED
@@ -9,6 +9,10 @@ module ActiveModel
|
|
9
9
|
|
10
10
|
config.active_model = ActiveSupport::OrderedOptions.new
|
11
11
|
|
12
|
+
initializer "active_model.deprecator", before: :load_environment_config do |app|
|
13
|
+
app.deprecators[:active_model] = ActiveModel.deprecator
|
14
|
+
end
|
15
|
+
|
12
16
|
initializer "active_model.secure_password" do
|
13
17
|
ActiveModel::SecurePassword.min_cost = Rails.env.test?
|
14
18
|
end
|
@@ -16,8 +16,8 @@ module ActiveModel
|
|
16
16
|
|
17
17
|
module ClassMethods
|
18
18
|
# Adds methods to set and authenticate against a BCrypt password.
|
19
|
-
# This mechanism requires you to have a +XXX_digest+ attribute
|
20
|
-
#
|
19
|
+
# This mechanism requires you to have a +XXX_digest+ attribute,
|
20
|
+
# where +XXX+ is the attribute name of your desired password.
|
21
21
|
#
|
22
22
|
# The following validations are added automatically:
|
23
23
|
# * Password must be present on creation
|
@@ -29,10 +29,17 @@ module ActiveModel
|
|
29
29
|
# it). When this attribute has a +nil+ value, the validation will not be
|
30
30
|
# triggered.
|
31
31
|
#
|
32
|
-
#
|
33
|
-
#
|
32
|
+
# Additionally, a +XXX_challenge+ attribute is created. When set to a
|
33
|
+
# value other than +nil+, it will validate against the currently persisted
|
34
|
+
# password. This validation relies on dirty tracking, as provided by
|
35
|
+
# ActiveModel::Dirty; if dirty tracking methods are not defined, this
|
36
|
+
# validation will fail.
|
34
37
|
#
|
35
|
-
#
|
38
|
+
# All of the above validations can be omitted by passing
|
39
|
+
# <tt>validations: false</tt> as an argument. This allows complete
|
40
|
+
# customizability of validation behavior.
|
41
|
+
#
|
42
|
+
# To use +has_secure_password+, add bcrypt (~> 3.1.7) to your Gemfile:
|
36
43
|
#
|
37
44
|
# gem 'bcrypt', '~> 3.1.7'
|
38
45
|
#
|
@@ -46,20 +53,30 @@ module ActiveModel
|
|
46
53
|
# has_secure_password :recovery_password, validations: false
|
47
54
|
# end
|
48
55
|
#
|
49
|
-
# user = User.new(name:
|
50
|
-
#
|
51
|
-
# user.password
|
52
|
-
# user.
|
53
|
-
# user.
|
54
|
-
# user.
|
56
|
+
# user = User.new(name: "david", password: "", password_confirmation: "nomatch")
|
57
|
+
#
|
58
|
+
# user.save # => false, password required
|
59
|
+
# user.password = "vr00m"
|
60
|
+
# user.save # => false, confirmation doesn't match
|
61
|
+
# user.password_confirmation = "vr00m"
|
62
|
+
# user.save # => true
|
63
|
+
#
|
64
|
+
# user.authenticate("notright") # => false
|
65
|
+
# user.authenticate("vr00m") # => user
|
66
|
+
# User.find_by(name: "david")&.authenticate("notright") # => false
|
67
|
+
# User.find_by(name: "david")&.authenticate("vr00m") # => user
|
68
|
+
#
|
55
69
|
# user.recovery_password = "42password"
|
56
|
-
# user.recovery_password_digest
|
57
|
-
# user.save
|
58
|
-
#
|
59
|
-
# user.
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
70
|
+
# user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
|
71
|
+
# user.save # => true
|
72
|
+
#
|
73
|
+
# user.authenticate_recovery_password("42password") # => user
|
74
|
+
#
|
75
|
+
# user.update(password: "pwn3d", password_challenge: "") # => false, challenge doesn't authenticate
|
76
|
+
# user.update(password: "nohack4u", password_challenge: "vr00m") # => true
|
77
|
+
#
|
78
|
+
# user.authenticate("vr00m") # => false, old password
|
79
|
+
# user.authenticate("nohack4u") # => user
|
63
80
|
#
|
64
81
|
# ===== Conditionally requiring a password
|
65
82
|
#
|
@@ -88,7 +105,7 @@ module ActiveModel
|
|
88
105
|
begin
|
89
106
|
require "bcrypt"
|
90
107
|
rescue LoadError
|
91
|
-
|
108
|
+
warn "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install."
|
92
109
|
raise
|
93
110
|
end
|
94
111
|
|
@@ -105,7 +122,24 @@ module ActiveModel
|
|
105
122
|
record.errors.add(attribute, :blank) unless record.public_send("#{attribute}_digest").present?
|
106
123
|
end
|
107
124
|
|
108
|
-
|
125
|
+
validate do |record|
|
126
|
+
if challenge = record.public_send(:"#{attribute}_challenge")
|
127
|
+
digest_was = record.public_send(:"#{attribute}_digest_was") if record.respond_to?(:"#{attribute}_digest_was")
|
128
|
+
|
129
|
+
unless digest_was.present? && BCrypt::Password.new(digest_was).is_password?(challenge)
|
130
|
+
record.errors.add(:"#{attribute}_challenge")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Validates that the password does not exceed the maximum allowed bytes for BCrypt (72 bytes).
|
136
|
+
validate do |record|
|
137
|
+
password_value = record.public_send(attribute)
|
138
|
+
if password_value.present? && password_value.bytesize > ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
|
139
|
+
record.errors.add(attribute, :password_too_long)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
109
143
|
validates_confirmation_of attribute, allow_blank: true
|
110
144
|
end
|
111
145
|
end
|
@@ -126,9 +160,7 @@ module ActiveModel
|
|
126
160
|
end
|
127
161
|
end
|
128
162
|
|
129
|
-
|
130
|
-
instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
|
131
|
-
end
|
163
|
+
attr_accessor :"#{attribute}_confirmation", :"#{attribute}_challenge"
|
132
164
|
|
133
165
|
# Returns +self+ if the password is correct, otherwise +false+.
|
134
166
|
#
|
@@ -145,6 +177,12 @@ module ActiveModel
|
|
145
177
|
attribute_digest.present? && BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
|
146
178
|
end
|
147
179
|
|
180
|
+
# Returns the salt, a small chunk of random data added to the password before it's hashed.
|
181
|
+
define_method("#{attribute}_salt") do
|
182
|
+
attribute_digest = public_send("#{attribute}_digest")
|
183
|
+
attribute_digest.present? ? BCrypt::Password.new(attribute_digest).salt : nil
|
184
|
+
end
|
185
|
+
|
148
186
|
alias_method :authenticate, :authenticate_password if attribute == :password
|
149
187
|
end
|
150
188
|
end
|