In my app, I have a large complex model with many associations. I need to serve it as json. The model's size and complexity, however, mean that assembling the json version is costly both in terms of CPU and RAM. Fortunately, since the model is not frequently updated it's a good candidate for caching. I'd like to keep a cached version of the model's json string on a static file hosting service (e.g. S3), handled by activestorage.
The code below summarizes what's I've done so far. In the controller, incoming GET requests for the model are redirected to the cached version on activestorage. The model keeps the cached version up-to-date using an after_save
callback.
class ComplicatedModelController < ApplicationController
def show
@complicated_model = ComplicatedModel.find(params[:id])
redirect_to @complicated_model.activestorage_cached_json_response.url
end
class ComplicatedModel < ActiveRecord::Base
has_one_attached :activestorage_cached_json_response
after_save do
# updated cached version - how can I do this WITHOUT retriggering the aftersave callback?
self.activestorage_cached_json_response.attach(
io: StringIO.new(self.as_json), # this step is costly
filename: "ComplicatedModel_#{self.id}.json",
content_type: 'application/json'
)
end
end
The problem is that updating the activestorage attachment within the aftersave callback creates an infinite loop: this step saves the model again, and so retriggers the callback. To make this strategy work, I'd need to update the activestorage attachment WITHOUT triggering the aftersave callback. Is this possible?
Ah, I figured it out. I just needed to use an :unless after the callback, as is explained here: https://stackoverflow.com/a/7386222/765287
Working code is below.
class ComplicatedModel < ActiveRecord::Base
attr_accessor :skip_aftersave_callback
has_one_attached :activestorage_cached_json_response
after_save :update_cache, unless: :skip_aftersave_callback # change here
def update_cache
# update_cache is always called in the after_save callback;
# this flag stops the callback after the .attach below
self.skip_aftersave_callback = true
self.activestorage_cached_json_response.attach(
io: StringIO.new(self.as_json), # this step is costly
filename: "ComplicatedModel_#{self.id}.json",
content_type: 'application/json'
)
end
end