activemodel 6.0.5.1 → 6.1.7.4
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 +95 -160
- data/MIT-LICENSE +1 -2
- data/README.rdoc +1 -1
- data/lib/active_model/attribute.rb +18 -17
- data/lib/active_model/attribute_assignment.rb +3 -4
- data/lib/active_model/attribute_methods.rb +74 -38
- data/lib/active_model/attribute_mutation_tracker.rb +8 -5
- data/lib/active_model/attribute_set/builder.rb +80 -13
- data/lib/active_model/attribute_set.rb +18 -16
- data/lib/active_model/attributes.rb +20 -24
- data/lib/active_model/callbacks.rb +1 -1
- data/lib/active_model/dirty.rb +17 -4
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +316 -208
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/naming.rb +2 -2
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +1 -1
- data/lib/active_model/secure_password.rb +15 -14
- data/lib/active_model/serialization.rb +9 -6
- data/lib/active_model/serializers/json.rb +7 -0
- data/lib/active_model/type/date_time.rb +2 -2
- data/lib/active_model/type/float.rb +2 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +11 -7
- data/lib/active_model/type/helpers/numeric.rb +8 -3
- data/lib/active_model/type/helpers/time_value.rb +27 -17
- data/lib/active_model/type/helpers/timezone.rb +1 -1
- data/lib/active_model/type/immutable_string.rb +14 -10
- data/lib/active_model/type/integer.rb +11 -2
- data/lib/active_model/type/registry.rb +12 -9
- data/lib/active_model/type/string.rb +12 -2
- data/lib/active_model/type/value.rb +9 -1
- data/lib/active_model/type.rb +3 -2
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/callbacks.rb +15 -15
- data/lib/active_model/validations/clusivity.rb +5 -1
- data/lib/active_model/validations/confirmation.rb +2 -2
- data/lib/active_model/validations/exclusion.rb +1 -1
- data/lib/active_model/validations/format.rb +2 -2
- data/lib/active_model/validations/inclusion.rb +1 -1
- data/lib/active_model/validations/length.rb +2 -2
- data/lib/active_model/validations/numericality.rb +54 -41
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/validates.rb +6 -4
- data/lib/active_model/validations.rb +6 -6
- data/lib/active_model/validator.rb +7 -1
- data/lib/active_model.rb +2 -1
- metadata +9 -7
@@ -207,10 +207,12 @@ module ActiveModel
|
|
207
207
|
# person.nickname_short? # => true
|
208
208
|
def alias_attribute(new_name, old_name)
|
209
209
|
self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
210
|
+
CodeGenerator.batch(self, __FILE__, __LINE__) do |owner|
|
211
|
+
attribute_method_matchers.each do |matcher|
|
212
|
+
matcher_new = matcher.method_name(new_name).to_s
|
213
|
+
matcher_old = matcher.method_name(old_name).to_s
|
214
|
+
define_proxy_call false, owner, matcher_new, matcher_old
|
215
|
+
end
|
214
216
|
end
|
215
217
|
end
|
216
218
|
|
@@ -249,7 +251,9 @@ module ActiveModel
|
|
249
251
|
# end
|
250
252
|
# end
|
251
253
|
def define_attribute_methods(*attr_names)
|
252
|
-
|
254
|
+
CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
|
255
|
+
attr_names.flatten.each { |attr_name| define_attribute_method(attr_name, _owner: owner) }
|
256
|
+
end
|
253
257
|
end
|
254
258
|
|
255
259
|
# Declares an attribute that should be prefixed and suffixed by
|
@@ -281,21 +285,23 @@ module ActiveModel
|
|
281
285
|
# person.name = 'Bob'
|
282
286
|
# person.name # => "Bob"
|
283
287
|
# person.name_short? # => true
|
284
|
-
def define_attribute_method(attr_name)
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
288
|
+
def define_attribute_method(attr_name, _owner: generated_attribute_methods)
|
289
|
+
CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
|
290
|
+
attribute_method_matchers.each do |matcher|
|
291
|
+
method_name = matcher.method_name(attr_name)
|
292
|
+
|
293
|
+
unless instance_method_already_implemented?(method_name)
|
294
|
+
generate_method = "define_method_#{matcher.target}"
|
295
|
+
|
296
|
+
if respond_to?(generate_method, true)
|
297
|
+
send(generate_method, attr_name.to_s, owner: owner)
|
298
|
+
else
|
299
|
+
define_proxy_call true, owner, method_name, matcher.target, attr_name.to_s
|
300
|
+
end
|
295
301
|
end
|
296
302
|
end
|
303
|
+
attribute_method_matchers_cache.clear
|
297
304
|
end
|
298
|
-
attribute_method_matchers_cache.clear
|
299
305
|
end
|
300
306
|
|
301
307
|
# Removes all the previously dynamically defined methods from the class.
|
@@ -323,12 +329,52 @@ module ActiveModel
|
|
323
329
|
# person.name_short? # => NoMethodError
|
324
330
|
def undefine_attribute_methods
|
325
331
|
generated_attribute_methods.module_eval do
|
326
|
-
|
332
|
+
undef_method(*instance_methods)
|
327
333
|
end
|
328
334
|
attribute_method_matchers_cache.clear
|
329
335
|
end
|
330
336
|
|
331
337
|
private
|
338
|
+
class CodeGenerator
|
339
|
+
class << self
|
340
|
+
def batch(owner, path, line)
|
341
|
+
if owner.is_a?(CodeGenerator)
|
342
|
+
yield owner
|
343
|
+
else
|
344
|
+
instance = new(owner, path, line)
|
345
|
+
result = yield instance
|
346
|
+
instance.execute
|
347
|
+
result
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def initialize(owner, path, line)
|
353
|
+
@owner = owner
|
354
|
+
@path = path
|
355
|
+
@line = line
|
356
|
+
@sources = ["# frozen_string_literal: true\n"]
|
357
|
+
@renames = {}
|
358
|
+
end
|
359
|
+
|
360
|
+
def <<(source_line)
|
361
|
+
@sources << source_line
|
362
|
+
end
|
363
|
+
|
364
|
+
def rename_method(old_name, new_name)
|
365
|
+
@renames[old_name] = new_name
|
366
|
+
end
|
367
|
+
|
368
|
+
def execute
|
369
|
+
@owner.module_eval(@sources.join(";"), @path, @line - 1)
|
370
|
+
@renames.each do |old_name, new_name|
|
371
|
+
@owner.alias_method new_name, old_name
|
372
|
+
@owner.undef_method old_name
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
private_constant :CodeGenerator
|
377
|
+
|
332
378
|
def generated_attribute_methods
|
333
379
|
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
|
334
380
|
end
|
@@ -352,18 +398,14 @@ module ActiveModel
|
|
352
398
|
|
353
399
|
def attribute_method_matchers_matching(method_name)
|
354
400
|
attribute_method_matchers_cache.compute_if_absent(method_name) do
|
355
|
-
|
356
|
-
# match any other pattern match the actual attribute name.
|
357
|
-
# This is currently only needed to support legacy usage.
|
358
|
-
matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
|
359
|
-
matchers.map { |matcher| matcher.match(method_name) }.compact
|
401
|
+
attribute_method_matchers.map { |matcher| matcher.match(method_name) }.compact
|
360
402
|
end
|
361
403
|
end
|
362
404
|
|
363
405
|
# Define a method `name` in `mod` that dispatches to `send`
|
364
406
|
# using the given `extra` args. This falls back on `define_method`
|
365
407
|
# and `send` if the given names cannot be compiled.
|
366
|
-
def define_proxy_call(include_private,
|
408
|
+
def define_proxy_call(include_private, code_generator, name, target, *extra)
|
367
409
|
defn = if NAME_COMPILABLE_REGEXP.match?(name)
|
368
410
|
"def #{name}(*args)"
|
369
411
|
else
|
@@ -378,12 +420,11 @@ module ActiveModel
|
|
378
420
|
"send(:'#{target}', #{extra})"
|
379
421
|
end
|
380
422
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
end
|
385
|
-
ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)
|
386
|
-
RUBY
|
423
|
+
code_generator <<
|
424
|
+
defn <<
|
425
|
+
body <<
|
426
|
+
"end" <<
|
427
|
+
"ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)"
|
387
428
|
end
|
388
429
|
|
389
430
|
class AttributeMethodMatcher #:nodoc:
|
@@ -407,10 +448,6 @@ module ActiveModel
|
|
407
448
|
def method_name(attr_name)
|
408
449
|
@method_name % attr_name
|
409
450
|
end
|
410
|
-
|
411
|
-
def plain?
|
412
|
-
prefix.empty? && suffix.empty?
|
413
|
-
end
|
414
451
|
end
|
415
452
|
end
|
416
453
|
|
@@ -499,10 +536,10 @@ module ActiveModel
|
|
499
536
|
# to allocate an object on each call to the attribute method.
|
500
537
|
# Making it frozen means that it doesn't get duped when used to
|
501
538
|
# key the @attributes in read_attribute.
|
502
|
-
def self.define_attribute_accessor_method(
|
539
|
+
def self.define_attribute_accessor_method(owner, attr_name, writer: false)
|
503
540
|
method_name = "#{attr_name}#{'=' if writer}"
|
504
541
|
if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
|
505
|
-
yield method_name, "'#{attr_name}'
|
542
|
+
yield method_name, "'#{attr_name}'"
|
506
543
|
else
|
507
544
|
safe_name = attr_name.unpack1("h*")
|
508
545
|
const_name = "ATTR_#{safe_name}"
|
@@ -510,8 +547,7 @@ module ActiveModel
|
|
510
547
|
temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
|
511
548
|
attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
|
512
549
|
yield temp_method_name, attr_name_expr
|
513
|
-
|
514
|
-
mod.undef_method temp_method_name
|
550
|
+
owner.rename_method(temp_method_name, method_name)
|
515
551
|
end
|
516
552
|
end
|
517
553
|
end
|
@@ -7,9 +7,8 @@ module ActiveModel
|
|
7
7
|
class AttributeMutationTracker # :nodoc:
|
8
8
|
OPTION_NOT_GIVEN = Object.new
|
9
9
|
|
10
|
-
def initialize(attributes
|
10
|
+
def initialize(attributes)
|
11
11
|
@attributes = attributes
|
12
|
-
@forced_changes = forced_changes
|
13
12
|
end
|
14
13
|
|
15
14
|
def changed_attribute_names
|
@@ -62,11 +61,15 @@ module ActiveModel
|
|
62
61
|
end
|
63
62
|
|
64
63
|
def force_change(attr_name)
|
65
|
-
forced_changes
|
64
|
+
forced_changes[attr_name] = fetch_value(attr_name)
|
66
65
|
end
|
67
66
|
|
68
67
|
private
|
69
|
-
attr_reader :attributes
|
68
|
+
attr_reader :attributes
|
69
|
+
|
70
|
+
def forced_changes
|
71
|
+
@forced_changes ||= {}
|
72
|
+
end
|
70
73
|
|
71
74
|
def attr_names
|
72
75
|
attributes.keys
|
@@ -82,7 +85,7 @@ module ActiveModel
|
|
82
85
|
end
|
83
86
|
|
84
87
|
class ForcedMutationTracker < AttributeMutationTracker # :nodoc:
|
85
|
-
def initialize(attributes
|
88
|
+
def initialize(attributes)
|
86
89
|
super
|
87
90
|
@finalized_changes = nil
|
88
91
|
end
|
@@ -13,14 +13,86 @@ module ActiveModel
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def build_from_database(values = {}, additional_types = {})
|
16
|
-
|
17
|
-
AttributeSet.new(attributes)
|
16
|
+
LazyAttributeSet.new(values, types, additional_types, default_attributes)
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
21
|
+
class LazyAttributeSet < AttributeSet # :nodoc:
|
22
|
+
def initialize(values, types, additional_types, default_attributes, attributes = {})
|
23
|
+
super(attributes)
|
24
|
+
@values = values
|
25
|
+
@types = types
|
26
|
+
@additional_types = additional_types
|
27
|
+
@default_attributes = default_attributes
|
28
|
+
@casted_values = {}
|
29
|
+
@materialized = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def key?(name)
|
33
|
+
(values.key?(name) || types.key?(name) || @attributes.key?(name)) && self[name].initialized?
|
34
|
+
end
|
35
|
+
|
36
|
+
def keys
|
37
|
+
keys = values.keys | types.keys | @attributes.keys
|
38
|
+
keys.keep_if { |name| self[name].initialized? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch_value(name, &block)
|
42
|
+
if attr = @attributes[name]
|
43
|
+
return attr.value(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
@casted_values.fetch(name) do
|
47
|
+
value_present = true
|
48
|
+
value = values.fetch(name) { value_present = false }
|
49
|
+
|
50
|
+
if value_present
|
51
|
+
type = additional_types.fetch(name, types[name])
|
52
|
+
@casted_values[name] = type.deserialize(value)
|
53
|
+
else
|
54
|
+
attr = default_attribute(name, value_present, value)
|
55
|
+
attr.value(&block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
def attributes
|
62
|
+
unless @materialized
|
63
|
+
values.each_key { |key| self[key] }
|
64
|
+
types.each_key { |key| self[key] }
|
65
|
+
@materialized = true
|
66
|
+
end
|
67
|
+
@attributes
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
attr_reader :values, :types, :additional_types, :default_attributes
|
72
|
+
|
73
|
+
def default_attribute(
|
74
|
+
name,
|
75
|
+
value_present = true,
|
76
|
+
value = values.fetch(name) { value_present = false }
|
77
|
+
)
|
78
|
+
type = additional_types.fetch(name, types[name])
|
79
|
+
|
80
|
+
if value_present
|
81
|
+
@attributes[name] = Attribute.from_database(name, value, type, @casted_values[name])
|
82
|
+
elsif types.key?(name)
|
83
|
+
if attr = default_attributes[name]
|
84
|
+
@attributes[name] = attr.dup
|
85
|
+
else
|
86
|
+
@attributes[name] = Attribute.uninitialized(name, type)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
Attribute.null(name)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
22
94
|
class LazyAttributeHash # :nodoc:
|
23
|
-
delegate :transform_values, :
|
95
|
+
delegate :transform_values, :each_value, :fetch, :except, to: :materialize
|
24
96
|
|
25
97
|
def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
|
26
98
|
@types = types
|
@@ -40,9 +112,6 @@ module ActiveModel
|
|
40
112
|
end
|
41
113
|
|
42
114
|
def []=(key, value)
|
43
|
-
if frozen?
|
44
|
-
raise RuntimeError, "Can't modify frozen hash"
|
45
|
-
end
|
46
115
|
delegate_hash[key] = value
|
47
116
|
end
|
48
117
|
|
@@ -57,14 +126,9 @@ module ActiveModel
|
|
57
126
|
super
|
58
127
|
end
|
59
128
|
|
60
|
-
def
|
129
|
+
def each_key(&block)
|
61
130
|
keys = types.keys | values.keys | delegate_hash.keys
|
62
|
-
keys.
|
63
|
-
attribute = self[key]
|
64
|
-
if yield(key, attribute)
|
65
|
-
hash[key] = attribute
|
66
|
-
end
|
67
|
-
end
|
131
|
+
keys.each(&block)
|
68
132
|
end
|
69
133
|
|
70
134
|
def ==(other)
|
@@ -81,6 +145,9 @@ module ActiveModel
|
|
81
145
|
|
82
146
|
def marshal_load(values)
|
83
147
|
if values.is_a?(Hash)
|
148
|
+
ActiveSupport::Deprecation.warn(<<~MSG)
|
149
|
+
Marshalling load from legacy attributes format is deprecated and will be removed in Rails 7.0.
|
150
|
+
MSG
|
84
151
|
empty_hash = {}.freeze
|
85
152
|
initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
|
86
153
|
@materialized = true
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
3
4
|
require "active_support/core_ext/object/deep_dup"
|
4
5
|
require "active_model/attribute_set/builder"
|
5
6
|
require "active_model/attribute_set/yaml_encoder"
|
@@ -13,11 +14,11 @@ module ActiveModel
|
|
13
14
|
end
|
14
15
|
|
15
16
|
def [](name)
|
16
|
-
attributes[name] ||
|
17
|
+
@attributes[name] || default_attribute(name)
|
17
18
|
end
|
18
19
|
|
19
20
|
def []=(name, value)
|
20
|
-
attributes[name] = value
|
21
|
+
@attributes[name] = value
|
21
22
|
end
|
22
23
|
|
23
24
|
def values_before_type_cast
|
@@ -25,9 +26,9 @@ module ActiveModel
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def to_hash
|
28
|
-
|
29
|
+
keys.index_with { |name| self[name].value }
|
29
30
|
end
|
30
|
-
|
31
|
+
alias :to_h :to_hash
|
31
32
|
|
32
33
|
def key?(name)
|
33
34
|
attributes.key?(name) && self[name].initialized?
|
@@ -42,35 +43,36 @@ module ActiveModel
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def write_from_database(name, value)
|
45
|
-
attributes[name] = self[name].with_value_from_database(value)
|
46
|
+
@attributes[name] = self[name].with_value_from_database(value)
|
46
47
|
end
|
47
48
|
|
48
49
|
def write_from_user(name, value)
|
49
|
-
attributes
|
50
|
+
raise FrozenError, "can't modify frozen attributes" if frozen?
|
51
|
+
@attributes[name] = self[name].with_value_from_user(value)
|
52
|
+
value
|
50
53
|
end
|
51
54
|
|
52
55
|
def write_cast_value(name, value)
|
53
|
-
attributes[name] = self[name].with_cast_value(value)
|
56
|
+
@attributes[name] = self[name].with_cast_value(value)
|
57
|
+
value
|
54
58
|
end
|
55
59
|
|
56
60
|
def freeze
|
57
|
-
|
61
|
+
attributes.freeze
|
58
62
|
super
|
59
63
|
end
|
60
64
|
|
61
65
|
def deep_dup
|
62
|
-
|
63
|
-
copy.instance_variable_set(:@attributes, attributes.deep_dup)
|
64
|
-
end
|
66
|
+
AttributeSet.new(attributes.deep_dup)
|
65
67
|
end
|
66
68
|
|
67
69
|
def initialize_dup(_)
|
68
|
-
@attributes = attributes.dup
|
70
|
+
@attributes = @attributes.dup
|
69
71
|
super
|
70
72
|
end
|
71
73
|
|
72
74
|
def initialize_clone(_)
|
73
|
-
@attributes = attributes.clone
|
75
|
+
@attributes = @attributes.clone
|
74
76
|
super
|
75
77
|
end
|
76
78
|
|
@@ -81,7 +83,7 @@ module ActiveModel
|
|
81
83
|
end
|
82
84
|
|
83
85
|
def accessed
|
84
|
-
attributes.select { |
|
86
|
+
attributes.each_key.select { |name| self[name].has_been_read? }
|
85
87
|
end
|
86
88
|
|
87
89
|
def map(&block)
|
@@ -97,8 +99,8 @@ module ActiveModel
|
|
97
99
|
attr_reader :attributes
|
98
100
|
|
99
101
|
private
|
100
|
-
def
|
101
|
-
|
102
|
+
def default_attribute(name)
|
103
|
+
Attribute.null(name)
|
102
104
|
end
|
103
105
|
end
|
104
106
|
end
|
@@ -42,16 +42,14 @@ module ActiveModel
|
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
45
|
-
def define_method_attribute=(name)
|
45
|
+
def define_method_attribute=(name, owner:)
|
46
46
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
47
|
-
|
47
|
+
owner, name, writer: true,
|
48
48
|
) do |temp_method_name, attr_name_expr|
|
49
|
-
|
50
|
-
def #{temp_method_name}(value)
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
RUBY
|
49
|
+
owner <<
|
50
|
+
"def #{temp_method_name}(value)" <<
|
51
|
+
" _write_attribute(#{attr_name_expr}, value)" <<
|
52
|
+
"end"
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
@@ -79,10 +77,14 @@ module ActiveModel
|
|
79
77
|
super
|
80
78
|
end
|
81
79
|
|
80
|
+
def initialize_dup(other) # :nodoc:
|
81
|
+
@attributes = @attributes.deep_dup
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
82
85
|
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
83
86
|
#
|
84
87
|
# class Person
|
85
|
-
# include ActiveModel::Model
|
86
88
|
# include ActiveModel::Attributes
|
87
89
|
#
|
88
90
|
# attribute :name, :string
|
@@ -112,25 +114,19 @@ module ActiveModel
|
|
112
114
|
@attributes.keys
|
113
115
|
end
|
114
116
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
117
|
+
def freeze
|
118
|
+
@attributes = @attributes.clone.freeze unless frozen?
|
119
|
+
super
|
120
|
+
end
|
119
121
|
|
120
|
-
|
121
|
-
|
122
|
+
private
|
123
|
+
def _write_attribute(attr_name, value)
|
124
|
+
@attributes.write_from_user(attr_name, value)
|
122
125
|
end
|
126
|
+
alias :attribute= :_write_attribute
|
123
127
|
|
124
128
|
def attribute(attr_name)
|
125
|
-
|
126
|
-
name = self.class.attribute_aliases[name] || name
|
127
|
-
|
128
|
-
@attributes.fetch_value(name)
|
129
|
-
end
|
130
|
-
|
131
|
-
# Dispatch target for <tt>*=</tt> attribute methods.
|
132
|
-
def attribute=(attribute_name, value)
|
133
|
-
write_attribute(attribute_name, value)
|
129
|
+
@attributes.fetch_value(attr_name)
|
134
130
|
end
|
135
131
|
end
|
136
132
|
end
|
@@ -147,7 +147,7 @@ module ActiveModel
|
|
147
147
|
conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
|
148
148
|
v != false
|
149
149
|
}
|
150
|
-
options[:if] = Array(options[:if])
|
150
|
+
options[:if] = Array(options[:if]) + [conditional]
|
151
151
|
set_callback(:"#{callback}", :after, *args, options, &block)
|
152
152
|
end
|
153
153
|
end
|
data/lib/active_model/dirty.rb
CHANGED
@@ -83,7 +83,9 @@ module ActiveModel
|
|
83
83
|
#
|
84
84
|
# person.previous_changes # => {"name" => [nil, "Bill"]}
|
85
85
|
# person.name_previously_changed? # => true
|
86
|
+
# person.name_previously_changed?(from: nil, to: "Bill") # => true
|
86
87
|
# person.name_previous_change # => [nil, "Bill"]
|
88
|
+
# person.name_previously_was # => nil
|
87
89
|
# person.reload!
|
88
90
|
# person.previous_changes # => {}
|
89
91
|
#
|
@@ -122,8 +124,9 @@ module ActiveModel
|
|
122
124
|
|
123
125
|
included do
|
124
126
|
attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
|
125
|
-
attribute_method_suffix "_previously_changed?", "_previous_change"
|
127
|
+
attribute_method_suffix "_previously_changed?", "_previous_change", "_previously_was"
|
126
128
|
attribute_method_affix prefix: "restore_", suffix: "!"
|
129
|
+
attribute_method_affix prefix: "clear_", suffix: "_change"
|
127
130
|
end
|
128
131
|
|
129
132
|
def initialize_dup(other) # :nodoc:
|
@@ -136,7 +139,12 @@ module ActiveModel
|
|
136
139
|
@mutations_from_database = nil
|
137
140
|
end
|
138
141
|
|
139
|
-
|
142
|
+
def as_json(options = {}) # :nodoc:
|
143
|
+
options[:except] = [*options[:except], "mutations_from_database", "mutations_before_last_save"]
|
144
|
+
super(options)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Clears dirty data and moves +changes+ to +previous_changes+ and
|
140
148
|
# +mutations_from_database+ to +mutations_before_last_save+ respectively.
|
141
149
|
def changes_applied
|
142
150
|
unless defined?(@attributes)
|
@@ -176,8 +184,13 @@ module ActiveModel
|
|
176
184
|
end
|
177
185
|
|
178
186
|
# 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)
|
187
|
+
def attribute_previously_changed?(attr_name, **options) # :nodoc:
|
188
|
+
mutations_before_last_save.changed?(attr_name.to_s, **options)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Dispatch target for <tt>*_previously_was</tt> attribute methods.
|
192
|
+
def attribute_previously_was(attr_name) # :nodoc:
|
193
|
+
mutations_before_last_save.original_value(attr_name.to_s)
|
181
194
|
end
|
182
195
|
|
183
196
|
# Restore all previous data of the provided attributes.
|