Гем под названием "cache_digests" (включен по умолчанию в Rails 4) автоматически добавляет цифровую подпись к каждому фрагментному кэшу, основываюсь на представлении (вьюхе). При этом, если страница изменяется, то старый кэш автоматически удаляется. Но остерегайтесь подводных камней!
Я написал небольшое приложение, в котором имеется список с проектами, у каждого из которых есть определенный список задач. Предположим, что в данном приложении возникли проблемы с производительностью и для их исправления было принято решение воспользоваться фрагментным кэшированием.
Следующий код отображает список проектов:
/app/views/projects/index.html.erb
<h1>Projects</h1>
<%= render @projects %>
Для каждого проекта генерируется партиал _project. Он тоже весьма прост и занимается отображением списка задач:
/app/views/projects/_project.html.erb
<h2><%= link_to project.name, edit_project_path(project) %></h2>
<ul><%= render project.tasks %></ul>
В свою очередь, _project рендерит еще один партиал: _task. Итак, добавим фрагментное кэширование для _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 %>
Так как вышеприведенный код отображает список задач, то было бы разумно переставать кэшировать старые данные при появлении новой задачи. Эту цель можно достичь добавив в связь с проектом touch: true в модель Task:
/app/models/task.rb
class Task < ActiveRecord::Base
attr_accessible :name, :completed_at
belongs_to :project, touch: true
end
Теперь при изменении задачи проекта она будет помечена как обновленная. Проверим работу кэширования в режиме development:
/config/development.rb
config.action_controller.perform_caching = true
После рестарта сервера и обновления страницы каждый из проектов будет кэшироваться с помощью фрагментного кэширования. В то же время если одна из задач будет отредактирована, то срок действия кэша истечет и таким образом будут загружены новые данные.
Все это замечательно, но что произойдет если изменения будут внесены в код самой страницы? К примеру, я обновил код для отображения задач в виде нумерованного списка:
/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 %>
Теперь обновим страницу в браузере. Никаких видимых изменений не произошло! Это случилось из-за того что страница со старым кодом уже сохранилась в кэше, и срок его действия еще не истек. Поэтому старый контент по-прежнему виден. Эту проблему обычно обходят путем обновления версии ключа кэша:
/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 %>
Так как значение ключа было изменено, то старый кэш стал невалиден и на странице отображаются задачи с нумерованным списком. Ура!
Но есть некоторая проблема. Нужно постоянно помнить, что при каждом изменении кода страницы необходимо также поменять номер версии кэша для вступления новых изменений в силу. В принципе, это нетрудно, но все мгновенно усложняется если используется вложенное фрагментное кэширование. Предположим, что я также хочу закэшировать партиал с задачами с целью еще немного увеличить производительность:
/app/views/tasks/_task.html.erb
<% cache ['v1', task] do %>
<li>
<%= task.name %>
<%= link_to "edit", edit_task_path(task) %>
</li>
<% end %>
Теперь нужно также обновить ключ кэша в партиале с проектом для того, чтобы весь старый кэш исчез.
То есть, к примеру, если мы обновим партиал с задачей, изменив имя ссылки с «edit» на «rename» то очевидно, что необходимо поменять его ключ кэша. Но никаких видимых изменений на странице не произойдет до тех пор пока также не изменится значение ключа в партиале с проектами. И только после этого мы увидим наши долгожданные нововведения:
Сache digests
Да, такое кэширование работает, но, согласитесь, оно ужасно. И тут к нам на помощь приходит гем под названием «cache_digests»! Его функционал включен в Rails 4, но также он был выделен с отдельный гем для того, чтобы разработчики могли использовать его уже сегодня в проектах с Rails 3.
Этот гем включает цифровую подпись в фрагментный кэш, базируясь на представлениях. Это значит, что изменения в коде страницы будут также менять ключ кэша, очищая таким образом старый.
Давайте опробуем его работу. Для этого нужно включить в gemfile следующую строку:
/Gemfile
gem 'cache_digests'
И затем:
$ bundle install
Теперь нет более необходимости в указании версии ключа и поэтому можно со спокойной совестью удалить лишний код из партиалов _project и _task. После этого необходимо перезапустить сервер и обновить страницу в браузере для вступления в силу нового кэширования.
Если этого не сделать, и при этом попробовать немного поменять код вьюхи проектов и обновить страничку, то изменения не произойдут. Причина кроется в том, что гем «cache digest» не анализирует изменения в представлениях при каждом изменении кода, ведь это крайне неразумно. Вместо этого он хранит свой локальный кэш для каждого представления, и каждому ставит в соответствие уникальную цифровую подпись.
Чтобы увидеть изменения в режиме development необходимо рестартануть сервер нашего приложения. Такие проблемы не должны появляться в продакшене, так как при каждом новом деплое все равно перезапускается сервер.
Теперь, обновив страницу, будет видно, что обновления в коде не остались незамеченными гемом и перед нами наконец-то предстала обновленная страница. Кстати говоря, гем достаточно умен и умеет определять зависимости. Ну вот, к примеру, мы еще помним, что представление с проектами вызывает метод render для отображения списка задач. Поэтому, очевидно, что если партиал с задачами вдруг изменился, то возникает необходимость в удалении старого кэша в вьюхе с проектами.
Подводные камни
Но все же не стоит сильно расслабляться, так как возможны случаи, в которых зависимости не будут верным образом определены. Рассмотрим небольшой пример.
Допустим, в модели Project существует метод incomplete_tasks. И я решил воспользоваться этим методом для отображения неоконченных задач в партиале (отвечающим за отображение списка проектов). Если это сделать, то станет видно, что изменения в вьюхе не были отображены, поскольку зависимости не были определены верно. Пожалуй, неплохой идеей в данном случае будет запуск rake задачи cache_digests:nested_dependencies, столь любезно предоставленный гемом.
$ rake cache_digests:nested_dependencies TEMPLATE=projects/index
[
{
"projects/project": [
"incomplete_tasks/incomplete_task"
]
}
]
Как видно, из вышеприведенного кода передается путь к необходимой вьюхе для анализа возникшей проблемы.
Вывод rake задачи показывает, что была найдена зависимость в партиале с проектами (что хорошо), но при этом определена она неверно: на месте incomplete_task должен находиться task. Для того, чтобы исправить сей неприятный казус рекомендую воспользоваться следующим кодом (обратите внимание, что я указываю partial и использую 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 %>
Снова запустив тот же rake task станет видно, что зависимости определены теперь должным образом и кэш был успешно обновлен!
$ rake cache_digests:nested_dependencies TEMPLATE=projects/index
[
{
"projects/project": [
"tasks/task"
]
}
]
Более подробно о работе гема можно почитать в его README, что я и рекомендую сделать всем заинтересовавшимся. Спасибо за внимание!
Обо всех найденных ошибках, неточностях перевода и прочих подобных вещах просьба сообщать в личку.
Приложение
Исходный код приложения из урока
Автор: Loremaster