aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/attribute_methods.rb
diff options
context:
space:
mode:
authorDylan Thacker-Smith <Dylan.Smith@shopify.com>2018-06-22 11:54:31 -0400
committerJeremy Daer <jeremydaer@gmail.com>2018-10-12 09:50:10 -0700
commit99c87ad2474d5c5b6e52ceac34c3cf9f9cb57f9f (patch)
treef1000530afb4b52af586e5486db5df50b52b9e81 /activemodel/lib/active_model/attribute_methods.rb
parentee95bed3e6e16eadd1940d9c27953236f1649c08 (diff)
downloadrails-99c87ad2474d5c5b6e52ceac34c3cf9f9cb57f9f.tar.gz
rails-99c87ad2474d5c5b6e52ceac34c3cf9f9cb57f9f.tar.bz2
rails-99c87ad2474d5c5b6e52ceac34c3cf9f9cb57f9f.zip
Improve model attribute accessor method names for backtraces
Ruby uses the original method name, so will show the __temp__ method name in the backtrace. However, in the common case the method name is compatible with the `def` keyword, so we can avoid the __temp__ method name in that case to improve the name shown in backtraces or TracePoint#method_id.
Diffstat (limited to 'activemodel/lib/active_model/attribute_methods.rb')
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb38
1 files changed, 38 insertions, 0 deletions
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 1ad9071cc2..d8352343e9 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -474,5 +474,43 @@ module ActiveModel
def _read_attribute(attr)
__send__(attr)
end
+
+ module AttrNames # :nodoc:
+ DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/
+
+ # We want to generate the methods via module_eval rather than
+ # define_method, because define_method is slower on dispatch.
+ # Evaluating many similar methods may use more memory as the instruction
+ # sequences are duplicated and cached (in MRI). define_method may
+ # be slower on dispatch, but if you're careful about the closure
+ # created, then define_method will consume much less memory.
+ #
+ # But sometimes the database might return columns with
+ # characters that are not allowed in normal method names (like
+ # 'my_column(omg)'. So to work around this we first define with
+ # the __temp__ identifier, and then use alias method to rename
+ # it to what we want.
+ #
+ # We are also defining a constant to hold the frozen string of
+ # the attribute name. Using a constant means that we do not have
+ # to allocate an object on each call to the attribute method.
+ # Making it frozen means that it doesn't get duped when used to
+ # key the @attributes in read_attribute.
+ def self.define_attribute_accessor_method(mod, attr_name, writer: false)
+ method_name = "#{attr_name}#{'=' if writer}"
+ if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
+ yield method_name, "'#{attr_name}'.freeze"
+ else
+ safe_name = attr_name.unpack1("h*")
+ const_name = "ATTR_#{safe_name}"
+ const_set(const_name, attr_name) unless const_defined?(const_name)
+ temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
+ attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
+ yield temp_method_name, attr_name_expr
+ mod.send(:alias_method, method_name, temp_method_name)
+ mod.send(:undef_method, temp_method_name)
+ end
+ end
+ end
end
end