activemodel 6.1.7.6 → 7.0.0
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 +58 -148
- data/MIT-LICENSE +2 -1
- data/README.rdoc +3 -3
- data/lib/active_model/api.rb +99 -0
- data/lib/active_model/attribute.rb +4 -0
- data/lib/active_model/attribute_methods.rb +65 -81
- data/lib/active_model/attribute_set/builder.rb +1 -10
- data/lib/active_model/attribute_set.rb +4 -1
- data/lib/active_model/attributes.rb +15 -12
- data/lib/active_model/callbacks.rb +1 -1
- data/lib/active_model/conversion.rb +2 -2
- data/lib/active_model/dirty.rb +5 -4
- data/lib/active_model/errors.rb +35 -235
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/locale/en.yml +1 -0
- data/lib/active_model/model.rb +6 -59
- data/lib/active_model/naming.rb +15 -8
- data/lib/active_model/secure_password.rb +1 -1
- data/lib/active_model/serialization.rb +7 -2
- data/lib/active_model/translation.rb +1 -1
- data/lib/active_model/type/date.rb +1 -1
- data/lib/active_model/type/helpers/numeric.rb +9 -1
- data/lib/active_model/type/helpers/time_value.rb +3 -3
- data/lib/active_model/type/integer.rb +4 -1
- data/lib/active_model/type/registry.rb +8 -38
- data/lib/active_model/type/time.rb +1 -1
- data/lib/active_model/type.rb +6 -6
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/clusivity.rb +1 -1
- data/lib/active_model/validations/comparability.rb +29 -0
- data/lib/active_model/validations/comparison.rb +82 -0
- data/lib/active_model/validations/confirmation.rb +4 -4
- data/lib/active_model/validations/numericality.rb +28 -21
- data/lib/active_model/validations.rb +4 -4
- data/lib/active_model/validator.rb +2 -2
- data/lib/active_model.rb +2 -1
- metadata +15 -12
|
@@ -67,6 +67,7 @@ module ActiveModel
|
|
|
67
67
|
|
|
68
68
|
NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
|
|
69
69
|
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
|
|
70
|
+
FORWARD_PARAMETERS = "*args"
|
|
70
71
|
|
|
71
72
|
included do
|
|
72
73
|
class_attribute :attribute_aliases, instance_writer: false, default: {}
|
|
@@ -105,8 +106,8 @@ module ActiveModel
|
|
|
105
106
|
# person.name # => "Bob"
|
|
106
107
|
# person.clear_name
|
|
107
108
|
# person.name # => nil
|
|
108
|
-
def attribute_method_prefix(*prefixes)
|
|
109
|
-
self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new
|
|
109
|
+
def attribute_method_prefix(*prefixes, parameters: nil)
|
|
110
|
+
self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new(prefix: prefix, parameters: parameters) }
|
|
110
111
|
undefine_attribute_methods
|
|
111
112
|
end
|
|
112
113
|
|
|
@@ -140,8 +141,8 @@ module ActiveModel
|
|
|
140
141
|
# person.name = 'Bob'
|
|
141
142
|
# person.name # => "Bob"
|
|
142
143
|
# person.name_short? # => true
|
|
143
|
-
def attribute_method_suffix(*suffixes)
|
|
144
|
-
self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new
|
|
144
|
+
def attribute_method_suffix(*suffixes, parameters: nil)
|
|
145
|
+
self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new(suffix: suffix, parameters: parameters) }
|
|
145
146
|
undefine_attribute_methods
|
|
146
147
|
end
|
|
147
148
|
|
|
@@ -177,7 +178,7 @@ module ActiveModel
|
|
|
177
178
|
# person.reset_name_to_default!
|
|
178
179
|
# person.name # => 'Default Name'
|
|
179
180
|
def attribute_method_affix(*affixes)
|
|
180
|
-
self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new
|
|
181
|
+
self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new(**affix) }
|
|
181
182
|
undefine_attribute_methods
|
|
182
183
|
end
|
|
183
184
|
|
|
@@ -207,11 +208,33 @@ module ActiveModel
|
|
|
207
208
|
# person.nickname_short? # => true
|
|
208
209
|
def alias_attribute(new_name, old_name)
|
|
209
210
|
self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
|
|
210
|
-
CodeGenerator.batch(self, __FILE__, __LINE__) do |
|
|
211
|
+
ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator|
|
|
211
212
|
attribute_method_matchers.each do |matcher|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
method_name = matcher.method_name(new_name).to_s
|
|
214
|
+
target_name = matcher.method_name(old_name).to_s
|
|
215
|
+
parameters = matcher.parameters
|
|
216
|
+
|
|
217
|
+
mangled_name = target_name
|
|
218
|
+
unless NAME_COMPILABLE_REGEXP.match?(target_name)
|
|
219
|
+
mangled_name = "__temp__#{target_name.unpack1("h*")}"
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
code_generator.define_cached_method(method_name, as: mangled_name, namespace: :alias_attribute) do |batch|
|
|
223
|
+
body = if CALL_COMPILABLE_REGEXP.match?(target_name)
|
|
224
|
+
"self.#{target_name}(#{parameters || ''})"
|
|
225
|
+
else
|
|
226
|
+
call_args = [":'#{target_name}'"]
|
|
227
|
+
call_args << parameters if parameters
|
|
228
|
+
"send(#{call_args.join(", ")})"
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
modifier = matcher.parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
|
|
232
|
+
|
|
233
|
+
batch <<
|
|
234
|
+
"#{modifier}def #{mangled_name}(#{parameters || ''})" <<
|
|
235
|
+
body <<
|
|
236
|
+
"end"
|
|
237
|
+
end
|
|
215
238
|
end
|
|
216
239
|
end
|
|
217
240
|
end
|
|
@@ -251,7 +274,7 @@ module ActiveModel
|
|
|
251
274
|
# end
|
|
252
275
|
# end
|
|
253
276
|
def define_attribute_methods(*attr_names)
|
|
254
|
-
CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
|
|
277
|
+
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
|
|
255
278
|
attr_names.flatten.each { |attr_name| define_attribute_method(attr_name, _owner: owner) }
|
|
256
279
|
end
|
|
257
280
|
end
|
|
@@ -286,7 +309,7 @@ module ActiveModel
|
|
|
286
309
|
# person.name # => "Bob"
|
|
287
310
|
# person.name_short? # => true
|
|
288
311
|
def define_attribute_method(attr_name, _owner: generated_attribute_methods)
|
|
289
|
-
CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
|
|
312
|
+
ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
|
|
290
313
|
attribute_method_matchers.each do |matcher|
|
|
291
314
|
method_name = matcher.method_name(attr_name)
|
|
292
315
|
|
|
@@ -296,7 +319,7 @@ module ActiveModel
|
|
|
296
319
|
if respond_to?(generate_method, true)
|
|
297
320
|
send(generate_method, attr_name.to_s, owner: owner)
|
|
298
321
|
else
|
|
299
|
-
define_proxy_call
|
|
322
|
+
define_proxy_call(owner, method_name, matcher.target, matcher.parameters, attr_name.to_s, namespace: :active_model)
|
|
300
323
|
end
|
|
301
324
|
end
|
|
302
325
|
end
|
|
@@ -335,46 +358,6 @@ module ActiveModel
|
|
|
335
358
|
end
|
|
336
359
|
|
|
337
360
|
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
|
-
|
|
378
361
|
def generated_attribute_methods
|
|
379
362
|
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
|
|
380
363
|
end
|
|
@@ -398,42 +381,48 @@ module ActiveModel
|
|
|
398
381
|
|
|
399
382
|
def attribute_method_matchers_matching(method_name)
|
|
400
383
|
attribute_method_matchers_cache.compute_if_absent(method_name) do
|
|
401
|
-
attribute_method_matchers.
|
|
384
|
+
attribute_method_matchers.filter_map { |matcher| matcher.match(method_name) }
|
|
402
385
|
end
|
|
403
386
|
end
|
|
404
387
|
|
|
405
388
|
# Define a method `name` in `mod` that dispatches to `send`
|
|
406
|
-
# using the given `extra` args. This falls back on `
|
|
407
|
-
#
|
|
408
|
-
def define_proxy_call(
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
"define_method(:'#{name}') do |*args|"
|
|
389
|
+
# using the given `extra` args. This falls back on `send`
|
|
390
|
+
# if the called name cannot be compiled.
|
|
391
|
+
def define_proxy_call(code_generator, name, target, parameters, *call_args, namespace:)
|
|
392
|
+
mangled_name = name
|
|
393
|
+
unless NAME_COMPILABLE_REGEXP.match?(name)
|
|
394
|
+
mangled_name = "__temp__#{name.unpack1("h*")}"
|
|
413
395
|
end
|
|
414
396
|
|
|
415
|
-
|
|
397
|
+
code_generator.define_cached_method(name, as: mangled_name, namespace: namespace) do |batch|
|
|
398
|
+
call_args.map!(&:inspect)
|
|
399
|
+
call_args << parameters if parameters
|
|
416
400
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
401
|
+
body = if CALL_COMPILABLE_REGEXP.match?(target)
|
|
402
|
+
"self.#{target}(#{call_args.join(", ")})"
|
|
403
|
+
else
|
|
404
|
+
call_args.unshift(":'#{target}'")
|
|
405
|
+
"send(#{call_args.join(", ")})"
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
|
|
422
409
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
410
|
+
batch <<
|
|
411
|
+
"#{modifier}def #{mangled_name}(#{parameters || ''})" <<
|
|
412
|
+
body <<
|
|
413
|
+
"end"
|
|
414
|
+
end
|
|
428
415
|
end
|
|
429
416
|
|
|
430
|
-
class AttributeMethodMatcher
|
|
431
|
-
attr_reader :prefix, :suffix, :target
|
|
417
|
+
class AttributeMethodMatcher # :nodoc:
|
|
418
|
+
attr_reader :prefix, :suffix, :target, :parameters
|
|
432
419
|
|
|
433
420
|
AttributeMethodMatch = Struct.new(:target, :attr_name)
|
|
434
421
|
|
|
435
|
-
def initialize(
|
|
436
|
-
@prefix
|
|
422
|
+
def initialize(prefix: "", suffix: "", parameters: nil)
|
|
423
|
+
@prefix = prefix
|
|
424
|
+
@suffix = suffix
|
|
425
|
+
@parameters = parameters.nil? ? FORWARD_PARAMETERS : parameters
|
|
437
426
|
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
|
|
438
427
|
@target = "#{@prefix}attribute#{@suffix}"
|
|
439
428
|
@method_name = "#{prefix}%s#{suffix}"
|
|
@@ -469,7 +458,7 @@ module ActiveModel
|
|
|
469
458
|
match ? attribute_missing(match, *args, &block) : super
|
|
470
459
|
end
|
|
471
460
|
end
|
|
472
|
-
ruby2_keywords(:method_missing)
|
|
461
|
+
ruby2_keywords(:method_missing)
|
|
473
462
|
|
|
474
463
|
# +attribute_missing+ is like +method_missing+, but for attributes. When
|
|
475
464
|
# +method_missing+ is called we check to see if there is a matching
|
|
@@ -520,10 +509,6 @@ module ActiveModel
|
|
|
520
509
|
|
|
521
510
|
# We want to generate the methods via module_eval rather than
|
|
522
511
|
# define_method, because define_method is slower on dispatch.
|
|
523
|
-
# Evaluating many similar methods may use more memory as the instruction
|
|
524
|
-
# sequences are duplicated and cached (in MRI). define_method may
|
|
525
|
-
# be slower on dispatch, but if you're careful about the closure
|
|
526
|
-
# created, then define_method will consume much less memory.
|
|
527
512
|
#
|
|
528
513
|
# But sometimes the database might return columns with
|
|
529
514
|
# characters that are not allowed in normal method names (like
|
|
@@ -547,7 +532,6 @@ module ActiveModel
|
|
|
547
532
|
temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
|
|
548
533
|
attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
|
|
549
534
|
yield temp_method_name, attr_name_expr
|
|
550
|
-
owner.rename_method(temp_method_name, method_name)
|
|
551
535
|
end
|
|
552
536
|
end
|
|
553
537
|
end
|
|
@@ -144,16 +144,7 @@ module ActiveModel
|
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
def marshal_load(values)
|
|
147
|
-
|
|
148
|
-
ActiveSupport::Deprecation.warn(<<~MSG)
|
|
149
|
-
Marshalling load from legacy attributes format is deprecated and will be removed in Rails 7.0.
|
|
150
|
-
MSG
|
|
151
|
-
empty_hash = {}.freeze
|
|
152
|
-
initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
|
|
153
|
-
@materialized = true
|
|
154
|
-
else
|
|
155
|
-
initialize(*values)
|
|
156
|
-
end
|
|
147
|
+
initialize(*values)
|
|
157
148
|
end
|
|
158
149
|
|
|
159
150
|
protected
|
|
@@ -25,6 +25,10 @@ module ActiveModel
|
|
|
25
25
|
attributes.transform_values(&:value_before_type_cast)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def values_for_database
|
|
29
|
+
attributes.transform_values(&:value_for_database)
|
|
30
|
+
end
|
|
31
|
+
|
|
28
32
|
def to_hash
|
|
29
33
|
keys.index_with { |name| self[name].value }
|
|
30
34
|
end
|
|
@@ -54,7 +58,6 @@ module ActiveModel
|
|
|
54
58
|
|
|
55
59
|
def write_cast_value(name, value)
|
|
56
60
|
@attributes[name] = self[name].with_cast_value(value)
|
|
57
|
-
value
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
def freeze
|
|
@@ -4,25 +4,26 @@ require "active_model/attribute_set"
|
|
|
4
4
|
require "active_model/attribute/user_provided_default"
|
|
5
5
|
|
|
6
6
|
module ActiveModel
|
|
7
|
-
module Attributes
|
|
7
|
+
module Attributes # :nodoc:
|
|
8
8
|
extend ActiveSupport::Concern
|
|
9
9
|
include ActiveModel::AttributeMethods
|
|
10
10
|
|
|
11
11
|
included do
|
|
12
|
-
attribute_method_suffix "="
|
|
12
|
+
attribute_method_suffix "=", parameters: "value"
|
|
13
13
|
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
|
|
14
14
|
self.attribute_types = Hash.new(Type.default_value)
|
|
15
15
|
self._default_attributes = AttributeSet.new({})
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
module ClassMethods
|
|
19
|
-
def attribute(name,
|
|
19
|
+
def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options)
|
|
20
20
|
name = name.to_s
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
|
|
22
|
+
cast_type = Type.lookup(cast_type, **options) if Symbol === cast_type
|
|
23
|
+
cast_type ||= attribute_types[name]
|
|
24
|
+
|
|
25
|
+
self.attribute_types = attribute_types.merge(name => cast_type)
|
|
26
|
+
define_default_attribute(name, default, cast_type)
|
|
26
27
|
define_attribute_method(name)
|
|
27
28
|
end
|
|
28
29
|
|
|
@@ -46,10 +47,12 @@ module ActiveModel
|
|
|
46
47
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
|
47
48
|
owner, name, writer: true,
|
|
48
49
|
) do |temp_method_name, attr_name_expr|
|
|
49
|
-
owner
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
owner.define_cached_method("#{name}=", as: temp_method_name, namespace: :active_model) do |batch|
|
|
51
|
+
batch <<
|
|
52
|
+
"def #{temp_method_name}(value)" <<
|
|
53
|
+
" _write_attribute(#{attr_name_expr}, value)" <<
|
|
54
|
+
"end"
|
|
55
|
+
end
|
|
53
56
|
end
|
|
54
57
|
end
|
|
55
58
|
|
|
@@ -63,7 +63,7 @@ module ActiveModel
|
|
|
63
63
|
# NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
|
|
64
64
|
#
|
|
65
65
|
module Callbacks
|
|
66
|
-
def self.extended(base)
|
|
66
|
+
def self.extended(base) # :nodoc:
|
|
67
67
|
base.class_eval do
|
|
68
68
|
include ActiveSupport::Callbacks
|
|
69
69
|
end
|
|
@@ -96,10 +96,10 @@ module ActiveModel
|
|
|
96
96
|
self.class._to_partial_path
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
module ClassMethods
|
|
99
|
+
module ClassMethods # :nodoc:
|
|
100
100
|
# Provide a class level cache for #to_partial_path. This is an
|
|
101
101
|
# internal method and should not be accessed directly.
|
|
102
|
-
def _to_partial_path
|
|
102
|
+
def _to_partial_path # :nodoc:
|
|
103
103
|
@_to_partial_path ||= begin
|
|
104
104
|
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
|
|
105
105
|
collection = ActiveSupport::Inflector.tableize(name)
|
data/lib/active_model/dirty.rb
CHANGED
|
@@ -123,10 +123,11 @@ module ActiveModel
|
|
|
123
123
|
include ActiveModel::AttributeMethods
|
|
124
124
|
|
|
125
125
|
included do
|
|
126
|
-
attribute_method_suffix "
|
|
127
|
-
attribute_method_suffix "
|
|
128
|
-
|
|
129
|
-
attribute_method_affix prefix: "
|
|
126
|
+
attribute_method_suffix "_previously_changed?", "_changed?", parameters: "**options"
|
|
127
|
+
attribute_method_suffix "_change", "_will_change!", "_was", parameters: false
|
|
128
|
+
attribute_method_suffix "_previous_change", "_previously_was", parameters: false
|
|
129
|
+
attribute_method_affix prefix: "restore_", suffix: "!", parameters: false
|
|
130
|
+
attribute_method_affix prefix: "clear_", suffix: "_change", parameters: false
|
|
130
131
|
end
|
|
131
132
|
|
|
132
133
|
def initialize_dup(other) # :nodoc:
|