activemodel 6.1.7.10 → 7.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -224
  3. data/MIT-LICENSE +2 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_model/api.rb +99 -0
  6. data/lib/active_model/attribute.rb +4 -0
  7. data/lib/active_model/attribute_methods.rb +99 -52
  8. data/lib/active_model/attribute_set.rb +4 -1
  9. data/lib/active_model/attributes.rb +15 -12
  10. data/lib/active_model/callbacks.rb +1 -1
  11. data/lib/active_model/conversion.rb +2 -2
  12. data/lib/active_model/dirty.rb +5 -4
  13. data/lib/active_model/errors.rb +17 -3
  14. data/lib/active_model/gem_version.rb +4 -4
  15. data/lib/active_model/locale/en.yml +1 -0
  16. data/lib/active_model/model.rb +6 -59
  17. data/lib/active_model/naming.rb +15 -8
  18. data/lib/active_model/secure_password.rb +0 -1
  19. data/lib/active_model/serialization.rb +7 -2
  20. data/lib/active_model/translation.rb +1 -1
  21. data/lib/active_model/type/helpers/numeric.rb +9 -1
  22. data/lib/active_model/type/helpers/time_value.rb +2 -2
  23. data/lib/active_model/type/integer.rb +4 -1
  24. data/lib/active_model/type/registry.rb +8 -38
  25. data/lib/active_model/type/time.rb +1 -1
  26. data/lib/active_model/type.rb +6 -6
  27. data/lib/active_model/validations/absence.rb +1 -1
  28. data/lib/active_model/validations/clusivity.rb +1 -1
  29. data/lib/active_model/validations/comparability.rb +29 -0
  30. data/lib/active_model/validations/comparison.rb +82 -0
  31. data/lib/active_model/validations/confirmation.rb +4 -4
  32. data/lib/active_model/validations/numericality.rb +27 -20
  33. data/lib/active_model/validations.rb +4 -4
  34. data/lib/active_model/validator.rb +2 -2
  35. data/lib/active_model.rb +2 -1
  36. metadata +14 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bfa30ec1cf39f0ae54a4f77f21d1023a8e435113866365d73a47627db279756b
4
- data.tar.gz: ef666a965c4f96f319cfbe24fb8842a2e53b20d1989972f0b688fc4a1d82ce3b
3
+ metadata.gz: 03bc5c9c2ea021d9c3e3a3a47bf160adc73738341c6e488e85de66a38de111ac
4
+ data.tar.gz: a6c81f5be5dbc43a7086563d786221aadfe1d9392c2ff1fc483ee433ffab4230
5
5
  SHA512:
6
- metadata.gz: ef9afd00feb2966985cb1ac29259d65c4c10fd743c992394da2795cf9463ce9b17d9518741cabb5932b11640c635f37c45827fd25620f4e1e16243b8c342e63c
7
- data.tar.gz: 1793766ec3e99cbb7ec5cfd4697233e84510099d9b8607d9478438e830802c4e2c0d67de36d6b611a3b9e4a71dab950f63c7866d2994c600f1ee48f727c907c4
6
+ metadata.gz: 3c7f97c5745ee6074629c8e546a738fbb8616581db954e78630df9a361bd3ae098bca8bec4afcc3891db83addc77e61349a6dcca2040844b715bed7b4a27c803
7
+ data.tar.gz: 143fbb8f40593fe72a1d3465d18affdf9ca3d30e11189d7a35f899af0b5f090b6c1209cf7294361a7dd3c1c5376b92555ee00e3afc92fc2715b26b63c51c7529
data/CHANGELOG.md CHANGED
@@ -1,252 +1,55 @@
1
- ## Rails 6.1.7.10 (October 23, 2024) ##
1
+ ## Rails 7.0.0.alpha1 (September 15, 2021) ##
2
2
 
3
- * No changes.
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
- ## Rails 6.1.7.9 (October 15, 2024) ##
8
+ *Petrik de Heus*, *Nathaniel Watts*
7
9
 
8
- * No changes.
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
- ## Rails 6.1.7.8 (June 04, 2024) ##
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 +mutations_from_database+ attribute from json as it lead to recursion.
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
- ## Rails 6.1.3.2 (May 05, 2021) ##
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
- ## Rails 6.1.3 (February 17, 2021) ##
27
+ *Chris Salzberg*
157
28
 
158
- * No changes.
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
- ## Rails 6.1.2.1 (February 10, 2021) ##
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
- *DHH*
35
+ * Cache and re-use generated attribute methods.
218
36
 
219
- * Encapsulate each validation error as an Error object.
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
- The `ActiveModel`’s `errors` collection is now an array of these Error
222
- objects, instead of messages/details hash.
40
+ *Jean Boussier*
223
41
 
224
- For each of these `Error` object, its `message` and `full_message` methods
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
- The change tries its best at maintaining backward compatibility, however
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
- [kwargs-ann]: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
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
- The list of deprecated methods and their planned future behavioral changes at the next major release are:
49
+ *Lukas Pokorny*
238
50
 
239
- * `errors#slice!` will be removed.
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-2022 David Heinemeier Hansson
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::Model</tt>.
19
+ to integrate with Action Pack out of the box: <tt>ActiveModel::API</tt>.
20
20
 
21
21
  class Person
22
- include ActiveModel::Model
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::Model</tt> for more examples.
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
@@ -56,6 +56,10 @@ module ActiveModel
56
56
  type.serialize(value)
57
57
  end
58
58
 
59
+ def serializable?(&block)
60
+ type.serializable?(value, &block)
61
+ end
62
+
59
63
  def changed?
60
64
  changed_from_assignment? || changed_in_place?
61
65
  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 prefix: prefix }
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 suffix: suffix }
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 prefix: affix[:prefix], suffix: affix[:suffix] }
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 |owner|
211
+ CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator|
211
212
  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
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 true, owner, method_name, matcher.target, attr_name.to_s
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
- @sources = ["# frozen_string_literal: true\n"]
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 rename_method(old_name, new_name)
365
- @renames[old_name] = new_name
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
- @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
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.map { |matcher| matcher.match(method_name) }.compact
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 `define_method`
407
- # and `send` if the given names cannot be compiled.
408
- def define_proxy_call(include_private, code_generator, name, target, *extra)
409
- defn = if NAME_COMPILABLE_REGEXP.match?(name)
410
- "def #{name}(*args)"
411
- else
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
- extra = (extra.map!(&:inspect) << "*args").join(", ")
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
- body = if CALL_COMPILABLE_REGEXP.match?(target)
418
- "#{"self." unless include_private}#{target}(#{extra})"
419
- else
420
- "send(:'#{target}', #{extra})"
421
- end
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
- code_generator <<
424
- defn <<
425
- body <<
426
- "end" <<
427
- "ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)"
473
+ batch <<
474
+ "#{modifier}def #{mangled_name}(#{parameters || ''})" <<
475
+ body <<
476
+ "end"
477
+ end
428
478
  end
429
479
 
430
- class AttributeMethodMatcher #:nodoc:
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(options = {})
436
- @prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "")
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) if respond_to?(:ruby2_keywords, true)
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