Speed up method response times on results from custom aliased ActiveRecord queries.
Just add the gem to your Gemfile:
gem 'respond_to_faster', '~> 0.2.0'
That's it! Read on to learn about what RespondToFaster is doing under the hood (or checkout the source code, it's only a dozen lines long!)
Suppose you have a query with some custom SQL, like this (taken from the ActiveRecord Querying documentation):
Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)")
This query will group orders by date, with each date result responding to the
total_price. So if
order is the first result
returned, this will work:
order.ordered_date #=> Thu, 14 Dec 2017 order.total_price #=> 20.98
This is nice, but are those really methods? Let's have a look:
order.method(:ordered_date) #=> NameError: undefined method `ordered_date' for class `#<Class:0x00559df3a8ef30>'
That's strange! No method. So how is the object responding to the
As usual, Rails is doing some magic under the hood. You can find that magic
documented in the inline
ActiveModel::AttributeMethods, where there is the somewhat cryptic message:
Allows access to the object attributes, which are held in the hash returned by attributes, as though they were first-class methods. So a Person class with a name attribute can for example use Person#name and Person#name= and never directly use the attributes hash -- except for multiple assignments with ActiveRecord::Base#attributes=.
This inline comment dates back to the very first Rails commit by @dhh in 2004.
What the code (now in ActiveModel, previously in ActiveRecord) actually does is
method_missing to check if a given method call
matches a key in the
attributes hash of the model. If there's a match,
ActiveModel "dispatches" to an attribute handler, which returns the result.
Which is all fine and good, but method_missing is slow as molasses. You never really want to be relying on it to return results unless you have no alternative.
What this gem does
The reality is that in the vast majority of cases, you don't need this
method_missing override. AR has grown over the years to the point where most
attribute methods are defined, so these fallbacks are not necessary.
But there is one case where you do: so-called "virtual columns". This is the type of query mentioned at the top of this readme. In this case, depending on the query, some custom attributes are needed on the objects returned, and ActiveRecord still relies on this (very slow) mechanism to make the magic work.
But this is a high price to pay for some simple magic. This gem instead
defines the methods on a module included into the singleton classes of the
objects returned, so that you never need to go to
method_missing. This makes
calling these accessors things significantly faster, as much as 5-10 times
If you don't use any custom querying with aliases like the one above, this gem will not do much for you.
However, if you've got some crazy heavy SQL logic somewhere deep in your application, and you're finding it takes forever, give this gem a shot and tell me what you find! I'd like to get this eventually merged into ActiveRecord so I'd like to know about any issues you encounter in your application.
Bug reports and pull requests are welcome on GitHub at https://github.com/shioyama/respond_to_faster. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the RespondToFaster project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.