lib/active_model/attribute_methods.rb in activemodel-7.2.0.beta2 vs lib/active_model/attribute_methods.rb in activemodel-7.2.0.beta3
- old
+ new
@@ -213,13 +213,11 @@
generate_alias_attribute_methods(code_generator, new_name, old_name)
end
end
def generate_alias_attribute_methods(code_generator, new_name, old_name)
- attribute_method_patterns.each do |pattern|
- alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
- end
+ define_attribute_method(old_name, _owner: code_generator, as: new_name)
end
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
method_name = pattern.method_name(new_name).to_s
target_name = pattern.method_name(old_name).to_s
@@ -303,29 +301,49 @@
#
# person = Person.new
# person.name = 'Bob'
# person.name # => "Bob"
# person.name_short? # => true
- def define_attribute_method(attr_name, _owner: generated_attribute_methods)
+ def define_attribute_method(attr_name, _owner: generated_attribute_methods, as: attr_name)
ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
attribute_method_patterns.each do |pattern|
- method_name = pattern.method_name(attr_name)
-
- unless instance_method_already_implemented?(method_name)
- generate_method = "define_method_#{pattern.proxy_target}"
-
- if respond_to?(generate_method, true)
- send(generate_method, attr_name.to_s, owner: owner)
- else
- define_proxy_call(owner, method_name, pattern.proxy_target, pattern.parameters, attr_name.to_s, namespace: :active_model_proxy)
- end
- end
+ define_attribute_method_pattern(pattern, attr_name, owner: owner, as: as)
end
attribute_method_patterns_cache.clear
end
end
+ def define_attribute_method_pattern(pattern, attr_name, owner:, as:, override: false) # :nodoc:
+ canonical_method_name = pattern.method_name(attr_name)
+ public_method_name = pattern.method_name(as)
+
+ # If defining a regular attribute method, we don't override methods that are explictly
+ # defined in parrent classes.
+ if instance_method_already_implemented?(public_method_name)
+ # However, for `alias_attribute`, we always define the method.
+ # We check for override second because `instance_method_already_implemented?`
+ # also check for dangerous methods.
+ return unless override
+ end
+
+ generate_method = "define_method_#{pattern.proxy_target}"
+
+ if respond_to?(generate_method, true)
+ send(generate_method, attr_name.to_s, owner: owner, as: as)
+ else
+ define_proxy_call(
+ owner,
+ canonical_method_name,
+ pattern.proxy_target,
+ pattern.parameters,
+ attr_name.to_s,
+ namespace: :active_model_proxy,
+ as: public_method_name
+ )
+ end
+ end
+
# Removes all the previously dynamically defined methods from the class, including alias attribute methods.
#
# class Person
# include ActiveModel::AttributeMethods
#
@@ -402,18 +420,23 @@
end
# Define a method `name` in `mod` that dispatches to `send`
# using the given `extra` args. This falls back on `send`
# if the called name cannot be compiled.
- def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace:)
+ def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace:, as: name)
mangled_name = build_mangled_name(name)
call_args.map!(&:inspect)
call_args << parameters if parameters
- namespace = :"#{namespace}_#{proxy_target}_#{call_args.join("_")}}"
- define_call(code_generator, name, proxy_target, mangled_name, parameters, call_args, namespace: namespace)
+ # We have to use a different namespace for every target method, because
+ # if someone defines an attribute that look like an attribute method we could clash, e.g.
+ # attribute :title_was
+ # attribute :title
+ namespace = :"#{namespace}_#{proxy_target}"
+
+ define_call(code_generator, name, proxy_target, mangled_name, parameters, call_args, namespace: namespace, as: as)
end
def build_mangled_name(name)
mangled_name = name
@@ -422,11 +445,11 @@
end
mangled_name
end
- def define_call(code_generator, name, target_name, mangled_name, parameters, call_args, namespace:)
- code_generator.define_cached_method(name, as: mangled_name, namespace: namespace) do |batch|
+ def define_call(code_generator, name, target_name, mangled_name, parameters, call_args, namespace:, as:)
+ code_generator.define_cached_method(mangled_name, as: as, namespace: namespace) do |batch|
body = if CALL_COMPILABLE_REGEXP.match?(target_name)
"self.#{target_name}(#{call_args.join(", ")})"
else
call_args.unshift(":'#{target_name}'")
"send(#{call_args.join(", ")})"