How can I cache the JSON representation of an object?

In the JSON view of my Car model, I include the output of an expensive method:

 #car.rb def as_json(options={}) super(options.merge(methods: [:some_expensive_method])) end 

I have a standard index action:

 #cars_controller.rb respond_to :json def index respond_with(Car.all) end 

I also use JSON representations for cars in other places, for example:

 #user_feed.rb def feed_contents Horse.all + Car.all end #user_feeds_controller.rb respond_to :json def index respond_with(UserFeed.feed_contents) end 

Since the JSON Car view is used in several places, I want it to be cached on its own, using car.cache_key as an expiration key.

Here's how I do it now:

 #car.rb def as_json(options={}) Rails.cache.fetch("#{cache_key}/as_json") do super(options.merge(methods: [:some_expensive_method])) end end 

Entering the cache code inside as_json incorrect because caching is not part of as_json reponsibility. What is the right way to do this? I am using Rails 3.2.15.

+8
json caching ruby-on-rails
source share
3 answers

Let me first say that I welcome the efforts of this issue. It will be difficult for you to find someone who is more interested in developing software in the right way - including using small methods that do one thing at the same level of abstraction than I do.

However, I must say that on this I actually object to the premise of the question. I think your as_json best as it is.

Most importantly, this is the decoupling of customers from implementations. The only thing Car#as_json clients should know is that the return value is a JSON representation for Car . And as_json does it and does it well. Caching and / or fetching is a part of the implementation that must remain inside the method, and it is a part that is an integral part of this one task.

Saying differently would be if any method with an if was "wrong" because it does two things. Of course, this is not so. In both cases (using if and using Rails.cache.fetch ), the implementation of the method is some atomic action, the result of which is based on the condition.

This is one thing that can go in one of two ways, which is not the same as two things.

Meanwhile, I would have to disagree with @severin's answer. Of course, this will work, but now you have connected your opinion with the implementation details. Nothing, and, of course, your opinion, should have any idea of โ€‹โ€‹the caching method or even that caching is involved. In my opinion, you have already skipped abstraction with this approach. Maybe it doesnโ€™t matter, but since we are talking about the โ€œright wayโ€ to do something ...

So, I say, keep things as they are. But I think this is a big question.

+2
source share

I always add caching to the as_json method (and this pearl is active_model_serializer ), but your comment on this was not correct, I thought, and I can understand your problems.

So, I looked at the respond_with documentation (see enter link description here ) and I found this:

If an acceptable format is not identified, the application returns status 406 - unacceptable. Otherwise, the default response should display a template named after the current action and the selected format, for example. index.html.erb. If the template is not available, the behavior depends on the selected format ...

So, you can create a json template for the actions involved, and then do the caching in this view. Something like the following should work:

 # app/views/cars/index.json.erb [<%= @cars.map {|car| render partial: 'cars/car.json', locals: {car: car}}.join(',') %>] # app/views/cars/show.json.erb <%= render partial: 'cars/car.json', locals: {car: @car} %> # more templates for other actions... # app/views/cars/_car.json.erb <%= Rails.cache.fetch("#{car.cache_key}/as_json") { car.as_json } %> 

You can probably clear the render partial: ... calls a bit, and then you will have a good solution with caching processed in the view where it belongs.

+2
source share

I'm not sure that I will follow him, but I think I would use a serialized field for him

http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Saving+arrays%2C+hashes%2C+and+other+non-mappable+objects+in+text+columns

and before_save callback to update this field after changing the rail model.

 class Car serialize :serialized_car, Hash before_save :generate_json_representation def generate_json_representation self.serialized_car = ... end def as_json(options={}) super(options.merge(methods: [:serialized_car])) end end 
0
source share

All Articles