📜 ⬆️ ⬇️

Rails 4 - Cache Digests



The heme called " cache_digests " (enabled by default in Rails 4) automatically adds a digital signature to each fragment cache, based on the view (view). In this case, if the page is changed, then the old cache is automatically deleted. But beware of the pitfalls!



The content of the cycle "The Subtleties of Rails 4"



I wrote a small application in which there is a list with projects, each of which has a specific list of tasks. Suppose that in this application there are problems with performance and to fix them, it was decided to use fragment caching.


')
The following code displays a list of projects:

/app/views/projects/index.html.erb
<h1>Projects</h1> <%= render @projects %> 

For each project, a partial _project is generated. It is also very simple and deals with the display of a list of tasks:

/app/views/projects/_project.html.erb
 <h2><%= link_to project.name, edit_project_path(project) %></h2> <ul><%= render project.tasks %></ul> 

In turn, _project renders another partial: _task . So, let's add fragmentary caching for _project .

/app/views/projects/_project.html.erb
 <% cache project do %> <h2><%= link_to project.name, edit_project_path(project) %></h2> <ul><%= render project.tasks %></ul> <% end %> 

Since the above code displays a list of tasks, it would be wise to stop caching old data when a new task appears. This goal can be achieved by adding a touch: true connection to the Task model:

/app/models/task.rb
 class Task < ActiveRecord::Base attr_accessible :name, :completed_at belongs_to :project, touch: true end 

Now when you change the project task, it will be marked as updated. Check the caching operation in development mode:

/config/development.rb
 config.action_controller.perform_caching = true 

After restarting the server and refreshing the page, each project will be cached using fragment caching. At the same time, if one of the tasks is edited, the cache will expire and thus new data will be loaded.

All this is great, but what happens if changes are made to the code of the page itself? For example, I updated the code to display tasks in a numbered list:

/app/views/projects/_project.html.erb
 <% cache project do %> <h2><%= link_to project.name, edit_project_path(project) %></h2> <ol><%= render project.tasks %></ol> <% end %> 

Now refresh the page in the browser. No visible changes have occurred! This happened due to the fact that the page with the old code has already been cached, and its validity has not expired. Therefore, the old content is still visible. This problem is usually avoided by updating the cache key version:

/app/views/projects/_project.html.erb
 <% cache ['v1', project] do %> <h2><%= link_to project.name, edit_project_path(project) %></h2> <ol><%= render project.tasks %></ol> <% end %> 

Since the key value was changed, the old cache became invalid and the page displays tasks with a numbered list. Hooray!



But there is some problem. You need to constantly remember that with each change of the page code it is also necessary to change the version number of the cache in order for the new changes to take effect. In principle, this is not difficult, but it all instantly becomes more complicated if nested fragment caching is used. Suppose I also want to cache the partial with tasks in order to further increase performance:

/app/views/tasks/_task.html.erb
 <% cache ['v1', task] do %> <li> <%= task.name %> <%= link_to "edit", edit_task_path(task) %> </li> <% end %> 

Now you also need to update the cache key in the project part in order for the entire old cache to disappear.

That is, for example, if we update a partial with a task by changing the name of the link from “edit” to “rename”, then it is obvious that it is necessary to change its cache key. But there will be no visible changes on the page until the key value in the project part also changes. And only after that we will see our long-awaited innovations:



Cache digests


Yes, this caching works, but, you see, it is terrible. And then a gem called “cache_digests” comes to our rescue! Its functionality is included in Rails 4, but it was also allocated with a separate gem so that developers can use it today in projects with Rails 3.

This gem includes a digital signature in the fragment cache based on views. This means that changes in the page code will also change the cache key, thus clearing the old one.

Let's try out his work. For this you need to include the following line in the gemfile:

/ Gemfile
 gem 'cache_digests' 

And then:
 $ bundle install 

Now there is no more need to specify the key version, and therefore it is possible with a clear conscience to remove the extra code from the _project and _task partials . After that, you need to restart the server and refresh the page in the browser for the new caching to take effect.

If this is not done, and at the same time try to slightly change the code of the project’s view and update the page, the changes will not occur. The reason lies in the fact that the cache digest gem does not analyze changes in views with each change of code, because it is extremely unwise. Instead, it stores its local cache for each submission, and each assigns a unique digital signature to each other.

To see the changes in the development mode, you need to restart our application server. Such problems should not appear in production, since with each new deployment the server is still restarted.

Now, having updated the page, it will be seen that the updates in the code did not go unnoticed by the heme, and finally an updated page appeared before us. By the way, heme is smart enough and can determine dependencies. Well, for example, we still remember that the presentation with projects calls the render method to display the list of tasks. Therefore, it is obvious that if the task partial has suddenly changed, then it becomes necessary to delete the old cache in the project view.

Underwater rocks


But you should not relax much, because there are cases in which the dependencies will not be correctly defined. Consider a small example.

For example, in the Project model, there is an incomplete_tasks method. And I decided to use this method to display unfinished tasks in the partial (responsible for displaying the list of projects). If you do this, you will see that the changes in the view were not displayed, because the dependencies were not correctly identified. Perhaps a good idea in this case would be to start the rake task cache_digests: nested_dependencies , so kindly provided by the heme.

 $ rake cache_digests:nested_dependencies TEMPLATE=projects/index [ { "projects/project": [ "incomplete_tasks/incomplete_task" ] } ] 

As you can see, from the above code, the path to the necessary view for analyzing the problem that occurred is transferred.

The conclusion of the rake task shows that a dependency was found in the project partial (which is good), but it was also incorrectly defined: the task must be in the place of incomplete_task . In order to fix this unpleasant incident, I recommend using the following code (note that I specify partial and use collection ):

/app/views/projects/_project.html.erb
 <% cache project do %> <h2><%= link_to project.name, edit_project_path(project) %></h2> <ul><%= render partial: 'tasks/task', collection: project.incomplete_tasks %></ul> <% end %> 

Running the same rake task again will show that the dependencies are now properly defined and the cache has been successfully updated!

 $ rake cache_digests:nested_dependencies TEMPLATE=projects/index [ { "projects/project": [ "tasks/task" ] } ] 

More details about the work of the heme can be found in its README , which I recommend to do to all who are interested. Thanks for attention!

All errors, inaccuracies of translation and other such things, please report in a personal.



application
The source code of the application from the lesson


Subscribe to my blog !

Source: https://habr.com/ru/post/165355/


All Articles