lib/active_model/attribute_methods.rb in activemodel-7.1.5 vs lib/active_model/attribute_methods.rb in activemodel-7.2.0.beta1
- old
+ new
@@ -64,11 +64,10 @@
module AttributeMethods
extend ActiveSupport::Concern
NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
- FORWARD_PARAMETERS = "*args"
included do
class_attribute :attribute_aliases, instance_writer: false, default: {}
class_attribute :attribute_method_patterns, instance_writer: false, default: [ ClassMethods::AttributeMethodPattern.new ]
end
@@ -213,45 +212,27 @@
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
generate_alias_attribute_methods(code_generator, new_name, old_name)
end
end
- def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
- ActiveSupport::CodeGenerator.batch(code_generator, __FILE__, __LINE__) do |owner|
- attribute_method_patterns.each do |pattern|
- alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
- end
- attribute_method_patterns_cache.clear
+ 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
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
parameters = pattern.parameters
- mangled_name = target_name
- unless NAME_COMPILABLE_REGEXP.match?(target_name)
- mangled_name = "__temp__#{target_name.unpack1("h*")}"
- end
+ mangled_name = build_mangled_name(target_name)
- code_generator.define_cached_method(mangled_name, as: method_name, namespace: :alias_attribute) do |batch|
- body = if CALL_COMPILABLE_REGEXP.match?(target_name)
- "self.#{target_name}(#{parameters || ''})"
- else
- call_args = [":'#{target_name}'"]
- call_args << parameters if parameters
- "send(#{call_args.join(", ")})"
- end
+ call_args = []
+ call_args << parameters if parameters
- modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
-
- batch <<
- "#{modifier}def #{mangled_name}(#{parameters || ''})" <<
- body <<
- "end"
- end
+ define_call(code_generator, method_name, target_name, mangled_name, parameters, call_args, namespace: :alias_attribute)
end
# Is +new_name+ an alias?
def attribute_alias?(new_name)
attribute_aliases.key? new_name.to_s
@@ -322,48 +303,29 @@
#
# person = Person.new
# person.name = 'Bob'
# person.name # => "Bob"
# person.name_short? # => true
- def define_attribute_method(attr_name, _owner: generated_attribute_methods, as: attr_name)
+ def define_attribute_method(attr_name, _owner: generated_attribute_methods)
ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
attribute_method_patterns.each do |pattern|
- define_attribute_method_pattern(pattern, attr_name, owner: owner, as: as)
+ 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
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
#
@@ -401,10 +363,12 @@
private
def inherited(base) # :nodoc:
super
base.class_eval do
@attribute_method_patterns_cache = nil
+ @aliases_by_attribute_name = nil
+ @generated_attribute_methods = nil
end
end
def resolve_attribute_name(name)
attribute_aliases.fetch(super, &:itself)
@@ -438,32 +402,41 @@
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:, as: name)
+ def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace:)
+ 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)
+ end
+
+ def build_mangled_name(name)
mangled_name = name
+
unless NAME_COMPILABLE_REGEXP.match?(name)
mangled_name = "__temp__#{name.unpack1("h*")}"
end
- call_args.map!(&:inspect)
- call_args << parameters if parameters
- namespace = :"#{namespace}_#{proxy_target}"
+ mangled_name
+ end
- code_generator.define_cached_method(mangled_name, as: as, namespace: namespace) do |batch|
- body = if CALL_COMPILABLE_REGEXP.match?(proxy_target)
- "self.#{proxy_target}(#{call_args.join(", ")})"
+ 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|
+ body = if CALL_COMPILABLE_REGEXP.match?(target_name)
+ "self.#{target_name}(#{call_args.join(", ")})"
else
- call_args.unshift(":'#{proxy_target}'")
+ call_args.unshift(":'#{target_name}'")
"send(#{call_args.join(", ")})"
end
- modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
-
batch <<
- "#{modifier}def #{mangled_name}(#{parameters || ''})" <<
+ "def #{mangled_name}(#{parameters || ''})" <<
body <<
"end"
end
end
@@ -473,11 +446,11 @@
AttributeMethod = Struct.new(:proxy_target, :attr_name)
def initialize(prefix: "", suffix: "", parameters: nil)
@prefix = prefix
@suffix = suffix
- @parameters = parameters.nil? ? FORWARD_PARAMETERS : parameters
+ @parameters = parameters.nil? ? "..." : parameters
@regex = /\A(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})\z/
@proxy_target = "#{@prefix}attribute#{@suffix}"
@method_name = "#{prefix}%s#{suffix}"
end
@@ -501,27 +474,25 @@
# <tt>ActiveRecord::Base#attributes=</tt>.
#
# It's also possible to instantiate related objects, so a <tt>Client</tt>
# class belonging to the +clients+ table with a +master_id+ foreign key
# can instantiate master through <tt>Client#master</tt>.
- def method_missing(method, *args, &block)
+ def method_missing(method, ...)
if respond_to_without_attributes?(method, true)
super
else
- match = matched_attribute_method(method.to_s)
- match ? attribute_missing(match, *args, &block) : super
+ match = matched_attribute_method(method.name)
+ match ? attribute_missing(match, ...) : super
end
end
- ruby2_keywords(:method_missing)
# +attribute_missing+ is like +method_missing+, but for attributes. When
# +method_missing+ is called we check to see if there is a matching
# attribute method. If so, we tell +attribute_missing+ to dispatch the
# attribute. This method can be overloaded to customize the behavior.
- def attribute_missing(match, *args, &block)
- __send__(match.proxy_target, match.attr_name, *args, &block)
+ def attribute_missing(match, ...)
+ __send__(match.proxy_target, match.attr_name, ...)
end
- ruby2_keywords(:attribute_missing)
# A +Person+ instance with a +name+ attribute can ask
# <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>,
# and <tt>person.respond_to?(:name?)</tt> which will all return +true+.
alias :respond_to_without_attributes? :respond_to?