クエリ部分は、スコープを使用して非常に簡単に実行できます。
class Category
scope :grouped_entries, lambda { |user, date|
select(['categories.*', 'COUNT(entries.id) as count_entries'])
.joins(:entries)
.where('entries.user_id = ?', user.id)
.where('MONTH(entries.created_at) = ?', date.month)
.group('categories.id')
}
end
その後、ループすることができます:
<% Category.grouped_entries(current_user, Date.today).each do |category| %>
<%= category.name %> with <%= category.count_entries %> entries this month.
<% end %>
もちろん、これをキャッシュするには、今月エントリが作成されるたびにキャッシュを更新する必要があります。たとえば、次のようにクエリをキャッシュできます。
@categories = Rails.cache.fetch("/grouped_entries/#{current_user.id}/#{Date.today.month}") do
Category.grouped_entries(current_user, Date.today).all
end
そして、user_id とエントリ created_at month を使用して新しいエントリが作成されると、単純に期限切れになります。すべてのカテゴリのエントリを個別にキャッシュする前に、まずこのアプローチを使用する必要があります。このクエリは非常に高速に実行されるため、各行を個別にキャッシュすることを詳しく調べる必要はありません。また、カテゴリごとに 1 つではなく、1 つのクエリを実行します。
各行を個別にキャッシュしない理由は次のとおりです。
- ユーザーのカテゴリまたはカテゴリ ID のリストを取得するためにデータベースにクエリを実行する必要があるため、とにかく 1 つのクエリを実行する必要があります。
- キャッシュの有効期限はより複雑です。たとえば、エントリのカテゴリが変更されたときに、古いカテゴリ キャッシュと新しいカテゴリ キャッシュを期限切れにする必要がある場合など、2 つのキャッシュを期限切れにする必要がある場合が多いためです。
- 期限切れのキャッシュ情報を取得するためにデータベースに対してさらに多くのクエリを実行することになり、データベースへのレイテンシが実際のクエリよりも長くかかる可能性があります。
- クエリは単純でインデックスを使用するため、すべての行をキャッシュする必要はありません。エントリの user_id と category_id にインデックスが必要です。