Low caching for collection

I want to use Redis to perform low level caching in my Rails application. In the controller, I usually do this to get all the books:

class BooksController < ApplicationController def index @books = Book.order(:title) end end 

And the review repeats over this:

 <ul> - @books.each do |book| <li>= "#{book.title} - #{book.author}"</li> </ul> 

Now I want to get the same result, but then cache. I have setup and running Redis. Therefore, I should use the cached_books method in the controller as follows:

 @books = Book.cached_books.order(:title) 

And leave the view as it is, or use book.cached_title and book.cached_author in the view and leave the controller as it is?

And what would the cached_books method look like in the Book model?

 class Book < ActiveRecord::Base ... def cached_books Rails.cache.fetch([self, "books"]) { books.to_a } end end 

For simplicity, I donโ€™t use expiration strategies right now, but obviously they should be there.

+5
source share
1 answer

So I have to use the cached_books method in the controller as follows:

Yes, you can. Although there are some mistakes you should be aware of. Book - ActiveRecord . When you call Book.something (e.g. Book.all or just even Book.order(:title) ), it returns ActiveRecord::Relation , which is basically a wrapper for the Book array (this wrapper prevents unnecessary queries from running, increasing performance) .

You cannot save the entire query result in Redis. Say you can save a JSON string of a hash array with model attributes, for example.

 [{ id: 1, title: 'How to make a sandwich", author: 'Mr. cooker' }, { id: 2, title: 'London Bridge', author: 'Fergie' }] 

And then you can "decrypt" this thing in the array after. Sort of

 def cached_books(key) # I suggest you to native wrapper if result = $redis.hget 'books_cache', key result.map do { |x| Book.new(x) } end end 

And you also have to serialize the attributes before putting them in the cache.

So now you have collections that can be repeated in a view with the same data, although you cannot call order in a cached collection, since it is a simple array (you can call sort , but the idea is to cache already sorted data).

Well ... is it worth it? In fact, not really. If you need to cache this part - perhaps the best way is to cache the displayed page, rather than the query result.

If you use cached_title and cached_author - this is a good question. First of all, it depends on what cached_title can be. If it is a line - there you cannot cache. You get a Book request through the database, or you get a Book from the cache - the title will be presented in some way, as this is a simple type. But let's take a closer look at author . Most likely, this will be related to another author model, and this is the place where the cache blends perfectly. You can override the author method inside the book (or define a new one and avoid the unpleasant effects that Rails may have in complex queries in the future) and see if there is a cache. If so, return the cache. If not, request the database, save the result in the cache and return it.

 def author Rails.cache.fetch("#{author_id}/info", expires_in: 12.hours) do # block executed if cache is not founded # it better to alias original method and call it here #instead of directly Author.find call though Author.find(author_id) end end 

Or less convenient, but more "safe":

 def cached_author Rails.cache.fetch("#{author_id}/info", expires_in: 12.hours) do author end end 
+5
source

All Articles