rails4 caching naming conventions - ruby-on-rails

I have a rails 4 app. I have to differentiate the different cache keys somehow but don't know the naming conventions.
FIRST EXAMPLE:
I have a task model with index, completed_tasks and incoming_tasks actions. A have the same instance name (#tasks) though because of the pagination.
At the moment the cache-keys are named like below. My questions: 1. Is the cache-key structure good enough? 2. Is it important in which order I put the parts of the key in the array? For example [#tasks.map(&:id), #tasks.map(&:updated_at).max, 'completed-tasks'] is better than ['completed-tasks', #tasks.map(&:id), #tasks.map(&:updated_at).max]?
completed_tasks.html.erb
<% cache([#tasks.map(&:id), #tasks.map(&:updated_at).max, 'completed-tasks']) do %>
<%= render #tasks %>
<% end %>
tasks.html.erb
<% cache([#tasks.map(&:id), #tasks.map(&:updated_at).max]) do %>
<%= render #tasks %>
<% end %>
incoming_tasks.html.erb
<% cache([#tasks.map(&:id), #tasks.map(&:updated_at).max, 'incoming-tasks']) do %>
<%= render #tasks %>
<% end %>
SECOND EXAMPLE:
I also have problem with the naming conventions of the russian-doll-caching:
products/index.html.erb
<% cache([#products.map(&:id), #products.map(&:updated_at).max]) do %>
<%= render #products %>
<% end %>
_product.html.erb
<% cache(product) do %>
<%= product.name %>
....
<% end %>
Is this version good enough or I always should put some string in both the outer and inner caching key array to avoid problems with similarly named cache-keys on other pages. For instance I plan to put <% cache(#product) do %> on the profile#show page which would be exactly the same like the inner caching in my example. If the key has to be different what the rails convention is to name the inner an outer cache keys?

First, according to Russian Doll Caching article, I think it's not necessary to set the cache_key on your own, you could just leave it to rails, it generates cache_key auto. For example, the cache_key of #tasks = Task.incoming should differ from #tasks = Task.completed with something like views/task/1-20160330214154/task/2-20160330214154/d5f56b3fdb0dbaf184cc7ff72208195e and views/task/3-20160330214154/task/4-20160330214154/84cc7ff72208195ed5f56b3fdb0dbaf1
cache [#tasks, 'incoming_tasks'] do
...
end
Second, As for the namespace, though the template digest will be the same but the #tasks digest will be different. So it seems to be okay without namespace in this case.
cache #tasks do
...
end
Third, when it comes to namespace, I prefer prefix rather than suffix. i.e.
cache ['incoming_tasks', #tasks] do
...
end
As the second example, I think this would do just fine.
<% cache #product do # first layer cache %>
<% cache #products do # second layer cache %>
<%= render #products %>
<% end %>
<% cache #product do # second layer cache %>
<%= product.name %>
....
<% end %>
<% end %>
The caching key for app/views/products/show.html.erb will be something like views/product/123-20160310191209/707c67b2d9fb66ab41d93cb120b61f46. That last bit is a MD5 of the template file itself and all of its dependencies. It'll change if you change either the template or any of the dependencies, and thus allow the cache to expire automatically.
For further reading: https://github.com/rails/cache_digests

It's best practice to always put a string at the end. It really just needs to be something that makes sense to you.

Related

Does the `cache do` block belong in the calling template or the called template?

What is the convention for where the cache do block goes?
Does it belong in "calling" template or the "inner" template that the "outer" template renders?
In some examples, I see the responsibility for caching a template B going in the "parent" or "calling" template, A—the template that renders template B. For example, https://blog.appsignal.com/2018/04/03/russian-doll-caching-in-rails.html says:
On the product index, we’ve wrapped each product partial in a cache block.
and shows this example:
# app/views/products/index.html.erb
<h1>Products</h1>
<% #products.each do |product| %>
<% cache product do %>
<%= render product %>
<% end %>
<% end %>
This identical example can also be found at https://edgeguides.rubyonrails.org/caching_with_rails.html#fragment-caching
But isn't that responsibility in the wrong place? That puts the burden on the user of the template rather than on the template itself (to cache itself). Which means that you have to remember, every time you render the product template, to wrap it in a cache block if you want it to be cached. This leads to duplicated code ("not DRY").
Wouldn't it be better, then, to put it in the template itself? Something like this, perhaps:
# app/views/products/_product.html.erb
<% cache product do %>
<article>
<h1><%= product.title %></h1>
…
</article>
<% end %>
There appears to be an examples of a template wrapping its own content in a cache block like this later on in the guide, at https://edgeguides.rubyonrails.org/caching_with_rails.html#russian-doll-caching:
For example, take the following view:
<% cache product do %>
<%= render product.games %>
<% end %>
Which in turn renders this view:
<% cache game do %>
<%= render game %>
<% end %>
but it's not 100% clear in precisely which files those templates actually belong. What file names would those views conventionally live in?
I can easily imagine/assume that the 1º template would live in either app/views/products/show.html.erb or app/views/products/_product.html.erb depending on whether it's an action view or a partial (it doesn't really matter which it is for the purposes of this discussion):
# app/views/products/show.html.erb ?
# app/views/products/_product.html.erb ?
<% cache product do %>
<%= render product.games %>
<% end %>
Where I am actually confused is the 2º template:
# app/views/games/_game.html.erb ?
<% cache game do %>
<%= render game %>
<% end %>
From context, I think we know this is a partial for a game (rendered once for each game in the collection passed in the _product template), so I guess it would live at app/views/games/_game.html.erb by convention.
But if that is the case, which is the 3º template that it is rendering here with render game?? It looks like it is rendering a partial for game — but aren't we already inside of the partial for game? Wouldn't this result in infinite recursion (games/_game.html.erb rendering itself recursively)?
Am I missing something obvious? Or is this just a poor example that needs to be updated in the guide? If so, what would an improved example look like?

rails 4 fragment caching for different views

In my rails 4 app I'm trying to take off with caching, but I'm a bit confused thanks to the different versions of cache-key-settings, cache helpers and auto-expiration.
So let me ask this through few examples. I don't move the examples to different questions on purpose since I feel this way anybody can understand the subtle differences at one glance.
1: latest users in sidebar
I'd like to display the latest users. This of course is the same for all the users in the app and displayed on all the pages. In the railscasts I saw a similar example where it got expired by calling expire_fragment... in a controller. But according to other resources this should expire automatically when something changes (e.g. new user registration). So my question: Am I setting the key properly and will it auto-expire?
_sidebar.html.erb (displayed on all pages in sidebar)
<% cache 'latest-users' %>
<%= render 'users/user_sidebar' %>
<% end %>
_users_sidebar.html.erb
<% #profiles_sidebar.each do |profile| %>
<%= profile.full_name %>
........
<% end %>
2: product show page
I'd like to display a given product (only on show page). This is the same again for all the users, but there are more versions since there are more products. The question is the same again: Am I setting the key properly and will it auto-expire?
products/show.html.erb
<% cache #product %>
<%= #product.name.upcase %>
<%= #product.user.full_name %>
<% end %>
3: products/index (paginated with will-paginate gem)
Here I'd like to cache all the products on a given page of the pagination at once, so products get cached in blocks. This is also the same for all the users, and only gets displayed on the products index page. (Later on I'd like to implement the russian-doll-caching for the individual products on this page.) My question: Am I doing this right and will it auto-expire?
products index.html.erb
<% cache [#products, params[:page]] %>
<%= render #products %>
<% end %>
_product.html.erb
<%= product.name %>
<%= product.user.full_name %>
.....
Example code I tried to use (not sure if it's a good one):
First with index page and with no russian doll.
Second is with russian doll for the show page with comments.
There is a pretty big difference between caching a single record and a collection of records.
You can quite simply tell if a single record has been changed by looking at the timestamp. The default cache_key method works like this:
Product.new.cache_key # => "products/new" - never expires
Product.find(5).cache_key # => "products/5" (updated_at not available)
Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
However telling when a collection is stale depends very much on how it is used.
In your first example you only really care about the created_at timestamp - in other situations you might want to look at when records are updated or even when associated records have been inserted / updated. There is no right answer here.
1.
First you would pull N number of users ordered by created_at:
#noobs = User.order(created_at: :desc).limit(10)
We can simply invalidate the cache here by looking at the first user in the collection.
<!-- _sidebar.html.erb -->
<% cache(#noobs.first) do %>
<%= render partial: 'users/profile', collection: #noobs %>
<% end %>
We can do this since we know that if a new user is registered (s)he will bump the previous number one down a slot.
We can then cache each individual user with russian doll caching. Notice that since the partial is called profile the partial gets passed the local variable profile:
<!-- users/_profile.html.erb -->
<% cache(profile) do %>
<%= profile.full_name %>
<% end %>
2.
Yes. It will expire. You might want to a partial with russian doll caching like the other examples.
3.
For a paginated collection you can use the ids of the records in the array to create a cache key.
Edited.
Since you don't want serve a stale representation of a product that may be updated you would also want to use updated_at as a cache key in the "outer layer" of the russian doll.
In this case it makes sense to load the records entirely. You can ignore my previous comment about .ids.
products/index.html.erb:
<% cache([#products.map(&:id), #products.map(&:updated_at).max]) do %>
<%= render #products %>
<% end %>
products/_product.html.erb:
<% cache(product) do %>
<%= product.name %>
<% end %>
IMHO, cache key is the spirit of the whole concept.
Now let's discuss these examples.
latest users in sidebar: fixed string as cache key
cache_key could be views/latest-users/7a1156131a6928cb0026877f8b749ac9 which the digest 7a11561.. is MD5 of cache block literal.
In this case, the cache only expires when you change the template or anything in this block.
<% cache 'latest-users' do %>
<%= render 'users/user_sidebar' %>
<% end %>
product show page: object as cache key
cache_key could be views/product/123-20160330214154/9be3eace37fa886b0816f107b581ed67, notice the cache is namespace with #{product.to_s}/#{product.id}-#{product.updated_at}.
In this case, the cache expires when (1) product.updated_at changed or (2) cache block literal changed.
And notice the cache differs from different product by id.
<% cache #product %>
<%= #product.name.upcase %>
<%= #product.user.full_name %>
<% end %>
products/index (paginated with will-paginate gem): array as cache key
cache_key could be views/product/123-20160330214154/product/456-20160330214154/d5f56b3fdb0dbaf184cc7ff72208195e not sure about this. but anyway, it should be expand something like this.
In this case, the cache expires when (1) either product-123 or product-456 changed(product.updated_at changed) or (2) cache block literal changed.
And the cache differs from different content of #products by their ids, so there's no need to append params[:page], it will cache each different page because of their different #products content.
<% cache [#products, params[:page]] %>
<%= render #products %>
<% end %>
You could read the article written by DHH, it described Russian Doll Caching very well. And the API doc

Correct way of rails caching complex query

I am new to caching and I'm not sure what my best course of action is.
I want to cache a part of my view that relies on a complex query. The query looks something like:
#sessions_next_week = group_by_wday(LittleClassSession.location_only([1,2]).age_range_only(age_from, age_to).supports_dropins_only(support).approved_users_only.next_week)
Above you'll see a number of scopes and methods called. The view renders an instance variable named #sessions_next_week like so:
<% #sessions_next_week.each do |wday, lcs| %>
<h3><%= wday %></h3>
<%= render partial: 'table_head' %>
<% lcs.each do |s| %>
<%= render partial: 'table_row', :locals => {:s => s, :show_day => true} %>
<% end %>
<%= render partial: 'table_foot' %>
<% end %>
As you can see, #sessions_next_week is iterated through, and its children are iterated through. Given this, and given the nature of the query results in the instance variable, I'm not sure where to implement the caching. In the model? In the view?
So my questions are:
Do I need model caching or can I do this in the view?
What's the correct implementation?
The solution is to simply add two character:
#sessions_next_week ||= group_by_wday(LittleClassSession.location_only([1,2]).age_range_only(age_from, age_to).supports_dropins_only(support).approved_users_only.next_week)
This is called memoization, and you can look it up. Here's one source: http://www.justinweiss.com/articles/4-simple-memoization-patterns-in-ruby-and-one-gem/

will_paginate problem with action and fragment caching

i have added action caching to my index part and listing 9 records in a page
will_paginate dosent work, and renders same set of recors again and again.
and same issue with fragment caching
please suggest some solution for this..
thanks
try this: Rails action caching with querystring parameters
Unless you have a very basic Rails app, I would recommend using fragment caching as it's a much easier to use and maintain compared to action/page caching. If you decide to use fragment caching, you could add something like the following to your index template:
<% #models.each do |model| %>
<% cache ["v1", model] do %>
<%= render model %>
<% end %>
<% end %>
This will render the partial _model.html.erb and cache the result. For details about the cache mechanisms in Rails, I suggest reading Rails Guides.
A more aggressive caching strategy would be to cache all the models on a single page. That could be done by setting the current page to an instance variable in the controller:
def index
#page = params[:page]
#models = ...
end
Now in your template you can include the page in the composite cache key:
<% cache ["v1", cache_key_for(#models), #page] do %>
<% #models.each do |model| %>
<%= render model %>
<% end %>
<% end %>
<% end %>
cache_key_for is a helper that computes a cache key for a set of models. It could be defined as:
def cache_key_for(models)
"#{models.count}-{models.map(&:updated_at).max.utc.to_s(:number)}"
end

Multiple objects in a Rails form

I want to edit multiple items of my model photo in one form. I am unsure of how to correctly present and POST this with a form, as well as how to gather the items in the update action in the controller.
This is what I want:
<form>
<input name="photos[1][title]" value="Photo with id 1" />
<input name="photos[2][title]" value="Photo with id 2" />
<input name="photos[3][title]" value="Custom title" />
</form>
The parameters are just an example, like I stated above: I am not sure of the best way to POST these values in this form.
In the controller I want to something like this:
#photos = Photo.find( params[photos] )
#photos.each do |photo|
photo.update_attributes!(params[:photos][photo] )
end
In Rails 4, just this
<%= form_tag photos_update_path do %>
<% #photos.each do |photo| %>
<%= fields_for "photos[]", photo do |pf| %>
<%= pf.text_field :caption %>
... other photo fields
UPDATE: This answer applies to Rails 2, or if you have special constraints that require custom logic. The easy cases are well addressed using fields_for as discussed elsewhere.
Rails isn't going to help you out a lot to do this. It goes against the standard view conventions, so you'll have to do workarounds in the view, the controller, even the routes. That's no fun.
The key resources on dealing with multi-model forms the Rails way are Stephen Chu's params-foo series, or if you're on Rails 2.3, check out Nested Object Forms
It becomes much easier if you define some kind of singular resource that you are editing, like a Photoset. A Photoset could be a real, ActiveRecord type of model or it can just be a facade that accepts data and throws errors as if it were an ActiveRecord model.
Now you can write a view form somewhat like this:
<%= form_for :photoset do |f|%>
<% f.object.photos.each do |photo| %>
<%= f.fields_for photo do |photo_form| %>
<%= photo_form.text_field :caption %>
<%= photo_form.label :caption %>
<%= photo_form.file_field :attached %>
<% end %>
<% end %>
<% end %>
Your model should validate each child Photo that comes in and aggregate their errors. You may want to check out a good article on how to include Validations in any class. It could look something like this:
class Photoset
include ActiveRecord::Validations
attr_accessor :photos
validate :all_photos_okay
def all_photos_okay
photos.each do |photo|
errors.add photo.errors unless photo.valid?
end
end
def save
photos.all?(&:save)
end
def photos=(incoming_data)
incoming_data.each do |incoming|
if incoming.respond_to? :attributes
#photos << incoming unless #photos.include? incoming
else
if incoming[:id]
target = #photos.select { |t| t.id == incoming[:id] }
end
if target
target.attributes = incoming
else
#photos << Photo.new incoming
end
end
end
end
def photos
# your photo-find logic here
#photos || Photo.find :all
end
end
By using a facade model for the Photoset, you can keep your controller and view logic simple and straightforward, reserving the most complex code for a dedicated model. This code probably won't run out of the box, but hopefully it will give you some ideas and point you in the right direction to resolve your question.
Rails does have a way to do this - I don't know when it was introduced, but it's basically described here: http://guides.rubyonrails.org/form_helpers.html#using-form-helpers
It took a bit of fiddling to alter the configuration properly for the case where there's no parent object, but this seems to be correct (it's basically the same as gamov's answer, but cleaner and doesn't allow for "new" records mixed in with the "update" records):
<%= form_tag photos_update_path do %>
<% #photos.each do |photo| %>
<%= fields_for "photos[#{photo.id}]", photo do |pf| %>
<%= pf.text_field :caption %>
... [other fields]
<% end %>
<% end %>
<% end %>
In your controller, you'll end up with a hash in params[:photos], where the keys are photo IDs, and the values are attribute hashes.
You can use "model name[]" syntax to represent multiple objects.
In view, use "photo[]" as a model name.
<% form_for "photo[]", :url => photos_update_path do |f| %>
<% for #photo in #photos %>
<%= render :partial => "photo_form", :locals => {f => f} %>
<%= submit_tag "Save"%>
<% end %>
<% end %>
This will populate input fields just like you described.
In your controller, you can do bulk updates.
def update
Photo.update(params[:photo].keys, params[:photo].values)
...
end
Indeed, as Turadg mentioned, Rack (Rails 3.0.5) fails if you mix new & existing records in Glen's answer.
You can work around this by making fields_for work manually:
<%= form_tag photos_update_path do %>
<% #photos.each_with_index do |photo,i| %>
<%= fields_for 'photos[#{i}]', photo do |pf| %>
<%= pf.hidden_field :id %>
... [other photo fields]
<% end %>
<% end %>
This is pretty ugly if you ask me, but it's the only way I found to edit multiple records while mixing new and existing records.
The trick here is that instead of having an array of records, the params hash gets a array of hashes (numbered with i, 0,1,2, etc) AND the id in the record hash. Rails will update the existing records accordingly and create the new ones.
One more note: You still need to process the new and existing records in the controller separately (check if :id.present?)

Resources