Кэширование фрагментов и интенсивная загрузка: как получить лучшее из обоих миров?

Мне кажется, что кэширование фрагментов и нетерпевая загрузка - по крайней мере иногда - несколько расходятся друг с другом. Скажем, у меня есть Пользователь, у которого много сообщений, у каждого из которых есть много комментариев, которые, в свою очередь, также могут иметь много комментариев и т.д.

Когда мне приходится отображать страницу, я мог бы сделать желаемую загрузку пользователя, всех ее сообщений, всех их комментариев и т.д., чтобы избежать попадания в базу данных 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