activemodel 6.0.0.beta3 → 6.0.2.rc2
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 +65 -3
- data/README.rdoc +3 -1
- data/lib/active_model/attribute_methods.rb +15 -14
- data/lib/active_model/attribute_mutation_tracker.rb +88 -29
- data/lib/active_model/attributes.rb +50 -11
- data/lib/active_model/dirty.rb +35 -98
- data/lib/active_model/errors.rb +17 -12
- data/lib/active_model/gem_version.rb +2 -2
- data/lib/active_model/railtie.rb +2 -2
- data/lib/active_model/secure_password.rb +21 -15
- data/lib/active_model/type/boolean.rb +10 -1
- data/lib/active_model/type/helpers/time_value.rb +10 -3
- data/lib/active_model/type/integer.rb +5 -0
- data/lib/active_model/validations/acceptance.rb +35 -23
- metadata +11 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f53fd7a85f2c82e65474f83bfea395162728cb37c5a2b0e54a66558a192b31ef
|
4
|
+
data.tar.gz: d066c69bfc2aa4d87b599ab00a79f53cd052814b174f0f8777592e0e6859c85a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b35e989bdf9139fab9fac79123771c2338cc5919f3f096b58b2918b74b79a60fb86cff8c634666f76b95ef59e21f71210feec3b483376218b08348a487d37789
|
7
|
+
data.tar.gz: 1b524687a3256eca1b51d21f68fc28e79cf8e8583e17d51057667b97d504d64cd27a9de093b473985577717ab24de9bc469db2b411aaa385bc3bde76d33fee49
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,55 @@
|
|
1
|
+
## Rails 6.0.2.rc2 (December 09, 2019) ##
|
2
|
+
|
3
|
+
* No changes.
|
4
|
+
|
5
|
+
|
6
|
+
## Rails 6.0.1 (November 5, 2019) ##
|
7
|
+
|
8
|
+
* No changes.
|
9
|
+
|
10
|
+
|
11
|
+
## Rails 6.0.0 (August 16, 2019) ##
|
12
|
+
|
13
|
+
* No changes.
|
14
|
+
|
15
|
+
|
16
|
+
## Rails 6.0.0.rc2 (July 22, 2019) ##
|
17
|
+
|
18
|
+
* No changes.
|
19
|
+
|
20
|
+
|
21
|
+
## Rails 6.0.0.rc1 (April 24, 2019) ##
|
22
|
+
|
23
|
+
* Type cast falsy boolean symbols on boolean attribute as false.
|
24
|
+
|
25
|
+
Fixes #35676.
|
26
|
+
|
27
|
+
*Ryuta Kamizono*
|
28
|
+
|
29
|
+
* Change how validation error translation strings are fetched: The new behavior
|
30
|
+
will first try the more specific keys, including doing locale fallback, then try
|
31
|
+
the less specific ones.
|
32
|
+
|
33
|
+
For example, this is the order in which keys will now be tried for a `blank`
|
34
|
+
error on a `product`'s `title` attribute with current locale set to `en-US`:
|
35
|
+
|
36
|
+
en-US.activerecord.errors.models.product.attributes.title.blank
|
37
|
+
en-US.activerecord.errors.models.product.blank
|
38
|
+
en-US.activerecord.errors.messages.blank
|
39
|
+
|
40
|
+
en.activerecord.errors.models.product.attributes.title.blank
|
41
|
+
en.activerecord.errors.models.product.blank
|
42
|
+
en.activerecord.errors.messages.blank
|
43
|
+
|
44
|
+
en-US.errors.attributes.title.blank
|
45
|
+
en-US.errors.messages.blank
|
46
|
+
|
47
|
+
en.errors.attributes.title.blank
|
48
|
+
en.errors.messages.blank
|
49
|
+
|
50
|
+
*Hugo Vacher*
|
51
|
+
|
52
|
+
|
1
53
|
## Rails 6.0.0.beta3 (March 11, 2019) ##
|
2
54
|
|
3
55
|
* No changes.
|
@@ -11,12 +63,12 @@
|
|
11
63
|
Before:
|
12
64
|
|
13
65
|
Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
|
14
|
-
=> #<Day id: nil, day: "0001-01-03", created_at: nil, updated_at: nil>
|
66
|
+
# => #<Day id: nil, day: "0001-01-03", created_at: nil, updated_at: nil>
|
15
67
|
|
16
68
|
After:
|
17
69
|
|
18
70
|
Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
|
19
|
-
=> #<Day id: nil, day: "0001-01-01", created_at: nil, updated_at: nil>
|
71
|
+
# => #<Day id: nil, day: "0001-01-01", created_at: nil, updated_at: nil>
|
20
72
|
|
21
73
|
Fixes #28521.
|
22
74
|
|
@@ -52,6 +104,16 @@
|
|
52
104
|
|
53
105
|
## Rails 6.0.0.beta1 (January 18, 2019) ##
|
54
106
|
|
107
|
+
* Internal calls to `human_attribute_name` on an `Active Model` now pass attributes as strings instead of symbols
|
108
|
+
in some cases.
|
109
|
+
|
110
|
+
This is in line with examples in Rails docs and puts the code in line with the intention -
|
111
|
+
the potential use of strings or symbols.
|
112
|
+
|
113
|
+
It is recommended to cast the attribute input to your desired type as if you you are overriding that methid.
|
114
|
+
|
115
|
+
*Martin Larochelle*
|
116
|
+
|
55
117
|
* Add `ActiveModel::Errors#of_kind?`.
|
56
118
|
|
57
119
|
*bogdanvlviv*, *Rafael Mendonça França*
|
@@ -106,7 +168,7 @@
|
|
106
168
|
|
107
169
|
*Unathi Chonco*
|
108
170
|
|
109
|
-
* Add `config.active_model.
|
171
|
+
* Add `config.active_model.i18n_customize_full_message` in order to control whether
|
110
172
|
the `full_message` error format can be overridden at the attribute or model
|
111
173
|
level in the locale files. This is `false` by default.
|
112
174
|
|
data/README.rdoc
CHANGED
@@ -5,6 +5,8 @@ They allow for Action Pack helpers to interact with non-Active Record models,
|
|
5
5
|
for example. Active Model also helps with building custom ORMs for use outside of
|
6
6
|
the Rails framework.
|
7
7
|
|
8
|
+
You can read more about Active Model in the {Active Model Basics}[https://edgeguides.rubyonrails.org/active_model_basics.html] guide.
|
9
|
+
|
8
10
|
Prior to Rails 3.0, if a plugin or gem developer wanted to have an object
|
9
11
|
interact with Action Pack helpers, it was required to either copy chunks of
|
10
12
|
code from Rails, or monkey patch entire helpers to make them handle objects
|
@@ -253,7 +255,7 @@ Active Model is released under the MIT license:
|
|
253
255
|
|
254
256
|
API documentation is at:
|
255
257
|
|
256
|
-
*
|
258
|
+
* https://api.rubyonrails.org
|
257
259
|
|
258
260
|
Bug reports for the Ruby on Rails project can be filed here:
|
259
261
|
|
@@ -286,12 +286,12 @@ module ActiveModel
|
|
286
286
|
method_name = matcher.method_name(attr_name)
|
287
287
|
|
288
288
|
unless instance_method_already_implemented?(method_name)
|
289
|
-
generate_method = "define_method_#{matcher.
|
289
|
+
generate_method = "define_method_#{matcher.target}"
|
290
290
|
|
291
291
|
if respond_to?(generate_method, true)
|
292
292
|
send(generate_method, attr_name.to_s)
|
293
293
|
else
|
294
|
-
define_proxy_call true, generated_attribute_methods, method_name, matcher.
|
294
|
+
define_proxy_call true, generated_attribute_methods, method_name, matcher.target, attr_name.to_s
|
295
295
|
end
|
296
296
|
end
|
297
297
|
end
|
@@ -352,17 +352,18 @@ module ActiveModel
|
|
352
352
|
|
353
353
|
def attribute_method_matchers_matching(method_name)
|
354
354
|
attribute_method_matchers_cache.compute_if_absent(method_name) do
|
355
|
-
#
|
356
|
-
#
|
355
|
+
# Bump plain matcher to last place so that only methods that do not
|
356
|
+
# match any other pattern match the actual attribute name.
|
357
|
+
# This is currently only needed to support legacy usage.
|
357
358
|
matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
|
358
|
-
matchers.map { |
|
359
|
+
matchers.map { |matcher| matcher.match(method_name) }.compact
|
359
360
|
end
|
360
361
|
end
|
361
362
|
|
362
363
|
# Define a method `name` in `mod` that dispatches to `send`
|
363
364
|
# using the given `extra` args. This falls back on `define_method`
|
364
365
|
# and `send` if the given names cannot be compiled.
|
365
|
-
def define_proxy_call(include_private, mod, name,
|
366
|
+
def define_proxy_call(include_private, mod, name, target, *extra)
|
366
367
|
defn = if NAME_COMPILABLE_REGEXP.match?(name)
|
367
368
|
"def #{name}(*args)"
|
368
369
|
else
|
@@ -371,34 +372,34 @@ module ActiveModel
|
|
371
372
|
|
372
373
|
extra = (extra.map!(&:inspect) << "*args").join(", ")
|
373
374
|
|
374
|
-
|
375
|
-
"#{"self." unless include_private}#{
|
375
|
+
body = if CALL_COMPILABLE_REGEXP.match?(target)
|
376
|
+
"#{"self." unless include_private}#{target}(#{extra})"
|
376
377
|
else
|
377
|
-
"send(:'#{
|
378
|
+
"send(:'#{target}', #{extra})"
|
378
379
|
end
|
379
380
|
|
380
381
|
mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
381
382
|
#{defn}
|
382
|
-
#{
|
383
|
+
#{body}
|
383
384
|
end
|
384
385
|
RUBY
|
385
386
|
end
|
386
387
|
|
387
388
|
class AttributeMethodMatcher #:nodoc:
|
388
|
-
attr_reader :prefix, :suffix, :
|
389
|
+
attr_reader :prefix, :suffix, :target
|
389
390
|
|
390
|
-
AttributeMethodMatch = Struct.new(:target, :attr_name
|
391
|
+
AttributeMethodMatch = Struct.new(:target, :attr_name)
|
391
392
|
|
392
393
|
def initialize(options = {})
|
393
394
|
@prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "")
|
394
395
|
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
|
395
|
-
@
|
396
|
+
@target = "#{@prefix}attribute#{@suffix}"
|
396
397
|
@method_name = "#{prefix}%s#{suffix}"
|
397
398
|
end
|
398
399
|
|
399
400
|
def match(method_name)
|
400
401
|
if @regex =~ method_name
|
401
|
-
AttributeMethodMatch.new(
|
402
|
+
AttributeMethodMatch.new(target, $1)
|
402
403
|
end
|
403
404
|
end
|
404
405
|
|
@@ -1,14 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
require "active_support/core_ext/object/duplicable"
|
4
5
|
|
5
6
|
module ActiveModel
|
6
7
|
class AttributeMutationTracker # :nodoc:
|
7
8
|
OPTION_NOT_GIVEN = Object.new
|
8
9
|
|
9
|
-
def initialize(attributes)
|
10
|
+
def initialize(attributes, forced_changes = Set.new)
|
10
11
|
@attributes = attributes
|
11
|
-
@forced_changes =
|
12
|
+
@forced_changes = forced_changes
|
12
13
|
end
|
13
14
|
|
14
15
|
def changed_attribute_names
|
@@ -18,24 +19,22 @@ module ActiveModel
|
|
18
19
|
def changed_values
|
19
20
|
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
20
21
|
if changed?(attr_name)
|
21
|
-
result[attr_name] =
|
22
|
+
result[attr_name] = original_value(attr_name)
|
22
23
|
end
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
27
|
def changes
|
27
28
|
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
28
|
-
change = change_to_attribute(attr_name)
|
29
|
-
if change
|
29
|
+
if change = change_to_attribute(attr_name)
|
30
30
|
result.merge!(attr_name => change)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
35
|
def change_to_attribute(attr_name)
|
36
|
-
attr_name = attr_name.to_s
|
37
36
|
if changed?(attr_name)
|
38
|
-
[
|
37
|
+
[original_value(attr_name), fetch_value(attr_name)]
|
39
38
|
end
|
40
39
|
end
|
41
40
|
|
@@ -44,29 +43,26 @@ module ActiveModel
|
|
44
43
|
end
|
45
44
|
|
46
45
|
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
|
47
|
-
attr_name
|
48
|
-
|
49
|
-
|
50
|
-
(OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) &&
|
51
|
-
(OPTION_NOT_GIVEN == to || attributes[attr_name].value == to)
|
46
|
+
attribute_changed?(attr_name) &&
|
47
|
+
(OPTION_NOT_GIVEN == from || original_value(attr_name) == from) &&
|
48
|
+
(OPTION_NOT_GIVEN == to || fetch_value(attr_name) == to)
|
52
49
|
end
|
53
50
|
|
54
51
|
def changed_in_place?(attr_name)
|
55
|
-
attributes[attr_name
|
52
|
+
attributes[attr_name].changed_in_place?
|
56
53
|
end
|
57
54
|
|
58
55
|
def forget_change(attr_name)
|
59
|
-
attr_name = attr_name.to_s
|
60
56
|
attributes[attr_name] = attributes[attr_name].forgetting_assignment
|
61
57
|
forced_changes.delete(attr_name)
|
62
58
|
end
|
63
59
|
|
64
60
|
def original_value(attr_name)
|
65
|
-
attributes[attr_name
|
61
|
+
attributes[attr_name].original_value
|
66
62
|
end
|
67
63
|
|
68
64
|
def force_change(attr_name)
|
69
|
-
forced_changes << attr_name
|
65
|
+
forced_changes << attr_name
|
70
66
|
end
|
71
67
|
|
72
68
|
private
|
@@ -75,45 +71,108 @@ module ActiveModel
|
|
75
71
|
def attr_names
|
76
72
|
attributes.keys
|
77
73
|
end
|
74
|
+
|
75
|
+
def attribute_changed?(attr_name)
|
76
|
+
forced_changes.include?(attr_name) || !!attributes[attr_name].changed?
|
77
|
+
end
|
78
|
+
|
79
|
+
def fetch_value(attr_name)
|
80
|
+
attributes.fetch_value(attr_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class ForcedMutationTracker < AttributeMutationTracker # :nodoc:
|
85
|
+
def initialize(attributes, forced_changes = {})
|
86
|
+
super
|
87
|
+
@finalized_changes = nil
|
88
|
+
end
|
89
|
+
|
90
|
+
def changed_in_place?(attr_name)
|
91
|
+
false
|
92
|
+
end
|
93
|
+
|
94
|
+
def change_to_attribute(attr_name)
|
95
|
+
if finalized_changes&.include?(attr_name)
|
96
|
+
finalized_changes[attr_name].dup
|
97
|
+
else
|
98
|
+
super
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def forget_change(attr_name)
|
103
|
+
forced_changes.delete(attr_name)
|
104
|
+
end
|
105
|
+
|
106
|
+
def original_value(attr_name)
|
107
|
+
if changed?(attr_name)
|
108
|
+
forced_changes[attr_name]
|
109
|
+
else
|
110
|
+
fetch_value(attr_name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def force_change(attr_name)
|
115
|
+
forced_changes[attr_name] = clone_value(attr_name) unless attribute_changed?(attr_name)
|
116
|
+
end
|
117
|
+
|
118
|
+
def finalize_changes
|
119
|
+
@finalized_changes = changes
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
attr_reader :finalized_changes
|
124
|
+
|
125
|
+
def attr_names
|
126
|
+
forced_changes.keys
|
127
|
+
end
|
128
|
+
|
129
|
+
def attribute_changed?(attr_name)
|
130
|
+
forced_changes.include?(attr_name)
|
131
|
+
end
|
132
|
+
|
133
|
+
def fetch_value(attr_name)
|
134
|
+
attributes.send(:_read_attribute, attr_name)
|
135
|
+
end
|
136
|
+
|
137
|
+
def clone_value(attr_name)
|
138
|
+
value = fetch_value(attr_name)
|
139
|
+
value.duplicable? ? value.clone : value
|
140
|
+
rescue TypeError, NoMethodError
|
141
|
+
value
|
142
|
+
end
|
78
143
|
end
|
79
144
|
|
80
145
|
class NullMutationTracker # :nodoc:
|
81
146
|
include Singleton
|
82
147
|
|
83
|
-
def changed_attribute_names
|
148
|
+
def changed_attribute_names
|
84
149
|
[]
|
85
150
|
end
|
86
151
|
|
87
|
-
def changed_values
|
152
|
+
def changed_values
|
88
153
|
{}
|
89
154
|
end
|
90
155
|
|
91
|
-
def changes
|
156
|
+
def changes
|
92
157
|
{}
|
93
158
|
end
|
94
159
|
|
95
160
|
def change_to_attribute(attr_name)
|
96
161
|
end
|
97
162
|
|
98
|
-
def any_changes?
|
163
|
+
def any_changes?
|
99
164
|
false
|
100
165
|
end
|
101
166
|
|
102
|
-
def changed?(
|
167
|
+
def changed?(attr_name, **)
|
103
168
|
false
|
104
169
|
end
|
105
170
|
|
106
|
-
def changed_in_place?(
|
171
|
+
def changed_in_place?(attr_name)
|
107
172
|
false
|
108
173
|
end
|
109
174
|
|
110
|
-
def
|
111
|
-
end
|
112
|
-
|
113
|
-
def original_value(*)
|
114
|
-
end
|
115
|
-
|
116
|
-
def force_change(*)
|
175
|
+
def original_value(attr_name)
|
117
176
|
end
|
118
177
|
end
|
119
178
|
end
|
@@ -26,6 +26,21 @@ module ActiveModel
|
|
26
26
|
define_attribute_method(name)
|
27
27
|
end
|
28
28
|
|
29
|
+
# Returns an array of attribute names as strings
|
30
|
+
#
|
31
|
+
# class Person
|
32
|
+
# include ActiveModel::Attributes
|
33
|
+
#
|
34
|
+
# attribute :name, :string
|
35
|
+
# attribute :age, :integer
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# Person.attribute_names
|
39
|
+
# # => ["name", "age"]
|
40
|
+
def attribute_names
|
41
|
+
attribute_types.keys
|
42
|
+
end
|
43
|
+
|
29
44
|
private
|
30
45
|
|
31
46
|
def define_method_attribute=(name)
|
@@ -65,33 +80,57 @@ module ActiveModel
|
|
65
80
|
super
|
66
81
|
end
|
67
82
|
|
83
|
+
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
84
|
+
#
|
85
|
+
# class Person
|
86
|
+
# include ActiveModel::Model
|
87
|
+
# include ActiveModel::Attributes
|
88
|
+
#
|
89
|
+
# attribute :name, :string
|
90
|
+
# attribute :age, :integer
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# person = Person.new(name: 'Francesco', age: 22)
|
94
|
+
# person.attributes
|
95
|
+
# # => {"name"=>"Francesco", "age"=>22}
|
68
96
|
def attributes
|
69
97
|
@attributes.to_hash
|
70
98
|
end
|
71
99
|
|
100
|
+
# Returns an array of attribute names as strings
|
101
|
+
#
|
102
|
+
# class Person
|
103
|
+
# include ActiveModel::Attributes
|
104
|
+
#
|
105
|
+
# attribute :name, :string
|
106
|
+
# attribute :age, :integer
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# person = Person.new
|
110
|
+
# person.attribute_names
|
111
|
+
# # => ["name", "age"]
|
112
|
+
def attribute_names
|
113
|
+
@attributes.keys
|
114
|
+
end
|
115
|
+
|
72
116
|
private
|
73
117
|
|
74
118
|
def write_attribute(attr_name, value)
|
75
|
-
name =
|
76
|
-
|
77
|
-
else
|
78
|
-
attr_name.to_s
|
79
|
-
end
|
119
|
+
name = attr_name.to_s
|
120
|
+
name = self.class.attribute_aliases[name] || name
|
80
121
|
|
81
122
|
@attributes.write_from_user(name, value)
|
82
123
|
value
|
83
124
|
end
|
84
125
|
|
85
126
|
def attribute(attr_name)
|
86
|
-
name =
|
87
|
-
|
88
|
-
|
89
|
-
attr_name.to_s
|
90
|
-
end
|
127
|
+
name = attr_name.to_s
|
128
|
+
name = self.class.attribute_aliases[name] || name
|
129
|
+
|
91
130
|
@attributes.fetch_value(name)
|
92
131
|
end
|
93
132
|
|
94
|
-
#
|
133
|
+
# Dispatch target for <tt>*=</tt> attribute methods.
|
95
134
|
def attribute=(attribute_name, value)
|
96
135
|
write_attribute(attribute_name, value)
|
97
136
|
end
|
data/lib/active_model/dirty.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/hash_with_indifferent_access"
|
4
|
-
require "active_support/core_ext/object/duplicable"
|
5
3
|
require "active_model/attribute_mutation_tracker"
|
6
4
|
|
7
5
|
module ActiveModel
|
@@ -122,9 +120,6 @@ module ActiveModel
|
|
122
120
|
extend ActiveSupport::Concern
|
123
121
|
include ActiveModel::AttributeMethods
|
124
122
|
|
125
|
-
OPTION_NOT_GIVEN = Object.new # :nodoc:
|
126
|
-
private_constant :OPTION_NOT_GIVEN
|
127
|
-
|
128
123
|
included do
|
129
124
|
attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
|
130
125
|
attribute_method_suffix "_previously_changed?", "_previous_change"
|
@@ -145,10 +140,9 @@ module ActiveModel
|
|
145
140
|
# +mutations_from_database+ to +mutations_before_last_save+ respectively.
|
146
141
|
def changes_applied
|
147
142
|
unless defined?(@attributes)
|
148
|
-
|
143
|
+
mutations_from_database.finalize_changes
|
149
144
|
end
|
150
145
|
@mutations_before_last_save = mutations_from_database
|
151
|
-
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
152
146
|
forget_attribute_assignments
|
153
147
|
@mutations_from_database = nil
|
154
148
|
end
|
@@ -159,7 +153,7 @@ module ActiveModel
|
|
159
153
|
# person.name = 'bob'
|
160
154
|
# person.changed? # => true
|
161
155
|
def changed?
|
162
|
-
|
156
|
+
mutations_from_database.any_changes?
|
163
157
|
end
|
164
158
|
|
165
159
|
# Returns an array with the name of the attributes with unsaved changes.
|
@@ -168,42 +162,37 @@ module ActiveModel
|
|
168
162
|
# person.name = 'bob'
|
169
163
|
# person.changed # => ["name"]
|
170
164
|
def changed
|
171
|
-
|
165
|
+
mutations_from_database.changed_attribute_names
|
172
166
|
end
|
173
167
|
|
174
|
-
#
|
175
|
-
def attribute_changed?(
|
176
|
-
|
177
|
-
(to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) &&
|
178
|
-
(from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
|
168
|
+
# Dispatch target for <tt>*_changed?</tt> attribute methods.
|
169
|
+
def attribute_changed?(attr_name, **options) # :nodoc:
|
170
|
+
mutations_from_database.changed?(attr_name.to_s, options)
|
179
171
|
end
|
180
172
|
|
181
|
-
#
|
182
|
-
def attribute_was(
|
183
|
-
|
173
|
+
# Dispatch target for <tt>*_was</tt> attribute methods.
|
174
|
+
def attribute_was(attr_name) # :nodoc:
|
175
|
+
mutations_from_database.original_value(attr_name.to_s)
|
184
176
|
end
|
185
177
|
|
186
|
-
#
|
187
|
-
def attribute_previously_changed?(
|
188
|
-
|
178
|
+
# 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)
|
189
181
|
end
|
190
182
|
|
191
183
|
# Restore all previous data of the provided attributes.
|
192
|
-
def restore_attributes(
|
193
|
-
|
184
|
+
def restore_attributes(attr_names = changed)
|
185
|
+
attr_names.each { |attr_name| restore_attribute!(attr_name) }
|
194
186
|
end
|
195
187
|
|
196
188
|
# Clears all dirty data: current changes and previous changes.
|
197
189
|
def clear_changes_information
|
198
|
-
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
199
190
|
@mutations_before_last_save = nil
|
200
|
-
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
201
191
|
forget_attribute_assignments
|
202
192
|
@mutations_from_database = nil
|
203
193
|
end
|
204
194
|
|
205
195
|
def clear_attribute_changes(attr_names)
|
206
|
-
attributes_changed_by_setter.except!(*attr_names)
|
207
196
|
attr_names.each do |attr_name|
|
208
197
|
clear_attribute_change(attr_name)
|
209
198
|
end
|
@@ -216,13 +205,7 @@ module ActiveModel
|
|
216
205
|
# person.name = 'robert'
|
217
206
|
# person.changed_attributes # => {"name" => "bob"}
|
218
207
|
def changed_attributes
|
219
|
-
|
220
|
-
# multiple times when it is known that the computed value cannot change.
|
221
|
-
if defined?(@cached_changed_attributes)
|
222
|
-
@cached_changed_attributes
|
223
|
-
else
|
224
|
-
attributes_changed_by_setter.reverse_merge(mutations_from_database.changed_values).freeze
|
225
|
-
end
|
208
|
+
mutations_from_database.changed_values
|
226
209
|
end
|
227
210
|
|
228
211
|
# Returns a hash of changed attributes indicating their original
|
@@ -232,9 +215,7 @@ module ActiveModel
|
|
232
215
|
# person.name = 'bob'
|
233
216
|
# person.changes # => { "name" => ["bill", "bob"] }
|
234
217
|
def changes
|
235
|
-
|
236
|
-
ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
|
237
|
-
end
|
218
|
+
mutations_from_database.changes
|
238
219
|
end
|
239
220
|
|
240
221
|
# Returns a hash of attributes that were changed before the model was saved.
|
@@ -244,27 +225,23 @@ module ActiveModel
|
|
244
225
|
# person.save
|
245
226
|
# person.previous_changes # => {"name" => ["bob", "robert"]}
|
246
227
|
def previous_changes
|
247
|
-
|
248
|
-
@previously_changed.merge(mutations_before_last_save.changes)
|
228
|
+
mutations_before_last_save.changes
|
249
229
|
end
|
250
230
|
|
251
231
|
def attribute_changed_in_place?(attr_name) # :nodoc:
|
252
|
-
mutations_from_database.changed_in_place?(attr_name)
|
232
|
+
mutations_from_database.changed_in_place?(attr_name.to_s)
|
253
233
|
end
|
254
234
|
|
255
235
|
private
|
256
236
|
def clear_attribute_change(attr_name)
|
257
|
-
mutations_from_database.forget_change(attr_name)
|
237
|
+
mutations_from_database.forget_change(attr_name.to_s)
|
258
238
|
end
|
259
239
|
|
260
240
|
def mutations_from_database
|
261
|
-
unless defined?(@mutations_from_database)
|
262
|
-
@mutations_from_database = nil
|
263
|
-
end
|
264
241
|
@mutations_from_database ||= if defined?(@attributes)
|
265
242
|
ActiveModel::AttributeMutationTracker.new(@attributes)
|
266
243
|
else
|
267
|
-
|
244
|
+
ActiveModel::ForcedMutationTracker.new(self)
|
268
245
|
end
|
269
246
|
end
|
270
247
|
|
@@ -276,68 +253,28 @@ module ActiveModel
|
|
276
253
|
@mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
|
277
254
|
end
|
278
255
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
ensure
|
283
|
-
clear_changed_attributes_cache
|
284
|
-
end
|
285
|
-
|
286
|
-
def clear_changed_attributes_cache
|
287
|
-
remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
|
288
|
-
end
|
289
|
-
|
290
|
-
# Returns +true+ if attr_name is changed, +false+ otherwise.
|
291
|
-
def changes_include?(attr_name)
|
292
|
-
attributes_changed_by_setter.include?(attr_name) || mutations_from_database.changed?(attr_name)
|
293
|
-
end
|
294
|
-
alias attribute_changed_by_setter? changes_include?
|
295
|
-
|
296
|
-
# Returns +true+ if attr_name were changed before the model was saved,
|
297
|
-
# +false+ otherwise.
|
298
|
-
def previous_changes_include?(attr_name)
|
299
|
-
previous_changes.include?(attr_name)
|
300
|
-
end
|
301
|
-
|
302
|
-
# Handles <tt>*_change</tt> for +method_missing+.
|
303
|
-
def attribute_change(attr)
|
304
|
-
[changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
|
256
|
+
# Dispatch target for <tt>*_change</tt> attribute methods.
|
257
|
+
def attribute_change(attr_name)
|
258
|
+
mutations_from_database.change_to_attribute(attr_name.to_s)
|
305
259
|
end
|
306
260
|
|
307
|
-
#
|
308
|
-
def attribute_previous_change(
|
309
|
-
|
261
|
+
# Dispatch target for <tt>*_previous_change</tt> attribute methods.
|
262
|
+
def attribute_previous_change(attr_name)
|
263
|
+
mutations_before_last_save.change_to_attribute(attr_name.to_s)
|
310
264
|
end
|
311
265
|
|
312
|
-
#
|
313
|
-
def attribute_will_change!(
|
314
|
-
|
315
|
-
begin
|
316
|
-
value = _read_attribute(attr)
|
317
|
-
value = value.duplicable? ? value.clone : value
|
318
|
-
rescue TypeError, NoMethodError
|
319
|
-
end
|
320
|
-
|
321
|
-
set_attribute_was(attr, value)
|
322
|
-
end
|
323
|
-
mutations_from_database.force_change(attr)
|
266
|
+
# Dispatch target for <tt>*_will_change!</tt> attribute methods.
|
267
|
+
def attribute_will_change!(attr_name)
|
268
|
+
mutations_from_database.force_change(attr_name.to_s)
|
324
269
|
end
|
325
270
|
|
326
|
-
#
|
327
|
-
def restore_attribute!(
|
328
|
-
|
329
|
-
|
330
|
-
|
271
|
+
# Dispatch target for <tt>restore_*!</tt> attribute methods.
|
272
|
+
def restore_attribute!(attr_name)
|
273
|
+
attr_name = attr_name.to_s
|
274
|
+
if attribute_changed?(attr_name)
|
275
|
+
__send__("#{attr_name}=", attribute_was(attr_name))
|
276
|
+
clear_attribute_change(attr_name)
|
331
277
|
end
|
332
278
|
end
|
333
|
-
|
334
|
-
def attributes_changed_by_setter
|
335
|
-
@attributes_changed_by_setter ||= ActiveSupport::HashWithIndifferentAccess.new
|
336
|
-
end
|
337
|
-
|
338
|
-
# Force an attribute to have a particular "before" value
|
339
|
-
def set_attribute_was(attr, old_value)
|
340
|
-
attributes_changed_by_setter[attr] = old_value
|
341
|
-
end
|
342
279
|
end
|
343
280
|
end
|
data/lib/active_model/errors.rb
CHANGED
@@ -63,9 +63,9 @@ module ActiveModel
|
|
63
63
|
MESSAGE_OPTIONS = [:message]
|
64
64
|
|
65
65
|
class << self
|
66
|
-
attr_accessor :
|
66
|
+
attr_accessor :i18n_customize_full_message # :nodoc:
|
67
67
|
end
|
68
|
-
self.
|
68
|
+
self.i18n_customize_full_message = false
|
69
69
|
|
70
70
|
attr_reader :messages, :details
|
71
71
|
|
@@ -413,7 +413,7 @@ module ActiveModel
|
|
413
413
|
return message if attribute == :base
|
414
414
|
attribute = attribute.to_s
|
415
415
|
|
416
|
-
if self.class.
|
416
|
+
if self.class.i18n_customize_full_message && @base.class.respond_to?(:i18n_scope)
|
417
417
|
attribute = attribute.remove(/\[\d\]/)
|
418
418
|
parts = attribute.split(".")
|
419
419
|
attribute_name = parts.pop
|
@@ -479,6 +479,14 @@ module ActiveModel
|
|
479
479
|
# * <tt>errors.messages.blank</tt>
|
480
480
|
def generate_message(attribute, type = :invalid, options = {})
|
481
481
|
type = options.delete(:message) if options[:message].is_a?(Symbol)
|
482
|
+
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
|
483
|
+
|
484
|
+
options = {
|
485
|
+
model: @base.model_name.human,
|
486
|
+
attribute: @base.class.human_attribute_name(attribute),
|
487
|
+
value: value,
|
488
|
+
object: @base
|
489
|
+
}.merge!(options)
|
482
490
|
|
483
491
|
if @base.class.respond_to?(:i18n_scope)
|
484
492
|
i18n_scope = @base.class.i18n_scope.to_s
|
@@ -487,6 +495,11 @@ module ActiveModel
|
|
487
495
|
:"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
|
488
496
|
end
|
489
497
|
defaults << :"#{i18n_scope}.errors.messages.#{type}"
|
498
|
+
|
499
|
+
catch(:exception) do
|
500
|
+
translation = I18n.translate(defaults.first, options.merge(default: defaults.drop(1), throw: true))
|
501
|
+
return translation unless translation.nil?
|
502
|
+
end unless options[:message]
|
490
503
|
else
|
491
504
|
defaults = []
|
492
505
|
end
|
@@ -496,15 +509,7 @@ module ActiveModel
|
|
496
509
|
|
497
510
|
key = defaults.shift
|
498
511
|
defaults = options.delete(:message) if options[:message]
|
499
|
-
|
500
|
-
|
501
|
-
options = {
|
502
|
-
default: defaults,
|
503
|
-
model: @base.model_name.human,
|
504
|
-
attribute: @base.class.human_attribute_name(attribute),
|
505
|
-
value: value,
|
506
|
-
object: @base
|
507
|
-
}.merge!(options)
|
512
|
+
options[:default] = defaults
|
508
513
|
|
509
514
|
I18n.translate(key, options)
|
510
515
|
end
|
data/lib/active_model/railtie.rb
CHANGED
@@ -13,8 +13,8 @@ module ActiveModel
|
|
13
13
|
ActiveModel::SecurePassword.min_cost = Rails.env.test?
|
14
14
|
end
|
15
15
|
|
16
|
-
initializer "active_model.
|
17
|
-
ActiveModel::Errors.
|
16
|
+
initializer "active_model.i18n_customize_full_message" do
|
17
|
+
ActiveModel::Errors.i18n_customize_full_message = config.active_model.delete(:i18n_customize_full_message) || false
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -69,6 +69,27 @@ module ActiveModel
|
|
69
69
|
raise
|
70
70
|
end
|
71
71
|
|
72
|
+
include InstanceMethodsOnActivation.new(attribute)
|
73
|
+
|
74
|
+
if validations
|
75
|
+
include ActiveModel::Validations
|
76
|
+
|
77
|
+
# This ensures the model has a password by checking whether the password_digest
|
78
|
+
# is present, so that this works with both new and existing records. However,
|
79
|
+
# when there is an error, the message is added to the password attribute instead
|
80
|
+
# so that the error message will make sense to the end-user.
|
81
|
+
validate do |record|
|
82
|
+
record.errors.add(attribute, :blank) unless record.send("#{attribute}_digest").present?
|
83
|
+
end
|
84
|
+
|
85
|
+
validates_length_of attribute, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
|
86
|
+
validates_confirmation_of attribute, allow_blank: true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class InstanceMethodsOnActivation < Module
|
92
|
+
def initialize(attribute)
|
72
93
|
attr_reader attribute
|
73
94
|
|
74
95
|
define_method("#{attribute}=") do |unencrypted_password|
|
@@ -101,21 +122,6 @@ module ActiveModel
|
|
101
122
|
end
|
102
123
|
|
103
124
|
alias_method :authenticate, :authenticate_password if attribute == :password
|
104
|
-
|
105
|
-
if validations
|
106
|
-
include ActiveModel::Validations
|
107
|
-
|
108
|
-
# This ensures the model has a password by checking whether the password_digest
|
109
|
-
# is present, so that this works with both new and existing records. However,
|
110
|
-
# when there is an error, the message is added to the password attribute instead
|
111
|
-
# so that the error message will make sense to the end-user.
|
112
|
-
validate do |record|
|
113
|
-
record.errors.add(attribute, :blank) unless record.send("#{attribute}_digest").present?
|
114
|
-
end
|
115
|
-
|
116
|
-
validates_length_of attribute, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
|
117
|
-
validates_confirmation_of attribute, allow_blank: true
|
118
|
-
end
|
119
125
|
end
|
120
126
|
end
|
121
127
|
end
|
@@ -14,7 +14,16 @@ module ActiveModel
|
|
14
14
|
# - Empty strings are coerced to +nil+
|
15
15
|
# - All other values will be coerced to +true+
|
16
16
|
class Boolean < Value
|
17
|
-
FALSE_VALUES = [
|
17
|
+
FALSE_VALUES = [
|
18
|
+
false, 0,
|
19
|
+
"0", :"0",
|
20
|
+
"f", :f,
|
21
|
+
"F", :F,
|
22
|
+
"false", :false,
|
23
|
+
"FALSE", :FALSE,
|
24
|
+
"off", :off,
|
25
|
+
"OFF", :OFF,
|
26
|
+
].to_set.freeze
|
18
27
|
|
19
28
|
def type # :nodoc:
|
20
29
|
:boolean
|
@@ -22,10 +22,17 @@ module ActiveModel
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def apply_seconds_precision(value)
|
25
|
-
return value unless precision && value.respond_to?(:
|
26
|
-
|
25
|
+
return value unless precision && value.respond_to?(:nsec)
|
26
|
+
|
27
|
+
number_of_insignificant_digits = 9 - precision
|
27
28
|
round_power = 10**number_of_insignificant_digits
|
28
|
-
|
29
|
+
rounded_off_nsec = value.nsec % round_power
|
30
|
+
|
31
|
+
if rounded_off_nsec > 0
|
32
|
+
value.change(nsec: value.nsec - rounded_off_nsec)
|
33
|
+
else
|
34
|
+
value
|
35
|
+
end
|
29
36
|
end
|
30
37
|
|
31
38
|
def type_cast_for_schema(value)
|
@@ -17,7 +17,8 @@ module ActiveModel
|
|
17
17
|
private
|
18
18
|
|
19
19
|
def setup!(klass)
|
20
|
-
|
20
|
+
define_attributes = LazilyDefineAttributes.new(attributes)
|
21
|
+
klass.include(define_attributes) unless klass.included_modules.include?(define_attributes)
|
21
22
|
end
|
22
23
|
|
23
24
|
def acceptable_option?(value)
|
@@ -25,46 +26,57 @@ module ActiveModel
|
|
25
26
|
end
|
26
27
|
|
27
28
|
class LazilyDefineAttributes < Module
|
28
|
-
def initialize(
|
29
|
+
def initialize(attributes)
|
30
|
+
@attributes = attributes.map(&:to_s)
|
31
|
+
end
|
32
|
+
|
33
|
+
def included(klass)
|
34
|
+
@lock = Mutex.new
|
35
|
+
mod = self
|
36
|
+
|
29
37
|
define_method(:respond_to_missing?) do |method_name, include_private = false|
|
30
|
-
|
38
|
+
mod.define_on(klass)
|
39
|
+
super(method_name, include_private) || mod.matches?(method_name)
|
31
40
|
end
|
32
41
|
|
33
42
|
define_method(:method_missing) do |method_name, *args, &block|
|
34
|
-
|
35
|
-
|
43
|
+
mod.define_on(klass)
|
44
|
+
if mod.matches?(method_name)
|
36
45
|
send(method_name, *args, &block)
|
37
46
|
else
|
38
47
|
super(method_name, *args, &block)
|
39
48
|
end
|
40
49
|
end
|
41
50
|
end
|
42
|
-
end
|
43
|
-
|
44
|
-
class AttributeDefinition
|
45
|
-
def initialize(attributes)
|
46
|
-
@attributes = attributes.map(&:to_s)
|
47
|
-
end
|
48
51
|
|
49
52
|
def matches?(method_name)
|
50
|
-
attr_name =
|
51
|
-
attributes.
|
53
|
+
attr_name = method_name.to_s.chomp("=")
|
54
|
+
attributes.any? { |name| name == attr_name }
|
52
55
|
end
|
53
56
|
|
54
57
|
def define_on(klass)
|
55
|
-
|
56
|
-
|
57
|
-
klass.define_attribute_methods
|
58
|
-
klass.attr_reader(*attr_readers)
|
59
|
-
klass.attr_writer(*attr_writers)
|
60
|
-
end
|
58
|
+
@lock&.synchronize do
|
59
|
+
return unless @lock
|
61
60
|
|
62
|
-
|
63
|
-
|
61
|
+
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
|
62
|
+
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
|
63
|
+
|
64
|
+
attr_reader(*attr_readers)
|
65
|
+
attr_writer(*attr_writers)
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
+
remove_method :respond_to_missing?
|
68
|
+
remove_method :method_missing
|
69
|
+
|
70
|
+
@lock = nil
|
67
71
|
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def ==(other)
|
75
|
+
self.class == other.class && attributes == other.attributes
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
attr_reader :attributes
|
68
80
|
end
|
69
81
|
end
|
70
82
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activemodel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.0.
|
4
|
+
version: 6.0.2.rc2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 6.0.
|
19
|
+
version: 6.0.2.rc2
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 6.0.
|
26
|
+
version: 6.0.2.rc2
|
27
27
|
description: A toolkit for building modeling frameworks like Active Record. Rich support
|
28
28
|
for attributes, callbacks, validations, serialization, internationalization, and
|
29
29
|
testing.
|
@@ -97,12 +97,15 @@ files:
|
|
97
97
|
- lib/active_model/validations/with.rb
|
98
98
|
- lib/active_model/validator.rb
|
99
99
|
- lib/active_model/version.rb
|
100
|
-
homepage:
|
100
|
+
homepage: https://rubyonrails.org
|
101
101
|
licenses:
|
102
102
|
- MIT
|
103
103
|
metadata:
|
104
|
-
|
105
|
-
changelog_uri: https://github.com/rails/rails/blob/v6.0.
|
104
|
+
bug_tracker_uri: https://github.com/rails/rails/issues
|
105
|
+
changelog_uri: https://github.com/rails/rails/blob/v6.0.2.rc2/activemodel/CHANGELOG.md
|
106
|
+
documentation_uri: https://api.rubyonrails.org/v6.0.2.rc2/
|
107
|
+
mailing_list_uri: https://groups.google.com/forum/#!forum/rubyonrails-talk
|
108
|
+
source_code_uri: https://github.com/rails/rails/tree/v6.0.2.rc2/activemodel
|
106
109
|
post_install_message:
|
107
110
|
rdoc_options: []
|
108
111
|
require_paths:
|
@@ -118,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
121
|
- !ruby/object:Gem::Version
|
119
122
|
version: 1.3.1
|
120
123
|
requirements: []
|
121
|
-
rubygems_version: 3.0.
|
124
|
+
rubygems_version: 3.0.3
|
122
125
|
signing_key:
|
123
126
|
specification_version: 4
|
124
127
|
summary: A toolkit for building modeling frameworks (part of Rails).
|