mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #41911 from Shopify/simplify-proxy-call
Allow to pass the method signature when defining attribute methods
This commit is contained in:
commit
6a5fb7dbd4
8 changed files with 50 additions and 36 deletions
|
@ -67,6 +67,7 @@ module ActiveModel
|
||||||
|
|
||||||
NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
|
NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
|
||||||
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
|
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
|
||||||
|
FORWARD_PARAMETERS = "*args"
|
||||||
|
|
||||||
included do
|
included do
|
||||||
class_attribute :attribute_aliases, instance_writer: false, default: {}
|
class_attribute :attribute_aliases, instance_writer: false, default: {}
|
||||||
|
@ -105,8 +106,8 @@ module ActiveModel
|
||||||
# person.name # => "Bob"
|
# person.name # => "Bob"
|
||||||
# person.clear_name
|
# person.clear_name
|
||||||
# person.name # => nil
|
# person.name # => nil
|
||||||
def attribute_method_prefix(*prefixes)
|
def attribute_method_prefix(*prefixes, parameters: nil)
|
||||||
self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
|
self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new(prefix: prefix, parameters: parameters) }
|
||||||
undefine_attribute_methods
|
undefine_attribute_methods
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -140,8 +141,8 @@ module ActiveModel
|
||||||
# person.name = 'Bob'
|
# person.name = 'Bob'
|
||||||
# person.name # => "Bob"
|
# person.name # => "Bob"
|
||||||
# person.name_short? # => true
|
# person.name_short? # => true
|
||||||
def attribute_method_suffix(*suffixes)
|
def attribute_method_suffix(*suffixes, parameters: nil)
|
||||||
self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix }
|
self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new(suffix: suffix, parameters: parameters) }
|
||||||
undefine_attribute_methods
|
undefine_attribute_methods
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -177,7 +178,7 @@ module ActiveModel
|
||||||
# person.reset_name_to_default!
|
# person.reset_name_to_default!
|
||||||
# person.name # => 'Default Name'
|
# person.name # => 'Default Name'
|
||||||
def attribute_method_affix(*affixes)
|
def attribute_method_affix(*affixes)
|
||||||
self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] }
|
self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new(**affix) }
|
||||||
undefine_attribute_methods
|
undefine_attribute_methods
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -211,7 +212,7 @@ module ActiveModel
|
||||||
attribute_method_matchers.each do |matcher|
|
attribute_method_matchers.each do |matcher|
|
||||||
matcher_new = matcher.method_name(new_name).to_s
|
matcher_new = matcher.method_name(new_name).to_s
|
||||||
matcher_old = matcher.method_name(old_name).to_s
|
matcher_old = matcher.method_name(old_name).to_s
|
||||||
define_proxy_call owner, matcher_new, matcher_old
|
define_proxy_call(owner, matcher_new, matcher_old, matcher.parameters)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -296,7 +297,7 @@ module ActiveModel
|
||||||
if respond_to?(generate_method, true)
|
if respond_to?(generate_method, true)
|
||||||
send(generate_method, attr_name.to_s, owner: owner)
|
send(generate_method, attr_name.to_s, owner: owner)
|
||||||
else
|
else
|
||||||
define_proxy_call owner, method_name, matcher.target, attr_name.to_s
|
define_proxy_call(owner, method_name, matcher.target, matcher.parameters, attr_name.to_s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -405,35 +406,47 @@ module ActiveModel
|
||||||
# Define a method `name` in `mod` that dispatches to `send`
|
# Define a method `name` in `mod` that dispatches to `send`
|
||||||
# using the given `extra` args. This falls back on `define_method`
|
# using the given `extra` args. This falls back on `define_method`
|
||||||
# and `send` if the given names cannot be compiled.
|
# and `send` if the given names cannot be compiled.
|
||||||
def define_proxy_call(code_generator, name, target, *extra)
|
def define_proxy_call(code_generator, name, target, parameters, *call_args)
|
||||||
defn = if NAME_COMPILABLE_REGEXP.match?(name)
|
mangled_name = name
|
||||||
"def #{name}(*args)"
|
unless NAME_COMPILABLE_REGEXP.match?(name)
|
||||||
else
|
mangled_name = "__temp__#{name.unpack1("h*")}"
|
||||||
"define_method(:'#{name}') do |*args|"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
extra = (extra.map!(&:inspect) << "*args").join(", ")
|
call_args.map!(&:inspect)
|
||||||
|
call_args << parameters if parameters
|
||||||
|
|
||||||
body = if CALL_COMPILABLE_REGEXP.match?(target)
|
body = if CALL_COMPILABLE_REGEXP.match?(target)
|
||||||
"self.#{target}(#{extra})"
|
"self.#{target}(#{call_args.join(", ")})"
|
||||||
else
|
else
|
||||||
"send(:'#{target}', #{extra})"
|
call_args.unshift(":'#{target}'")
|
||||||
|
"send(#{call_args.join(", ")})"
|
||||||
end
|
end
|
||||||
|
|
||||||
code_generator <<
|
code_generator <<
|
||||||
defn <<
|
"def #{mangled_name}(#{parameters || ''})" <<
|
||||||
body <<
|
body <<
|
||||||
"end" <<
|
"end"
|
||||||
"ruby2_keywords(:'#{name}')"
|
|
||||||
|
if parameters == FORWARD_PARAMETERS
|
||||||
|
code_generator << "ruby2_keywords(:'#{mangled_name}')"
|
||||||
|
end
|
||||||
|
|
||||||
|
if mangled_name != name
|
||||||
|
code_generator <<
|
||||||
|
"alias_method(:'#{name}', :'#{mangled_name}')" <<
|
||||||
|
"remove_method(:'#{mangled_name}')"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class AttributeMethodMatcher #:nodoc:
|
class AttributeMethodMatcher #:nodoc:
|
||||||
attr_reader :prefix, :suffix, :target
|
attr_reader :prefix, :suffix, :target, :parameters
|
||||||
|
|
||||||
AttributeMethodMatch = Struct.new(:target, :attr_name)
|
AttributeMethodMatch = Struct.new(:target, :attr_name)
|
||||||
|
|
||||||
def initialize(options = {})
|
def initialize(prefix: "", suffix: "", parameters: nil)
|
||||||
@prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "")
|
@prefix = prefix
|
||||||
|
@suffix = suffix
|
||||||
|
@parameters = parameters.nil? ? FORWARD_PARAMETERS : parameters
|
||||||
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
|
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
|
||||||
@target = "#{@prefix}attribute#{@suffix}"
|
@target = "#{@prefix}attribute#{@suffix}"
|
||||||
@method_name = "#{prefix}%s#{suffix}"
|
@method_name = "#{prefix}%s#{suffix}"
|
||||||
|
|
|
@ -9,7 +9,7 @@ module ActiveModel
|
||||||
include ActiveModel::AttributeMethods
|
include ActiveModel::AttributeMethods
|
||||||
|
|
||||||
included do
|
included do
|
||||||
attribute_method_suffix "="
|
attribute_method_suffix "=", parameters: "value"
|
||||||
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
|
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
|
||||||
self.attribute_types = Hash.new(Type.default_value)
|
self.attribute_types = Hash.new(Type.default_value)
|
||||||
self._default_attributes = AttributeSet.new({})
|
self._default_attributes = AttributeSet.new({})
|
||||||
|
|
|
@ -123,10 +123,11 @@ module ActiveModel
|
||||||
include ActiveModel::AttributeMethods
|
include ActiveModel::AttributeMethods
|
||||||
|
|
||||||
included do
|
included do
|
||||||
attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
|
attribute_method_suffix "_previously_changed?", "_changed?", parameters: "**options"
|
||||||
attribute_method_suffix "_previously_changed?", "_previous_change", "_previously_was"
|
attribute_method_suffix "_change", "_will_change!", "_was", parameters: false
|
||||||
attribute_method_affix prefix: "restore_", suffix: "!"
|
attribute_method_suffix "_previous_change", "_previously_was", parameters: false
|
||||||
attribute_method_affix prefix: "clear_", suffix: "_change"
|
attribute_method_affix prefix: "restore_", suffix: "!", parameters: false
|
||||||
|
attribute_method_affix prefix: "clear_", suffix: "_change", parameters: false
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize_dup(other) # :nodoc:
|
def initialize_dup(other) # :nodoc:
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Topic
|
||||||
include ActiveModel::AttributeMethods
|
include ActiveModel::AttributeMethods
|
||||||
include ActiveSupport::NumberHelper
|
include ActiveSupport::NumberHelper
|
||||||
|
|
||||||
attribute_method_suffix "_before_type_cast"
|
attribute_method_suffix "_before_type_cast", parameters: false
|
||||||
define_attribute_method :price
|
define_attribute_method :price
|
||||||
|
|
||||||
def self._validates_default_keys
|
def self._validates_default_keys
|
||||||
|
|
|
@ -29,8 +29,8 @@ module ActiveRecord
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
attribute_method_suffix "_before_type_cast", "_for_database"
|
attribute_method_suffix "_before_type_cast", "_for_database", parameters: false
|
||||||
attribute_method_suffix "_came_from_user?"
|
attribute_method_suffix "_came_from_user?", parameters: false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the value of the attribute identified by +attr_name+ before
|
# Returns the value of the attribute identified by +attr_name+ before
|
||||||
|
|
|
@ -17,13 +17,13 @@ module ActiveRecord
|
||||||
class_attribute :partial_writes, instance_writer: false, default: true
|
class_attribute :partial_writes, instance_writer: false, default: true
|
||||||
|
|
||||||
# Attribute methods for "changed in last call to save?"
|
# Attribute methods for "changed in last call to save?"
|
||||||
attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
|
attribute_method_affix(prefix: "saved_change_to_", suffix: "?", parameters: "**options")
|
||||||
attribute_method_prefix("saved_change_to_")
|
attribute_method_prefix("saved_change_to_", parameters: false)
|
||||||
attribute_method_suffix("_before_last_save")
|
attribute_method_suffix("_before_last_save", parameters: false)
|
||||||
|
|
||||||
# Attribute methods for "will change if I call save?"
|
# Attribute methods for "will change if I call save?"
|
||||||
attribute_method_affix(prefix: "will_save_change_to_", suffix: "?")
|
attribute_method_affix(prefix: "will_save_change_to_", suffix: "?", parameters: "**options")
|
||||||
attribute_method_suffix("_change_to_be_saved", "_in_database")
|
attribute_method_suffix("_change_to_be_saved", "_in_database", parameters: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
# <tt>reload</tt> the record and clears changed attributes.
|
# <tt>reload</tt> the record and clears changed attributes.
|
||||||
|
|
|
@ -6,7 +6,7 @@ module ActiveRecord
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
attribute_method_suffix "?"
|
attribute_method_suffix "?", parameters: false
|
||||||
end
|
end
|
||||||
|
|
||||||
def query_attribute(attr_name)
|
def query_attribute(attr_name)
|
||||||
|
|
|
@ -6,7 +6,7 @@ module ActiveRecord
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
attribute_method_suffix "="
|
attribute_method_suffix "=", parameters: "value"
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods # :nodoc:
|
module ClassMethods # :nodoc:
|
||||||
|
|
Loading…
Reference in a new issue