RespondToFaster :rocket:

Gem Version Build Status Dependency Status

Speed up method response times on results from custom aliased ActiveRecord queries.

Usage

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!)

Background

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 aliases ordered_date and 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 ordered_date message?

As usual, Rails is doing some magic under the hood. You can find that magic documented in the inline docs for 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 to override respond_to? and 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 faster.

Caveats

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.

Contributing

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.

License

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.