aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/attribute_methods.rb
diff options
context:
space:
mode:
authorschneems <richard.schneeman+foo@gmail.com>2018-10-11 17:07:28 -0500
committerschneems <richard.schneeman+foo@gmail.com>2018-10-15 15:31:32 -0500
commitf45267bc423017109e442e5c35a5765dc482b12b (patch)
treeb20711429044a4221710ade1561cabc7bb82f1f9 /activerecord/lib/active_record/attribute_methods.rb
parent134dab46e4e94d7e6e37cec43dca8183fe72aea6 (diff)
downloadrails-f45267bc423017109e442e5c35a5765dc482b12b.tar.gz
rails-f45267bc423017109e442e5c35a5765dc482b12b.tar.bz2
rails-f45267bc423017109e442e5c35a5765dc482b12b.zip
ActiveRecord#respond_to? No longer allocates strings
This is an alternative to https://github.com/rails/rails/pull/34195 The active record `respond_to?` method needs to do two things if `super` does not say that the method exists. It has to see if the "name" being passed in represents a column in the table. If it does then it needs to pass it to `has_attribute?` to see if the key exists in the current object. The reason why this is slow is that `has_attribute?` needs a string and most (almost all) objects passed in are symbols. The only time we need to allocate a string in this method is if the column does exist in the database, and since these are a limited number of strings (since column names are a finite set) then we can pre-generate all of them and use the same string. We generate a list hash of column names and convert them to symbols, and store the value as the string name. This allows us to both check if the "name" exists as a column, but also provides us with a string object we can use for the `has_attribute?` call. I then ran the test suite and found there was only one case where we're intentionally passing in a string and changed it to a symbol. (However there are tests where we are using a string key, but they don't ship with rails). As re-written this method should never allocate unless the user passes in a string key, which is fairly uncommon with `respond_to?`. This also eliminates the need to special case every common item that might come through the method via the `case` that was originally added in https://github.com/rails/rails/commit/f80aa5994603e684e3fecd3f53bfbf242c73a107 (by me) and then with an attempt to extend in https://github.com/rails/rails/pull/34195. As a bonus this reduces 6,300 comparisons (in the CodeTriage app homepage) to 450 as we also no longer need to loop through the column array to check for an `include?`.
Diffstat (limited to 'activerecord/lib/active_record/attribute_methods.rb')
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb15
1 files changed, 4 insertions, 11 deletions
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 3c785180e1..eeb88e4b7a 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -261,21 +261,14 @@ module ActiveRecord
def respond_to?(name, include_private = false)
return false unless super
- case name
- when :to_partial_path
- name = "to_partial_path"
- when :to_model
- name = "to_model"
- else
- name = name.to_s
- end
-
# If the result is true then check for the select case.
# For queries selecting a subset of columns, return false for unselected columns.
# We check defined?(@attributes) not to issue warnings if called on objects that
# have been allocated but not yet initialized.
- if defined?(@attributes) && self.class.column_names.include?(name)
- return has_attribute?(name)
+ if defined?(@attributes)
+ if name = self.class.symbol_column_to_string(name.to_sym)
+ return has_attribute?(name)
+ end
end
true