Структурирование приложения Rails для кэширования русской куклы с помощью отношения has_many
Изучив DHH и другие статьи в блоге об истечении срока действия кэша на основе ключей и кэшировании в российских куклах, я все еще не уверен, как обращаться с одним типом отношений. Чтобы быть конкретным, has_many
отношения.
Я поделюсь результатами моего исследования на примере приложения. Это немного истории, так что держись. Допустим, у нас есть следующие модели ActiveRecord. Все, что нас волнует, это правильное изменение модели cache_key
, право?
class Article < ActiveRecord::Base
attr_accessible :author_id, :body, :title
has_many :comments
belongs_to :author
end
class Comment < ActiveRecord::Base
attr_accessible :article_id, :author_id, :body
belongs_to :author
belongs_to :article, touch: true
end
class Author < ActiveRecord::Base
attr_accessible :name
has_many :articles
has_many :comments
end
У нас уже есть одна статья с одним комментарием. Оба от другого автора. Цель состоит в том, чтобы изменить cache_key
за статью в следующих случаях:
- Изменения в теле или названии статьи
- Изменения в теле комментария
- Изменение имени автора статьи
- Изменение имени автора комментария к статье
Так что по умолчанию мы хороши для случаев 1 и 2.
1.9.3-p194 :034 > article.cache_key
=> "articles/1-20130412185804"
1.9.3-p194 :035 > article.comments.first.update_attribute('body', 'First Post!')
1.9.3-p194 :038 > article.cache_key
=> "articles/1-20130412185913"
Но не для случая 3.
1.9.3-p194 :040 > article.author.update_attribute('name', 'Adam A.')
1.9.3-p194 :041 > article.cache_key
=> "articles/1-20130412185913"
Давайте определим композит cache_key
метод для Article
,
class Article < ActiveRecord::Base
attr_accessible :author_id, :body, :title
has_many :comments
belongs_to :author
def cache_key
[super, author.cache_key].join('/')
end
end
1.9.3-p194 :007 > article.cache_key
=> "articles/1-20130412185913/authors/1-20130412190438"
1.9.3-p194 :008 > article.author.update_attribute('name', 'Adam B.')
1.9.3-p194 :009 > article.cache_key
=> "articles/1-20130412185913/authors/1-20130412190849"
Выиграть! Но, конечно, это не работает для случая 4.
1.9.3-p194 :012 > article.comments.first.author.update_attribute('name', 'Bernard A.')
1.9.3-p194 :013 > article.cache_key
=> "articles/1-20130412185913/authors/1-20130412190849"
Так какие варианты остались? Мы могли бы что-то сделать с has_many
ассоциация по Author
, но has_many
не принимает {touch: true}
вариант, и, вероятно, по причине. Я думаю, что это может быть реализовано несколько следующим образом.
class Author < ActiveRecord::Base
attr_accessible :name
has_many :articles
has_many :comments
before_save do
articles.each { |record| record.touch }
comments.each { |record| record.touch }
end
end
article.comments.first.author.update_attribute('name', 'Bernard B.')
article.cache_key
=> "articles/1-20130412192036"
Пока это работает. Он оказывает огромное влияние на производительность, загружая, создавая и обновляя каждую статью и комментарии друг к другу, один за другим. Я не верю, что это правильное решение, но что?
Конечно, вариант использования 37signals может отличаться: project -> todolist -> todo
, Но я представляю один элемент todo, также принадлежащий пользователю.
Как решить эту проблему кеширования?
2 ответа
Один метод, на который я наткнулся, - это обработать его с помощью ключей кеша. Добавить has_many_through
Отношение для комментаторов к статье:
class Article < ActiveRecord::Base
attr_accessible :author_id, :body, :title
has_many :comments
has_many :commenters, through: :comments, source: :author
belongs_to :author
end
Затем в статье / шоу мы создадим ключ кеша следующим образом:
<% cache [@article, @article.commenters, @article.author] do %>
<h2><%= @article.title %></h2>
<p>Posted By: <%= @article.author.name %></p>
<p><%= @article.body %></p>
<ul><%= render @article.comments %></ul>
<% end %>
Хитрость в том, что ключ кеша генерируется из commenters
связь будет меняться всякий раз, когда комментарий добавляется, удаляется или обновляется. Хотя для генерации ключа кеша это требует дополнительных SQL-запросов, это хорошо сочетается с низкоуровневым кешированием в Rails, и добавление чего-то вроде gem identity_cache может легко помочь в этом.
Я хотел бы видеть, есть ли у других людей более чистые решения для этого все же.
Как рекомендовано здесь https://rails.lighthouseapp.com/projects/8994/tickets/4392-add-touch-option-to-has_many-associations, в моем случае я просто создал обратный вызов after_save для обновления временных меток связанных объектов.
def touch_line_items_and_tactics
self.line_item_advertisements.all.map(&:touch)
end
Кроме того, мы создали наше приложение rails на устаревшей базе данных, которая имеет last_modified_time
в качестве имени столбца и его семантики было "когда пользователь последний раз изменял его". Так что из-за различной семантики мы не могли использовать :touch
вариант из коробки. Мне пришлось monkeypatch cache_key и сенсорных методов, как этот https://gist.github.com/tispratik/9276110 чтобы сохранить обновленную метку времени в memcached вместо столбца updated_at базы данных.
Также обратите внимание, что я не мог использовать по умолчанию cache_timestamp_format
от Rails, поскольку он снабжен метками времени только до секунд. Я чувствовал необходимость иметь более точную временную метку, поэтому я выбрал: нсек (наносекунды).
Временная метка с cache_timestamp_format: 20140227181414
Метка времени с нсек: 20140227181414671756000