How do I cache a complex JSON response (with a lot of belongs_to associations) to be used by Vue.js in the frontend?
Thanks to Chris nice tutorials, we just rewrote a slow interface in our app to use Vue.js. Now Rails is just generating the JSON to be consumed by Vue.js in the view.
What I'm trying to achieve is to generate a cached JSON response using read_multi
or fetch_multi
, so all Conversations
(there are 20 on each pagination) are loaded on a single call to memcached.
Le'ts say I have a model called Conversation, that belongs to an user, to a tourist, to a property, and it also has many replies.
class Conversation
belongs_to :user
belongs_to :tourist
belongs_to :property
has_many :replies
has_many :holidays
def generate_json
{
tourist: {
name: tourist.name,
email: tourist.email,
property: {
id: property.id,
image_url: property.pictures.first.try(:image).try(:url,:thumb),
property_code: property.property_code,
title: property.title_br,
},
recent_replies: replies.last(5).map(&:generate_json)
}
end
end
For the has_many
associations, I simply use touch: true
in the other side of the association, so model Reply
is a belongs_to :conversation, touch: true
, which takes care of updating Conversation's updated_at
if it changes and the cache is automatically updated.
But every time I try to cache a model like this (wether in a view fragment or, in this case, in a JSON response) I end up with a huge and complex cache key due, mainly, to the belongs_to
association on the cached model. Something like this for example:
Rails.cache.fetch(self.cache_key, self.user.cache_key, self.tourist.cache_key, self.property.cache_key) do
... generates the JSON for this model
end
Justin Weiss has a nice tutorial (altough a bit complex) of how to cache complicated JSON responses (https://www.justinweiss.com/articles/a-faster-way-to-cache-complicated-data-models/) but as far as I could understand he's using manually expiring cache keys.
I'm a little bit lost at this point. How would you guys implement caching for this type of JSON structure?
One idea is to use just the self.cache_key
in the cache key for the Conversation
, (1) rely on touch: true
for the has_many
associations and (2) write manual after_commit :touch_conversations
for the belongs_to
associations, something like this:
model Tourist
has_many :conversations
after_commit :touch_conversations
def touch_conversations
self.conversations.update_all(updated_at: Time.now)
end
end
The problem with this approach is that some users already got + 200k conversations for example. Updating them all takes at least 4 seconds in my tests, and I'm afraid interfaces will start to get sluggish over time.