activemodel 6.1.7.10 → 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 +27 -224
- 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 +99 -52
- 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 +17 -3
- 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 +0 -1
- 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 +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 +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 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03bc5c9c2ea021d9c3e3a3a47bf160adc73738341c6e488e85de66a38de111ac
|
4
|
+
data.tar.gz: a6c81f5be5dbc43a7086563d786221aadfe1d9392c2ff1fc483ee433ffab4230
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c7f97c5745ee6074629c8e546a738fbb8616581db954e78630df9a361bd3ae098bca8bec4afcc3891db83addc77e61349a6dcca2040844b715bed7b4a27c803
|
7
|
+
data.tar.gz: 143fbb8f40593fe72a1d3465d18affdf9ca3d30e11189d7a35f899af0b5f090b6c1209cf7294361a7dd3c1c5376b92555ee00e3afc92fc2715b26b63c51c7529
|
data/CHANGELOG.md
CHANGED
@@ -1,252 +1,55 @@
|
|
1
|
-
## Rails
|
1
|
+
## Rails 7.0.0.alpha1 (September 15, 2021) ##
|
2
2
|
|
3
|
-
*
|
3
|
+
* Introduce `ActiveModel::API`.
|
4
4
|
|
5
|
+
Make `ActiveModel::API` the minimum API to talk with Action Pack and Action View.
|
6
|
+
This will allow adding more functionality to `ActiveModel::Model`.
|
5
7
|
|
6
|
-
|
8
|
+
*Petrik de Heus*, *Nathaniel Watts*
|
7
9
|
|
8
|
-
*
|
10
|
+
* Fix dirty check for Float::NaN and BigDecimal::NaN.
|
9
11
|
|
12
|
+
Float::NaN and BigDecimal::NaN in Ruby are [special values](https://bugs.ruby-lang.org/issues/1720)
|
13
|
+
and can't be compared with `==`.
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
* No changes.
|
14
|
-
|
15
|
-
|
16
|
-
## Rails 6.1.7.7 (February 21, 2024) ##
|
17
|
-
|
18
|
-
* No changes.
|
19
|
-
|
20
|
-
|
21
|
-
## Rails 6.1.7.6 (August 22, 2023) ##
|
22
|
-
|
23
|
-
* No changes.
|
24
|
-
|
25
|
-
|
26
|
-
## Rails 6.1.7.5 (August 22, 2023) ##
|
27
|
-
|
28
|
-
* No changes.
|
29
|
-
|
30
|
-
|
31
|
-
## Rails 6.1.7.4 (June 26, 2023) ##
|
32
|
-
|
33
|
-
* No changes.
|
34
|
-
|
35
|
-
|
36
|
-
## Rails 6.1.7.3 (March 13, 2023) ##
|
37
|
-
|
38
|
-
* No changes.
|
39
|
-
|
40
|
-
|
41
|
-
## Rails 6.1.7.2 (January 24, 2023) ##
|
42
|
-
|
43
|
-
* No changes.
|
44
|
-
|
45
|
-
|
46
|
-
## Rails 6.1.7.1 (January 17, 2023) ##
|
47
|
-
|
48
|
-
* No changes.
|
49
|
-
|
50
|
-
|
51
|
-
## Rails 6.1.7 (September 09, 2022) ##
|
52
|
-
|
53
|
-
* No changes.
|
54
|
-
|
55
|
-
|
56
|
-
## Rails 6.1.6.1 (July 12, 2022) ##
|
57
|
-
|
58
|
-
* No changes.
|
59
|
-
|
60
|
-
|
61
|
-
## Rails 6.1.6 (May 09, 2022) ##
|
62
|
-
|
63
|
-
* No changes.
|
64
|
-
|
65
|
-
|
66
|
-
## Rails 6.1.5.1 (April 26, 2022) ##
|
67
|
-
|
68
|
-
* No changes.
|
69
|
-
|
70
|
-
|
71
|
-
## Rails 6.1.5 (March 09, 2022) ##
|
72
|
-
|
73
|
-
* Clear secure password cache if password is set to `nil`
|
74
|
-
|
75
|
-
Before:
|
76
|
-
|
77
|
-
user.password = 'something'
|
78
|
-
user.password = nil
|
79
|
-
|
80
|
-
user.password # => 'something'
|
81
|
-
|
82
|
-
Now:
|
83
|
-
|
84
|
-
user.password = 'something'
|
85
|
-
user.password = nil
|
86
|
-
|
87
|
-
user.password # => nil
|
88
|
-
|
89
|
-
*Markus Doits*
|
90
|
-
|
91
|
-
* Fix delegation in `ActiveModel::Type::Registry#lookup` and `ActiveModel::Type.lookup`
|
92
|
-
|
93
|
-
Passing a last positional argument `{}` would be incorrectly considered as keyword argument.
|
94
|
-
|
95
|
-
*Benoit Daloze*
|
96
|
-
|
97
|
-
* Fix `to_json` after `changes_applied` for `ActiveModel::Dirty` object.
|
98
|
-
|
99
|
-
*Ryuta Kamizono*
|
100
|
-
|
101
|
-
|
102
|
-
## Rails 6.1.4.7 (March 08, 2022) ##
|
103
|
-
|
104
|
-
* No changes.
|
105
|
-
|
106
|
-
|
107
|
-
## Rails 6.1.4.6 (February 11, 2022) ##
|
108
|
-
|
109
|
-
* No changes.
|
110
|
-
|
111
|
-
|
112
|
-
## Rails 6.1.4.5 (February 11, 2022) ##
|
113
|
-
|
114
|
-
* No changes.
|
115
|
-
|
116
|
-
|
117
|
-
## Rails 6.1.4.4 (December 15, 2021) ##
|
118
|
-
|
119
|
-
* No changes.
|
120
|
-
|
121
|
-
|
122
|
-
## Rails 6.1.4.3 (December 14, 2021) ##
|
123
|
-
|
124
|
-
* No changes.
|
125
|
-
|
126
|
-
|
127
|
-
## Rails 6.1.4.2 (December 14, 2021) ##
|
128
|
-
|
129
|
-
* No changes.
|
130
|
-
|
131
|
-
|
132
|
-
## Rails 6.1.4.1 (August 19, 2021) ##
|
133
|
-
|
134
|
-
* No changes.
|
135
|
-
|
136
|
-
|
137
|
-
## Rails 6.1.4 (June 24, 2021) ##
|
15
|
+
*Marcelo Lauxen*
|
138
16
|
|
139
17
|
* Fix `to_json` for `ActiveModel::Dirty` object.
|
140
18
|
|
141
|
-
Exclude
|
19
|
+
Exclude `mutations_from_database` attribute from json as it lead to recursion.
|
142
20
|
|
143
21
|
*Anil Maurya*
|
144
22
|
|
23
|
+
* Add `ActiveModel::AttributeSet#values_for_database`.
|
145
24
|
|
146
|
-
|
147
|
-
|
148
|
-
* No changes.
|
149
|
-
|
150
|
-
|
151
|
-
## Rails 6.1.3.1 (March 26, 2021) ##
|
152
|
-
|
153
|
-
* No changes.
|
154
|
-
|
25
|
+
Returns attributes with values for assignment to the database.
|
155
26
|
|
156
|
-
|
27
|
+
*Chris Salzberg*
|
157
28
|
|
158
|
-
*
|
29
|
+
* Fix delegation in ActiveModel::Type::Registry#lookup and ActiveModel::Type.lookup.
|
159
30
|
|
31
|
+
Passing a last positional argument `{}` would be incorrectly considered as keyword argument.
|
160
32
|
|
161
|
-
|
162
|
-
|
163
|
-
* No changes.
|
164
|
-
|
165
|
-
|
166
|
-
## Rails 6.1.2 (February 09, 2021) ##
|
167
|
-
|
168
|
-
* No changes.
|
169
|
-
|
170
|
-
|
171
|
-
## Rails 6.1.1 (January 07, 2021) ##
|
172
|
-
|
173
|
-
* No changes.
|
174
|
-
|
175
|
-
|
176
|
-
## Rails 6.1.0 (December 09, 2020) ##
|
177
|
-
|
178
|
-
* Pass in `base` instead of `base_class` to Error.human_attribute_name
|
179
|
-
|
180
|
-
This is useful in cases where the `human_attribute_name` method depends
|
181
|
-
on other attributes' values of the class under validation to derive what the
|
182
|
-
attribute name should be.
|
183
|
-
|
184
|
-
*Filipe Sabella*
|
185
|
-
|
186
|
-
* Deprecate marshalling load from legacy attributes format.
|
187
|
-
|
188
|
-
*Ryuta Kamizono*
|
189
|
-
|
190
|
-
* `*_previously_changed?` accepts `:from` and `:to` keyword arguments like `*_changed?`.
|
191
|
-
|
192
|
-
topic.update!(status: :archived)
|
193
|
-
topic.status_previously_changed?(from: "active", to: "archived")
|
194
|
-
# => true
|
195
|
-
|
196
|
-
*George Claghorn*
|
197
|
-
|
198
|
-
* Raise FrozenError when trying to write attributes that aren't backed by the database on an object that is frozen:
|
199
|
-
|
200
|
-
class Animal
|
201
|
-
include ActiveModel::Attributes
|
202
|
-
attribute :age
|
203
|
-
end
|
204
|
-
|
205
|
-
animal = Animal.new
|
206
|
-
animal.freeze
|
207
|
-
animal.age = 25 # => FrozenError, "can't modify a frozen Animal"
|
208
|
-
|
209
|
-
*Josh Brody*
|
210
|
-
|
211
|
-
* Add `*_previously_was` attribute methods when dirty tracking. Example:
|
212
|
-
|
213
|
-
pirate.update(catchphrase: "Ahoy!")
|
214
|
-
pirate.previous_changes["catchphrase"] # => ["Thar She Blows!", "Ahoy!"]
|
215
|
-
pirate.catchphrase_previously_was # => "Thar She Blows!"
|
33
|
+
*Benoit Daloze*
|
216
34
|
|
217
|
-
|
35
|
+
* Cache and re-use generated attribute methods.
|
218
36
|
|
219
|
-
|
37
|
+
Generated methods with identical implementations will now share their instruction sequences
|
38
|
+
leading to reduced memory retention, and slightly faster load time.
|
220
39
|
|
221
|
-
|
222
|
-
objects, instead of messages/details hash.
|
40
|
+
*Jean Boussier*
|
223
41
|
|
224
|
-
|
225
|
-
are for generating error messages. Its `details` method would return error’s
|
226
|
-
extra parameters, found in the original `details` hash.
|
42
|
+
* Add `in: range` parameter to `numericality` validator.
|
227
43
|
|
228
|
-
|
229
|
-
some edge cases won’t be covered, like `errors#first` will return `ActiveModel::Error` and manipulating
|
230
|
-
`errors.messages` and `errors.details` hashes directly will have no effect. Moving forward,
|
231
|
-
please convert those direct manipulations to use provided API methods instead.
|
232
|
-
Please note that `errors#add` now accepts `options` as keyword arguments instead of `Hash` which
|
233
|
-
introduced a change in Ruby 3 to [keyword arguments][kwargs-ann].
|
44
|
+
*Michal Papis*
|
234
45
|
|
235
|
-
|
46
|
+
* Add `locale` argument to `ActiveModel::Name#initialize` to be used to generate the `singular`,
|
47
|
+
`plural`, `route_key` and `singular_route_key` values.
|
236
48
|
|
237
|
-
|
49
|
+
*Lukas Pokorny*
|
238
50
|
|
239
|
-
|
240
|
-
* `errors#each` with the `key, value` two-arguments block will stop working, while the `error` single-argument block would return `Error` object.
|
241
|
-
* `errors#values` will be removed.
|
242
|
-
* `errors#keys` will be removed.
|
243
|
-
* `errors#to_xml` will be removed.
|
244
|
-
* `errors#to_h` will be removed, and can be replaced with `errors#to_hash`.
|
245
|
-
* Manipulating `errors` itself as a hash will have no effect (e.g. `errors[:foo] = 'bar'`).
|
246
|
-
* Manipulating the hash returned by `errors#messages` (e.g. `errors.messages[:foo] = 'bar'`) will have no effect.
|
247
|
-
* Manipulating the hash returned by `errors#details` (e.g. `errors.details[:foo].clear`) will have no effect.
|
51
|
+
* Make ActiveModel::Errors#inspect slimmer for readability
|
248
52
|
|
249
53
|
*lulalala*
|
250
54
|
|
251
|
-
|
252
|
-
Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activemodel/CHANGELOG.md) for previous changes.
|
55
|
+
Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/activemodel/CHANGELOG.md) for previous changes.
|
data/MIT-LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2004-
|
1
|
+
Copyright (c) 2004-2021 David Heinemeier Hansson
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
@@ -18,3 +18,4 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
18
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
19
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
20
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README.rdoc
CHANGED
@@ -16,10 +16,10 @@ Model solves this by defining an explicit API. You can read more about the
|
|
16
16
|
API in <tt>ActiveModel::Lint::Tests</tt>.
|
17
17
|
|
18
18
|
Active Model provides a default module that implements the basic API required
|
19
|
-
to integrate with Action Pack out of the box: <tt>ActiveModel::
|
19
|
+
to integrate with Action Pack out of the box: <tt>ActiveModel::API</tt>.
|
20
20
|
|
21
21
|
class Person
|
22
|
-
include ActiveModel::
|
22
|
+
include ActiveModel::API
|
23
23
|
|
24
24
|
attr_accessor :name, :age
|
25
25
|
validates_presence_of :name
|
@@ -32,7 +32,7 @@ to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>.
|
|
32
32
|
|
33
33
|
It includes model name introspections, conversions, translations and
|
34
34
|
validations, resulting in a class suitable to be used with Action Pack.
|
35
|
-
See <tt>ActiveModel::
|
35
|
+
See <tt>ActiveModel::API</tt> for more examples.
|
36
36
|
|
37
37
|
Active Model also provides the following functionality to have ORM-like
|
38
38
|
behavior out of the box:
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
# == Active \Model \API
|
5
|
+
#
|
6
|
+
# Includes the required interface for an object to interact with
|
7
|
+
# Action Pack and Action View, using different Active Model modules.
|
8
|
+
# It includes model name introspections, conversions, translations and
|
9
|
+
# validations. Besides that, it allows you to initialize the object with a
|
10
|
+
# hash of attributes, pretty much like Active Record does.
|
11
|
+
#
|
12
|
+
# A minimal implementation could be:
|
13
|
+
#
|
14
|
+
# class Person
|
15
|
+
# include ActiveModel::API
|
16
|
+
# attr_accessor :name, :age
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# person = Person.new(name: 'bob', age: '18')
|
20
|
+
# person.name # => "bob"
|
21
|
+
# person.age # => "18"
|
22
|
+
#
|
23
|
+
# Note that, by default, <tt>ActiveModel::API</tt> implements <tt>persisted?</tt>
|
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::API
|
29
|
+
# attr_accessor :id, :name
|
30
|
+
#
|
31
|
+
# def persisted?
|
32
|
+
# self.id.present?
|
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
|
40
|
+
# sure you call +super+ if you want the attributes hash initialization to
|
41
|
+
# happen.
|
42
|
+
#
|
43
|
+
# class Person
|
44
|
+
# include ActiveModel::API
|
45
|
+
# attr_accessor :id, :name, :omg
|
46
|
+
#
|
47
|
+
# def initialize(attributes={})
|
48
|
+
# super
|
49
|
+
# @omg ||= true
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# person = Person.new(id: 1, name: 'bob')
|
54
|
+
# person.omg # => true
|
55
|
+
#
|
56
|
+
# For more detailed information on other functionalities available, please
|
57
|
+
# refer to the specific modules included in <tt>ActiveModel::API</tt>
|
58
|
+
# (see below).
|
59
|
+
module API
|
60
|
+
extend ActiveSupport::Concern
|
61
|
+
include ActiveModel::AttributeAssignment
|
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::API
|
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::API
|
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
|
98
|
+
end
|
99
|
+
end
|
@@ -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
|
+
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
|
@@ -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,7 +358,37 @@ module ActiveModel
|
|
335
358
|
end
|
336
359
|
|
337
360
|
private
|
338
|
-
class CodeGenerator
|
361
|
+
class CodeGenerator # :nodoc:
|
362
|
+
class MethodSet
|
363
|
+
METHOD_CACHES = Hash.new { |h, k| h[k] = Module.new }
|
364
|
+
|
365
|
+
def initialize(namespace)
|
366
|
+
@cache = METHOD_CACHES[namespace]
|
367
|
+
@sources = []
|
368
|
+
@methods = {}
|
369
|
+
end
|
370
|
+
|
371
|
+
def define_cached_method(name, as: name)
|
372
|
+
name = name.to_sym
|
373
|
+
as = as.to_sym
|
374
|
+
@methods.fetch(name) do
|
375
|
+
unless @cache.method_defined?(as)
|
376
|
+
yield @sources
|
377
|
+
end
|
378
|
+
@methods[name] = as
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def apply(owner, path, line)
|
383
|
+
unless @sources.empty?
|
384
|
+
@cache.module_eval("# frozen_string_literal: true\n" + @sources.join(";"), path, line)
|
385
|
+
end
|
386
|
+
@methods.each do |name, as|
|
387
|
+
owner.define_method(name, @cache.instance_method(as))
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
339
392
|
class << self
|
340
393
|
def batch(owner, path, line)
|
341
394
|
if owner.is_a?(CodeGenerator)
|
@@ -353,23 +406,16 @@ module ActiveModel
|
|
353
406
|
@owner = owner
|
354
407
|
@path = path
|
355
408
|
@line = line
|
356
|
-
@
|
357
|
-
@renames = {}
|
358
|
-
end
|
359
|
-
|
360
|
-
def <<(source_line)
|
361
|
-
@sources << source_line
|
409
|
+
@namespaces = Hash.new { |h, k| h[k] = MethodSet.new(k) }
|
362
410
|
end
|
363
411
|
|
364
|
-
def
|
365
|
-
@
|
412
|
+
def define_cached_method(name, namespace:, as: name, &block)
|
413
|
+
@namespaces[namespace].define_cached_method(name, as: as, &block)
|
366
414
|
end
|
367
415
|
|
368
416
|
def execute
|
369
|
-
@
|
370
|
-
|
371
|
-
@owner.alias_method new_name, old_name
|
372
|
-
@owner.undef_method old_name
|
417
|
+
@namespaces.each_value do |method_set|
|
418
|
+
method_set.apply(@owner, @path, @line - 1)
|
373
419
|
end
|
374
420
|
end
|
375
421
|
end
|
@@ -398,42 +444,48 @@ module ActiveModel
|
|
398
444
|
|
399
445
|
def attribute_method_matchers_matching(method_name)
|
400
446
|
attribute_method_matchers_cache.compute_if_absent(method_name) do
|
401
|
-
attribute_method_matchers.
|
447
|
+
attribute_method_matchers.filter_map { |matcher| matcher.match(method_name) }
|
402
448
|
end
|
403
449
|
end
|
404
450
|
|
405
451
|
# 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|"
|
452
|
+
# using the given `extra` args. This falls back on `send`
|
453
|
+
# if the called name cannot be compiled.
|
454
|
+
def define_proxy_call(code_generator, name, target, parameters, *call_args, namespace:)
|
455
|
+
mangled_name = name
|
456
|
+
unless NAME_COMPILABLE_REGEXP.match?(name)
|
457
|
+
mangled_name = "__temp__#{name.unpack1("h*")}"
|
413
458
|
end
|
414
459
|
|
415
|
-
|
460
|
+
code_generator.define_cached_method(name, as: mangled_name, namespace: namespace) do |batch|
|
461
|
+
call_args.map!(&:inspect)
|
462
|
+
call_args << parameters if parameters
|
416
463
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
464
|
+
body = if CALL_COMPILABLE_REGEXP.match?(target)
|
465
|
+
"self.#{target}(#{call_args.join(", ")})"
|
466
|
+
else
|
467
|
+
call_args.unshift(":'#{target}'")
|
468
|
+
"send(#{call_args.join(", ")})"
|
469
|
+
end
|
470
|
+
|
471
|
+
modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
|
422
472
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
473
|
+
batch <<
|
474
|
+
"#{modifier}def #{mangled_name}(#{parameters || ''})" <<
|
475
|
+
body <<
|
476
|
+
"end"
|
477
|
+
end
|
428
478
|
end
|
429
479
|
|
430
|
-
class AttributeMethodMatcher
|
431
|
-
attr_reader :prefix, :suffix, :target
|
480
|
+
class AttributeMethodMatcher # :nodoc:
|
481
|
+
attr_reader :prefix, :suffix, :target, :parameters
|
432
482
|
|
433
483
|
AttributeMethodMatch = Struct.new(:target, :attr_name)
|
434
484
|
|
435
|
-
def initialize(
|
436
|
-
@prefix
|
485
|
+
def initialize(prefix: "", suffix: "", parameters: nil)
|
486
|
+
@prefix = prefix
|
487
|
+
@suffix = suffix
|
488
|
+
@parameters = parameters.nil? ? FORWARD_PARAMETERS : parameters
|
437
489
|
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
|
438
490
|
@target = "#{@prefix}attribute#{@suffix}"
|
439
491
|
@method_name = "#{prefix}%s#{suffix}"
|
@@ -469,7 +521,7 @@ module ActiveModel
|
|
469
521
|
match ? attribute_missing(match, *args, &block) : super
|
470
522
|
end
|
471
523
|
end
|
472
|
-
ruby2_keywords(:method_missing)
|
524
|
+
ruby2_keywords(:method_missing)
|
473
525
|
|
474
526
|
# +attribute_missing+ is like +method_missing+, but for attributes. When
|
475
527
|
# +method_missing+ is called we check to see if there is a matching
|
@@ -520,10 +572,6 @@ module ActiveModel
|
|
520
572
|
|
521
573
|
# We want to generate the methods via module_eval rather than
|
522
574
|
# 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
575
|
#
|
528
576
|
# But sometimes the database might return columns with
|
529
577
|
# characters that are not allowed in normal method names (like
|
@@ -547,7 +595,6 @@ module ActiveModel
|
|
547
595
|
temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
|
548
596
|
attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
|
549
597
|
yield temp_method_name, attr_name_expr
|
550
|
-
owner.rename_method(temp_method_name, method_name)
|
551
598
|
end
|
552
599
|
end
|
553
600
|
end
|