I want to use Redis to do some low level caching in my Rails app. In a controller I normally us this to get all books:
class BooksController < ApplicationController
def index
@books = Book.order(:title)
end
end
And the view iterates over this:
<ul>
- @books.each do |book|
<li>= "#{book.title} - #{book.author}"</li>
</ul>
Now I want the exact same result but then cached. I have Redis setup and running. So should I use a cached_books
method in the controller like this:
@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 how would a 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 sake I leave out expire strategies for now, but obviously they need to be there.
So should I use a cached_books method in the controller like this:
Yes, you can. Although there's some gotchas you have to be aware of. Book
is ActiveRecord
. When you call Book.something
(eg Book.all
, or just even Book.order(:title)
it returns you a ActiveRecord::Relation
, which is basically wrapper for array of Book
s (this wrapper prevents of firing unnecessary queries, boosting perfomance).
You can't save the whole result of a query in Redis. Say, you can save a JSON string of an array of hashes with model attributes, eg
[{
id: 1,
title: 'How to make a sandwich",
author: 'Mr. cooker'
}, {
id: 2,
title: 'London's Bridge',
author: 'Fergie'
}]
And then you can 'decrypt' this thing into the array after. Something like
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 also, you will have to serialise attributes before putting them into the cache.
Ok, now you have collections which can be iterated in the view with same data, although you can't call order
on a cached collection, as it is a plain array (you may call sort
, but the idea is to cache already sorted data).
Well... does it worth it? Actually, not really. If you need to cache this piece – probably, best way is to cache a rendered page, not a query result.
Should you use cached_title
and cached_author
– that's the good question. First of all, it depends on what cached_title
may be. If it is a string – there's nothing you can cache. You get a Book
through DB request, or you get Book
from cache – in any way the title
will be presented in it, as it is a simple type. But let's look closer to the author
. Most likely it is going to be a relation to another model Author
and this is the place where cache suits perfectly great. You can re-define author
method inside the book (or define new and avoid nasty effects Rails may have in complex queries in the future) and see if there's a cache. If yes, return the cache. If not – query the DB, save result to 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's 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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.