Confusion caching Active Record queries with Rails.cache.fetch

My version is:

  • Rails: 3.2.6
  • dalli: 2.1.0

My env is:

  • config.action_controller.perform_caching = true
  • config.cache_store = :dalli_store, 'localhost:11211', {:namespace => 'MyNameSpace'}

When I write:

 Rails.cache.fetch(key) do
     User.where('status = 1').limit(1000)
 end

The user model can't be cached. If I use

 Rails.cache.fetch(key) do
     User.all
 end

it can be cached. How to cache query result?


Solution 1:

The reason is because

User.where('status = 1').limit(1000)

returns an ActiveRecord::Relation which is actually a scope, not a query. Rails caches the scope.

If you want to cache the query, you need to use a query method at the end, such as #all.

Rails.cache.fetch(key) do
  User.where('status = 1').limit(1000).all
end

Please note that it's never a good idea to cache ActiveRecord objects. Caching an object may result in inconsistent states and values. You should always cache primitive objects, when applicable. In this case, consider to cache the ids.

ids = Rails.cache.fetch(key) do
  User.where('status = 1').limit(1000).pluck(:id)
end
User.find(ids)

You may argue that in this case a call to User.find it's always executed. It's true, but the query using primary key is fast and you get around the problem I described before. Moreover, caching active record objects can be expensive and you might quickly end up filling all Memcached memory with just one single cache entry. Caching ids will prevent this problem as well.

Solution 2:

In addition to selected answer: for Rails 4+ you should use load instead of all for getting the result of scope.