Кэширование фрагментов и интенсивная загрузка: как получить лучшее из обоих миров?
Мне кажется, что кэширование фрагментов и нетерпевая загрузка - по крайней мере иногда - несколько расходятся друг с другом. Скажем, у меня есть Пользователь, у которого много сообщений, у каждого из которых есть много комментариев, которые, в свою очередь, также могут иметь много комментариев и т.д.
Когда мне приходится отображать страницу, я мог бы сделать желаемую загрузку пользователя, всех ее сообщений, всех их комментариев и т.д., чтобы избежать попадания в базу данных n-1 раз. Или я мог лениво загружать каждый объект и полагаться на кэширование фрагментов, чтобы запрашивать базу данных только для новых или измененных объектов. Использование кэширования фрагментов и нетерпевая загрузка кажутся расточительными, поскольку я мог бы сделать очень сложный запрос и создать экземпляр множества объектов только для использования их небольшой части.
Но что, если у меня есть приложение, в котором у пользователя много Foos, в свою очередь, есть много баров и т.д., но в котором каждый Foo создается вместе со всеми его барами и связанными с ними объектами в одно и то же время и с тех пор никогда не меняется. В этом случае я хотел бы использовать кэширование фрагментов для Foos, которые были визуализированы, но использовать активную загрузку, когда мне нужно загрузить новый Foo со всеми его связанными объектами. В конце концов, от кеширования фрагментов на более гранулированном уровне ничего не получится.
Как лучше всего в Rails сделать это? Полагаю, я мог бы сделать один запрос, чтобы получить только идентификаторы Foo, а затем сделать явную находку с нетерпеливой загрузкой, когда мне нужно отобразить каждый Foo. Есть ли лучший/более элегантный/более идиоматический способ сделать это?
Ответы
Ответ 1
Вы можете использовать метод fragment_exists?
в контроллере, чтобы предотвратить загрузку всех объектов, когда они уже находятся в кеше. Это произойдет только при первом вызове страницы.
Вот так:
if fragment_exists? "my_cache_key_#{id}"
# load your object without eager loading here
else
# eager load your objects here
end
Затем в представлении используйте фрагмент chaching:
<% cache("my_cache_key_#{@object.id}") do %>
...
...
...
<% end %>
Это должно сделать это за вас!
Ответ 2
Основываясь на ответе @daviddb для Rails 4, я счел нужным skip_digest
в представлениях, поскольку воссоздание хеша MD5 для шаблонов в контроллере для сравнения показалось немного.
Кроме того, не обнаружив объект в первый раз, будет сложно получить объект с последней измененной меткой времени, поэтому я нашел полезным сделать первоначальный запрос без .includes(:object1, :object2)
views/customers/show.html.slim
(отрегулируйте свой предпочтительный шаблонный двигатель)
- cache['customers/show', @customer], skip_digest: true do
h1
= @customer.account.name
= render 'account_summary', account: @customer.account
= render 'account_details', transactions: @customer.account.transactions
...
controllers/customers_controller.rb
def show
customer = Customer.find(params[:id])
if fragment_exist?(['customers/show', customer])
@customer = customer
else
@customer = Customer.includes(account: :transactions).find(params[:id])
end
end
Примечание. При установке skip_digest: true
вам необходимо очистить кеш при развертывании при изменении этого представления и любых частичных данных, от которых он зависит, чтобы обеспечить правильную визуализацию ваших новых макетов.
Ответ 3
Я сделал что-то подобное с кешированием, используя лямбда.
Ниже вы можете получить представление. Проблема в том, что она решает - получение самых популярных пользователей - тяжелая операция и требует > 5 секунд. Но с lambda вы можете кэшировать список пользователей.
controller:
def index
@users = -> { User.by_rating }
end
view:
= cache "rating-list", expires_in: 1.day do
- @users.call.each do |user|
= render user