Subtleties of Rails 4 - Cache Digests
- Transfer
- Tutorial
- Recovery mode
A gem called " cache_digests " (included 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 changes, then the old cache is automatically deleted. But beware of the pitfalls!
Contents of the Rails 4 Subtlety Cycle
- Cache digests
- Turbolinks
- Thread safety
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 were performance problems 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
Projects
<%= render @projects %>
A partial _project is generated for each project . It is also quite simple and deals with displaying a list of tasks:
/app/views/projects/_project.html.erb
<%= link_to project.name, edit_project_path(project) %>
<%= render project.tasks %>
In turn, _project renders another partial: _task . So, add fragment caching for _project .
/app/views/projects/_project.html.erb
<% cache project do %>
<%= link_to project.name, edit_project_path(project) %>
<%= render project.tasks %>
<% 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 to the project model Task:
/app/models/task.rb
class Task < ActiveRecord::Base
attr_accessible :name, :completed_at
belongs_to :project, touch: true
end
Now, when a project task changes, it will be marked as updated. Check the caching in development mode:
/config/development.rb
config.action_controller.perform_caching = true
After server restart and page refresh, each of the projects will be cached using fragment caching. At the same time, if one of the tasks is edited, the cache will expire and new data will be loaded in this way.
All this is great, but what happens if the changes are made to the code of the page itself? For example, I updated the code to display tasks in the form of a numbered list:
/app/views/projects/_project.html.erb
<% cache project do %>
<%= link_to project.name, edit_project_path(project) %>
<%= render project.tasks %>
<% 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 was already stored in the cache, and its validity period has not yet expired. Therefore, the old content is still visible. This problem is usually circumvented by updating the cache key version:
/app/views/projects/_project.html.erb
<% cache ['v1', project] do %>
<%= link_to project.name, edit_project_path(project) %>
<%= render project.tasks %>
<% end %>
Since the key value was changed, the old cache became invalid and tasks with a numbered list are displayed on the page. Hurrah!
But there is some problem. You must constantly remember that every time you change the page code, you must also change the version number of the cache for new changes to take effect. In principle, this is not difficult, but everything is instantly complicated if nested fragment caching is used. Suppose I also want to cache the task partial in order to increase performance a bit more:
/app/views/tasks/_task.html.erb
<% cache ['v1', task] do %>
<%= task.name %>
<%= link_to "edit", edit_task_path(task) %>
<% end %>
Now you also need to update the cache key in the project partial so that the entire old cache disappears.
That is, for example, if we update the partial with the task, changing the link name from “edit” to “rename”, then it is obvious that you need to change its cache key. But no visible changes on the page will happen until the key value in the partial with the projects also changes. And only after that we will see our long-awaited innovations:
Cache digests
Yes, such caching works, but you must admit that it is terrible. And then a gem called “cache_digests” comes to our aid! 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 digital signature in the fragment cache, based on views. This means that changes to the page code will also change the cache key, thus clearing the old one.
Let's try out his work. To do this, include the following line in gemfile:
/ Gemfile
gem 'cache_digests'
And then:
$ bundle install
Now there is no longer any need to specify the version of the key, and therefore, with a clear conscience, you can 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 view and update the page, then the changes will not occur. The reason is that the cache digest gem does not analyze changes in views with every code change, because this is extremely unreasonable. Instead, it stores its local cache for each view, and assigns a unique digital signature to each.
To see changes in development mode, you need to restart the server of our application. Such problems should not appear in production, as the server restarts anyway with each new deployment.
Now, having refreshed the page, it will be seen that the updates in the code did not go unnoticed by the gem and we finally got the updated page. By the way, gem is smart enough and can determine dependencies. Well, for example, we still remember that presentation with projects calls the render method to display a list of tasks. Therefore, it is obvious that if the task partial has suddenly changed, then there is a need to delete the old cache in the project view.
Underwater rocks
But still, you should not relax very much, as there may be cases in which dependencies will not be correctly identified. Consider a small example.
Suppose an incomplete_tasks method exists in the Project model . And I decided to use this method to display unfinished tasks in the partial (responsible for displaying a list of projects). If you do this, it will become clear that the changes in the view were not displayed, since the dependencies were not defined correctly. Perhaps a good idea in this case would be to run the rake task cache_digests: nested_dependencies , so kindly provided by the gem.
$ rake cache_digests:nested_dependencies TEMPLATE=projects/index
[
{
"projects/project": [
"incomplete_tasks/incomplete_task"
]
}
]
As you can see, the path to the required view for analyzing the problem is transmitted from the above code.
The output of the rake task shows that a dependency was found in the partial with the projects (which is good), but it was defined incorrectly: the task should be in 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 %>
<%= link_to project.name, edit_project_path(project) %>
<%= render partial: 'tasks/task', collection: project.incomplete_tasks %>
<% end %>
By running the same rake task again, it will become clear that the dependencies are now properly defined and the cache has been updated successfully!
$ rake cache_digests:nested_dependencies TEMPLATE=projects/index
[
{
"projects/project": [
"tasks/task"
]
}
]
More details about the work of the gem can be found in its README , which I recommend making everyone interested. Thanks for attention!
About all errors found, translation inaccuracies and other similar things, please inform in PM.
Application
Source code of the application from the lesson
Subscribe to my blog !