activemodel 6.1.3.1 → 7.0.0.alpha1
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 +29 -72
- data/MIT-LICENSE +1 -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 +99 -52
- data/lib/active_model/attribute_set/builder.rb +1 -1
- 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 +10 -4
- data/lib/active_model/errors.rb +20 -6
- 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/serialization.rb +7 -2
- data/lib/active_model/translation.rb +1 -1
- data/lib/active_model/type/helpers/numeric.rb +9 -1
- data/lib/active_model/type/helpers/time_value.rb +2 -2
- data/lib/active_model/type/integer.rb +4 -1
- data/lib/active_model/type/registry.rb +9 -41
- data/lib/active_model/type/time.rb +1 -1
- data/lib/active_model/type.rb +6 -5
- 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 +27 -20
- data/lib/active_model/validations.rb +4 -4
- data/lib/active_model/validator.rb +2 -2
- data/lib/active_model.rb +2 -1
- metadata +14 -11
@@ -146,7 +146,7 @@ module ActiveModel
|
|
146
146
|
def marshal_load(values)
|
147
147
|
if values.is_a?(Hash)
|
148
148
|
ActiveSupport::Deprecation.warn(<<~MSG)
|
149
|
-
Marshalling load from legacy attributes format is deprecated and will be removed in Rails
|
149
|
+
Marshalling load from legacy attributes format is deprecated and will be removed in Rails 7.0.
|
150
150
|
MSG
|
151
151
|
empty_hash = {}.freeze
|
152
152
|
initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
|
@@ -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:
|
@@ -139,6 +140,11 @@ module ActiveModel
|
|
139
140
|
@mutations_from_database = nil
|
140
141
|
end
|
141
142
|
|
143
|
+
def as_json(options = {}) # :nodoc:
|
144
|
+
options[:except] = [*options[:except], "mutations_from_database", "mutations_before_last_save"]
|
145
|
+
super(options)
|
146
|
+
end
|
147
|
+
|
142
148
|
# Clears dirty data and moves +changes+ to +previous_changes+ and
|
143
149
|
# +mutations_from_database+ to +mutations_before_last_save+ respectively.
|
144
150
|
def changes_applied
|
data/lib/active_model/errors.rb
CHANGED
@@ -249,7 +249,7 @@ module ActiveModel
|
|
249
249
|
|
250
250
|
You are passing a block expecting two parameters,
|
251
251
|
so the old hash behavior is simulated. As this is deprecated,
|
252
|
-
this will result in an ArgumentError in Rails
|
252
|
+
this will result in an ArgumentError in Rails 7.0.
|
253
253
|
MSG
|
254
254
|
@errors.
|
255
255
|
sort { |a, b| a.attribute <=> b.attribute }.
|
@@ -325,7 +325,7 @@ module ActiveModel
|
|
325
325
|
|
326
326
|
def to_h
|
327
327
|
ActiveSupport::Deprecation.warn(<<~EOM)
|
328
|
-
ActiveModel::Errors#to_h is deprecated and will be removed in Rails
|
328
|
+
ActiveModel::Errors#to_h is deprecated and will be removed in Rails 7.0.
|
329
329
|
Please use `ActiveModel::Errors.to_hash` instead. The values in the hash
|
330
330
|
returned by `ActiveModel::Errors.to_hash` is an array of error messages.
|
331
331
|
EOM
|
@@ -378,6 +378,14 @@ module ActiveModel
|
|
378
378
|
# If +type+ is a symbol, it will be translated using the appropriate
|
379
379
|
# scope (see +generate_message+).
|
380
380
|
#
|
381
|
+
# person.errors.add(:name, :blank)
|
382
|
+
# person.errors.messages
|
383
|
+
# # => {:name=>["can't be blank"]}
|
384
|
+
#
|
385
|
+
# person.errors.add(:name, :too_long, { count: 25 })
|
386
|
+
# person.errors.messages
|
387
|
+
# # => ["is too long (maximum is 25 characters)"]
|
388
|
+
#
|
381
389
|
# If +type+ is a proc, it will be called, allowing for things like
|
382
390
|
# <tt>Time.now</tt> to be used within an error.
|
383
391
|
#
|
@@ -563,6 +571,12 @@ module ActiveModel
|
|
563
571
|
add_from_legacy_details_hash(data["details"]) if data.key?("details")
|
564
572
|
end
|
565
573
|
|
574
|
+
def inspect # :nodoc:
|
575
|
+
inspection = @errors.inspect
|
576
|
+
|
577
|
+
"#<#{self.class.name} #{inspection}>"
|
578
|
+
end
|
579
|
+
|
566
580
|
private
|
567
581
|
def normalize_arguments(attribute, type, **options)
|
568
582
|
# Evaluate proc first
|
@@ -583,7 +597,7 @@ module ActiveModel
|
|
583
597
|
end
|
584
598
|
|
585
599
|
def deprecation_removal_warning(method_name, alternative_message = nil)
|
586
|
-
message = +"ActiveModel::Errors##{method_name} is deprecated and will be removed in Rails
|
600
|
+
message = +"ActiveModel::Errors##{method_name} is deprecated and will be removed in Rails 7.0."
|
587
601
|
if alternative_message
|
588
602
|
message << "\n\nTo achieve the same use:\n\n "
|
589
603
|
message << alternative_message
|
@@ -596,7 +610,7 @@ module ActiveModel
|
|
596
610
|
end
|
597
611
|
end
|
598
612
|
|
599
|
-
class DeprecationHandlingMessageHash < SimpleDelegator
|
613
|
+
class DeprecationHandlingMessageHash < SimpleDelegator # :nodoc:
|
600
614
|
def initialize(errors)
|
601
615
|
@errors = errors
|
602
616
|
super(prepare_content)
|
@@ -635,7 +649,7 @@ module ActiveModel
|
|
635
649
|
end
|
636
650
|
end
|
637
651
|
|
638
|
-
class DeprecationHandlingMessageArray < SimpleDelegator
|
652
|
+
class DeprecationHandlingMessageArray < SimpleDelegator # :nodoc:
|
639
653
|
def initialize(content, errors, attribute)
|
640
654
|
@errors = errors
|
641
655
|
@attribute = attribute
|
@@ -657,7 +671,7 @@ module ActiveModel
|
|
657
671
|
end
|
658
672
|
end
|
659
673
|
|
660
|
-
class DeprecationHandlingDetailsHash < SimpleDelegator
|
674
|
+
class DeprecationHandlingDetailsHash < SimpleDelegator # :nodoc:
|
661
675
|
def initialize(details)
|
662
676
|
details.default = []
|
663
677
|
details.freeze
|
data/lib/active_model/model.rb
CHANGED
@@ -3,11 +3,10 @@
|
|
3
3
|
module ActiveModel
|
4
4
|
# == Active \Model \Basic \Model
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# hash of attributes, pretty much like Active Record does.
|
6
|
+
# Allows implementing models similar to <tt>ActiveRecord::Base</tt>.
|
7
|
+
# Includes <tt>ActiveModel::API</tt> for the required interface for an
|
8
|
+
# object to interact with Action Pack and Action View, but can be
|
9
|
+
# extended with other functionalities.
|
11
10
|
#
|
12
11
|
# A minimal implementation could be:
|
13
12
|
#
|
@@ -20,23 +19,7 @@ module ActiveModel
|
|
20
19
|
# person.name # => "bob"
|
21
20
|
# person.age # => "18"
|
22
21
|
#
|
23
|
-
#
|
24
|
-
# to return +false+, which is the most common case. You may want to override
|
25
|
-
# it in your class to simulate a different scenario:
|
26
|
-
#
|
27
|
-
# class Person
|
28
|
-
# include ActiveModel::Model
|
29
|
-
# attr_accessor :id, :name
|
30
|
-
#
|
31
|
-
# def persisted?
|
32
|
-
# self.id == 1
|
33
|
-
# end
|
34
|
-
# end
|
35
|
-
#
|
36
|
-
# person = Person.new(id: 1, name: 'bob')
|
37
|
-
# person.persisted? # => true
|
38
|
-
#
|
39
|
-
# Also, if for some reason you need to run code on <tt>initialize</tt>, make
|
22
|
+
# If for some reason you need to run code on <tt>initialize</tt>, make
|
40
23
|
# sure you call +super+ if you want the attributes hash initialization to
|
41
24
|
# happen.
|
42
25
|
#
|
@@ -58,42 +41,6 @@ module ActiveModel
|
|
58
41
|
# (see below).
|
59
42
|
module Model
|
60
43
|
extend ActiveSupport::Concern
|
61
|
-
include ActiveModel::
|
62
|
-
include ActiveModel::Validations
|
63
|
-
include ActiveModel::Conversion
|
64
|
-
|
65
|
-
included do
|
66
|
-
extend ActiveModel::Naming
|
67
|
-
extend ActiveModel::Translation
|
68
|
-
end
|
69
|
-
|
70
|
-
# Initializes a new model with the given +params+.
|
71
|
-
#
|
72
|
-
# class Person
|
73
|
-
# include ActiveModel::Model
|
74
|
-
# attr_accessor :name, :age
|
75
|
-
# end
|
76
|
-
#
|
77
|
-
# person = Person.new(name: 'bob', age: '18')
|
78
|
-
# person.name # => "bob"
|
79
|
-
# person.age # => "18"
|
80
|
-
def initialize(attributes = {})
|
81
|
-
assign_attributes(attributes) if attributes
|
82
|
-
|
83
|
-
super()
|
84
|
-
end
|
85
|
-
|
86
|
-
# Indicates if the model is persisted. Default is +false+.
|
87
|
-
#
|
88
|
-
# class Person
|
89
|
-
# include ActiveModel::Model
|
90
|
-
# attr_accessor :id, :name
|
91
|
-
# end
|
92
|
-
#
|
93
|
-
# person = Person.new(id: 1, name: 'bob')
|
94
|
-
# person.persisted? # => false
|
95
|
-
def persisted?
|
96
|
-
false
|
97
|
-
end
|
44
|
+
include ActiveModel::API
|
98
45
|
end
|
99
46
|
end
|
data/lib/active_model/naming.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "active_support/core_ext/hash/except"
|
4
4
|
require "active_support/core_ext/module/introspection"
|
5
5
|
require "active_support/core_ext/module/redefine_method"
|
6
|
+
require "active_support/core_ext/module/delegation"
|
6
7
|
|
7
8
|
module ActiveModel
|
8
9
|
class Name
|
@@ -153,6 +154,7 @@ module ActiveModel
|
|
153
154
|
# Returns a new ActiveModel::Name instance. By default, the +namespace+
|
154
155
|
# and +name+ option will take the namespace and name of the given class
|
155
156
|
# respectively.
|
157
|
+
# Use +locale+ argument for singularize and pluralize model name.
|
156
158
|
#
|
157
159
|
# module Foo
|
158
160
|
# class Bar
|
@@ -161,7 +163,7 @@ module ActiveModel
|
|
161
163
|
#
|
162
164
|
# ActiveModel::Name.new(Foo::Bar).to_s
|
163
165
|
# # => "Foo::Bar"
|
164
|
-
def initialize(klass, namespace = nil, name = nil)
|
166
|
+
def initialize(klass, namespace = nil, name = nil, locale = :en)
|
165
167
|
@name = name || klass.name
|
166
168
|
|
167
169
|
raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
|
@@ -169,16 +171,17 @@ module ActiveModel
|
|
169
171
|
@unnamespaced = @name.delete_prefix("#{namespace.name}::") if namespace
|
170
172
|
@klass = klass
|
171
173
|
@singular = _singularize(@name)
|
172
|
-
@plural = ActiveSupport::Inflector.pluralize(@singular)
|
174
|
+
@plural = ActiveSupport::Inflector.pluralize(@singular, locale)
|
175
|
+
@uncountable = @plural == @singular
|
173
176
|
@element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
|
174
177
|
@human = ActiveSupport::Inflector.humanize(@element)
|
175
178
|
@collection = ActiveSupport::Inflector.tableize(@name)
|
176
179
|
@param_key = (namespace ? _singularize(@unnamespaced) : @singular)
|
177
180
|
@i18n_key = @name.underscore.to_sym
|
178
181
|
|
179
|
-
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
|
180
|
-
@singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
|
181
|
-
@route_key << "_index" if @
|
182
|
+
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key, locale) : @plural.dup)
|
183
|
+
@singular_route_key = ActiveSupport::Inflector.singularize(@route_key, locale)
|
184
|
+
@route_key << "_index" if @uncountable
|
182
185
|
end
|
183
186
|
|
184
187
|
# Transform the model name into a more human format, using I18n. By default,
|
@@ -206,6 +209,10 @@ module ActiveModel
|
|
206
209
|
I18n.translate(defaults.shift, **options)
|
207
210
|
end
|
208
211
|
|
212
|
+
def uncountable?
|
213
|
+
@uncountable
|
214
|
+
end
|
215
|
+
|
209
216
|
private
|
210
217
|
def _singularize(string)
|
211
218
|
ActiveSupport::Inflector.underscore(string).tr("/", "_")
|
@@ -232,7 +239,7 @@ module ActiveModel
|
|
232
239
|
# is required to pass the \Active \Model Lint test. So either extending the
|
233
240
|
# provided method below, or rolling your own is required.
|
234
241
|
module Naming
|
235
|
-
def self.extended(base)
|
242
|
+
def self.extended(base) # :nodoc:
|
236
243
|
base.silence_redefinition_of_method :model_name
|
237
244
|
base.delegate :model_name, to: :class
|
238
245
|
end
|
@@ -279,7 +286,7 @@ module ActiveModel
|
|
279
286
|
# ActiveModel::Naming.uncountable?(Sheep) # => true
|
280
287
|
# ActiveModel::Naming.uncountable?(Post) # => false
|
281
288
|
def self.uncountable?(record_or_class)
|
282
|
-
|
289
|
+
model_name_from_record_or_class(record_or_class).uncountable?
|
283
290
|
end
|
284
291
|
|
285
292
|
# Returns string to use while generating route names. It differs for
|
@@ -321,7 +328,7 @@ module ActiveModel
|
|
321
328
|
model_name_from_record_or_class(record_or_class).param_key
|
322
329
|
end
|
323
330
|
|
324
|
-
def self.model_name_from_record_or_class(record_or_class)
|
331
|
+
def self.model_name_from_record_or_class(record_or_class) # :nodoc:
|
325
332
|
if record_or_class.respond_to?(:to_model)
|
326
333
|
record_or_class.to_model.model_name
|
327
334
|
else
|
@@ -123,7 +123,7 @@ module ActiveModel
|
|
123
123
|
# user.serializable_hash(include: { notes: { only: 'title' }})
|
124
124
|
# # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
|
125
125
|
def serializable_hash(options = nil)
|
126
|
-
attribute_names =
|
126
|
+
attribute_names = self.attribute_names
|
127
127
|
|
128
128
|
return serializable_attributes(attribute_names) if options.blank?
|
129
129
|
|
@@ -148,6 +148,11 @@ module ActiveModel
|
|
148
148
|
hash
|
149
149
|
end
|
150
150
|
|
151
|
+
# Returns an array of attribute names as strings
|
152
|
+
def attribute_names # :nodoc:
|
153
|
+
attributes.keys
|
154
|
+
end
|
155
|
+
|
151
156
|
private
|
152
157
|
# Hook method defining how an attribute value should be retrieved for
|
153
158
|
# serialization. By default this is assumed to be an instance named after
|
@@ -177,7 +182,7 @@ module ActiveModel
|
|
177
182
|
# +association+ - name of the association
|
178
183
|
# +records+ - the association record(s) to be serialized
|
179
184
|
# +opts+ - options for the association records
|
180
|
-
def serializable_add_includes(options = {})
|
185
|
+
def serializable_add_includes(options = {}) # :nodoc:
|
181
186
|
return unless includes = options[:include]
|
182
187
|
|
183
188
|
unless includes.is_a?(Hash)
|
@@ -17,7 +17,7 @@ module ActiveModel
|
|
17
17
|
#
|
18
18
|
# This also provides the required class methods for hooking into the
|
19
19
|
# Rails internationalization API, including being able to define a
|
20
|
-
# class
|
20
|
+
# class-based +i18n_scope+ and +lookup_ancestors+ to find translations in
|
21
21
|
# parent classes.
|
22
22
|
module Translation
|
23
23
|
include ActiveModel::Naming
|
@@ -25,10 +25,18 @@ module ActiveModel
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
|
28
|
-
super || number_to_non_number?(old_value, new_value_before_type_cast)
|
28
|
+
(super || number_to_non_number?(old_value, new_value_before_type_cast)) &&
|
29
|
+
!equal_nan?(old_value, new_value_before_type_cast)
|
29
30
|
end
|
30
31
|
|
31
32
|
private
|
33
|
+
def equal_nan?(old_value, new_value)
|
34
|
+
(old_value.is_a?(::Float) || old_value.is_a?(BigDecimal)) &&
|
35
|
+
old_value.nan? &&
|
36
|
+
old_value.instance_of?(new_value.class) &&
|
37
|
+
new_value.nan?
|
38
|
+
end
|
39
|
+
|
32
40
|
def number_to_non_number?(old_value, new_value_before_type_cast)
|
33
41
|
old_value != nil && non_numeric_string?(new_value_before_type_cast.to_s)
|
34
42
|
end
|