I have an action in my Rails controller that accepts some parameters that are used for filtering and pagination. The response returned is only in JSON format and there is no view template.
I understand that caching can be done for the action and parameters using caches_action and cache_path. What I do not understand and have not been able to find is how to invalidate the cache for that action across all parameters. The cached action is similar to an "index" action and if there is a create/update/delete, the pagination is affected so the entire cache for that action must be removed.
This must be a fairly common problem, but I haven't been able to identify how it is solved.
The only way I could think of and from googling is to create custom cache keys for the cache_path attribute:
If looking up a single item, the custom cache key includes the updated_at date for that item.
If looking up a list of items, the custom cache key includes the count of items and the last updated_at date for the list of items.
The negative side-effect is that there will be database calls even if there is a cache hit.
The positive side-effect is that the cache is self-expiring since I am using memcached and memcached uses LRU replacement.
I get the feeling I am re-inventing the wheel but I cannot find solid documentation anywhere to solve this problem better.
Related
I have a Rails view that makes heavy use of scopes to filter down an Invoice table of hundreds of thousands of rows down to several thousand #invoices filtered records. #invoices object is an ActiveRecord relation.
Once the user presses a button in the view, these thousands of records need to be sent for processing to another controller / model.
Which would be the best way to accomplish this?
Passing the #invoices object as a param is not possible, so I can only think of two options:
1) passing an array of ids as parameters like this:
link_to bulk_process_path(#comprobantes.pluck(:id)), method: :post
but I'm worried I would hit the server's post max size if there a lot of records
2) passing the scopes involved in the original filtered view as parameters and recreating the filter in the target controller.
However this seems like unnecessary hits on the database. Furthermore, if I ever wanted to implement checkboxes to further refine the filtered view, then this method wouldn't work
3) Creating a temp table in the view, sending the name as a parameter and then reading it from the external controller? Then I'd have to keep track of and delete stale temp tables. Doesn't seem very elegant.
Maybe I'm missing something obvious but there doesn't seem to be an elegant solution.
Any help would be appreciated.
I can suggest another option.
When the user enters the page and starts filtering, you can save the filters on the session, then you do ajax requests on each checkbox changes and you can save those ids as exceptions when it's unchecked or remove the exception when it's checked.
You can even use websockets to make it more realtime.
You can also change the session storage method to ActiveRecordStore if you think the exceptions array can be too big, or use something like redis which is really fast.
That way, when the user has finished finteuning the filter, you do a post request but you don't need to send any params, everything is saved on the session. You then can exclude all the ids of unchecked and recreate the filter with the parameters.
Personally I think I would go this way. Hope this helps.
I want to implement low level cache on my application but I'm having some troubles following the documentation. This is what they have as example:
class Product < ActiveRecord::Base
def competing_price
Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do
Competitor::API.find_price(id)
end
end
end
My questions are:
How am I suppose to get that variable cache_key? Should be given somehow via rails cache or should I have my pre-builded key?
I'm not sure if I clearly understood how this works, confirm if this logic is correct: I set this for example on my controller to generate a ton of variables. And then every time a variable is requested (from view for example), the controller instead of recalculating it every time (long query) will retrieve the pre-made query in case the key haven't changed. If the key has changed it will recalculate again all variables inside the cache block.
ActiveRecord::Integration includes the cache_key method in Rails 4. It should be included by default in the standard Rails configuration. To test this, pop open console, get a record and call cache_key on it.
Reference on the method is available here.
It will usually generate a string similar to "#{record.class.name.underscore}/#{record.to_param}-#{record.updated_at}". This key-based invalidation approach lets you avoid a lot of the effort involved by simply looking for a cache value based on whenever the record was last updated. Old cache values will be ignored because they're not being retrieved.
DHH wrote a great article on the topic here.
I've taken the quote below, which I can see some sense in:
"Cached pages and fragments usually depend on model states. The cache doesn't care about which actions create, change or destroy the relevant model(s). So using a normal observer seems to be the best choice to me for expiring caches."
For example. I've got a resque worker that updates a model. I need a fragment cache to expire when a model is updated / created. This can't be done with a sweeper.
However, using an observer will mean I would need something like, either in the model or in the Resque job:
ActionController::Base.new.expire_fragment('foobar')
The model itself should not know about caching. Which will also break MVC principles that will lead to ugly ugly results down the road.
Use an ActiveRecord::Observer to watch for model changes. It can expire the cache.
You can auto-expire the cache by passing the model as an argument in your view template:
<% cache #model do %>
# your code here
<% end %>
What's happening behind the scenes is a cache named [model]/[id]-[updated_at] is created. Models have a method cache_key, which returns a string containing the model id and updated_at timestamp. When a model changes, the fragment's updated_at timestamp won't match and the cache will re-generate.
This is a much nicer approach and you don't have to worry about background workers or expiring the cache in your controllers/observers.
Ryan Bates also has a paid Railscast on the topic: Fragment Caching
A good and simple solution would be not to expire but to cache it with a key that will be different if the content is different. Here is an example
<% cache "post-#{#post.id}", #post.updated_at.to_i do %>
When that post gets updated or deleted and you fetch it again, it will miss the cache since the hash is different, so it will kind of expire and cache the new value. I think you can have some problems by doing this, for example if you are using the Rails default cache wich creates html files as cache, so you would end up with a lot of files in your public dir after some time, so you better set your application to use something like memcached, wich manages memory deleting old cached records/pages/parcials if needed to cache others or something like that.
I'd recommend reviewing this section on sweepers in the Rails Guide - Caching with Rails: An overview
http://guides.rubyonrails.org/caching_with_rails.html#sweepers
It looks like this can be done without specifically creating lots of cache expiration observers.
I'm not seeing something obvious re:caching strategies in rails.
I have a prices table that logs prices at particular times. So the current price would be the last row added to the prices table. Very frequently, the table is queried to display the entries, so caching that fetching would be great to stop (very) frequent queries hitting the database.
So as far as I can see it would be fine for my entire app to cache that data completely until a new row gets added.
Does rails caching handle that well? I see examples for on update of an active record object you expire the cache and force the updated object to be retrieved again - but I want the collection of objects (e.g. Price.find(:all) to be cached until Price.find(:all) contains a new object. So adding a new row to the db would have to expire the cache and force a new retrieval - the new price might be the latest for a few days, or it might only last a few minutes.)
If not self-evident, this is the first time I've ever looked at caching. I'll be attempting to deploy memcache on heroku.
Thanks a lot!
Edit: Just thought it might be useful to point out that the rails controllers only render JSON requests - rich single page app - so the main things to cache are the database query. This is why it is confusing me, I see partial caching, page caching, but I'm struggling to understand the type of caching I'm hopefully describing above.
Dave
To cache your prices, you can use the following. It would be helpful to place this somewhere it could be reused (such as your model).
cached_prices = Rails.cache.fetch("cached_prices", :expires_in => 300) do
Price.find(:all)
end
The above code caches the query for 5 minutes (300 seconds). In order to manually delete the cache entry (say when a new Price entry is created), call the following:
Rails.cache.delete("cached_prices")
To make this easier, you can place the cache delete code in an Observer on the Price model.
You can find much more information on all of the types of caching you have available to you here: http://guides.rubyonrails.org/caching_with_rails.html
Is there a technique that I can use in Rails so that whenever a simple "find" is performed on a Model object, memcached is first searched for the result, only if no result is found will a query by then made to the database?
Ideally, I'd like the solution to be implicit, so that I can just write Model.find(id), it first checks the cache and if a database query is required that the object returned is then added to the cache i.e. I don't need to wrap the Model.find(id) with additional code to check the cache for matching contents.
Thanks!
http://github.com/ngmoco/cache-money is the way to go