# Terminal
bundle add prosopite
bundle add pg_query
bundle add pagy
# Console
ActiveRecord::Migration.remove_index :order_items, :order_id
ActiveRecord::Migration.remove_index :order_items, :product_id
ActiveRecord::Migration.remove_index :orders, :customer_id
ActiveRecord::Migration.remove_index :products, :category_id
ActiveRecord::Migration.remove_index :products, :vendor_id
ActiveRecord::Migration.remove_index :reviews, :customer_id
ActiveRecord::Migration.remove_index :reviews, :product_id
ActiveRecord::Migration.add_index :order_items, :order_id
ActiveRecord::Migration.add_index :order_items, :product_id
ActiveRecord::Migration.add_index :orders, :customer_id
ActiveRecord::Migration.add_index :products, :category_id
ActiveRecord::Migration.add_index :products, :vendor_id
ActiveRecord::Migration.add_index :reviews, :customer_id
ActiveRecord::Migration.add_index :reviews, :product_id
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include Pagy::Method
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern
# Changes to the importmap will invalidate the etag for HTML responses
stale_when_importmap_changes
unless Rails.env.production?
around_action :n_plus_one_detection
def n_plus_one_detection
Prosopite.scan
yield
ensure
Prosopite.finish
end
end
end
# app/controllers/dashboard_controller.rb
class DashboardController < ApplicationController
def index
@pagy_products, @products = pagy(:offset, Product.includes(:vendor, :category, :reviews))
@pagy_orders, @orders = pagy(:offset, Order.includes(:customer, order_items: :product))
@pagy_reviews, @reviews = pagy(:offset, Review.includes(:customer, :product))
@pagy_vendors, @vendors = pagy(:offset, Vendor.includes(products: :order_items))
end
end
# app/views/dashboard/index.html.erb
<%= turbo_frame_tag :products do %>
<table class="min-w-full border border-gray-300">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700 border-b">Name</th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700 border-b">SKU</th>
<th class="px-4 py-2 text-right text-sm font-medium text-gray-700 border-b">Price</th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700 border-b">Vendor</th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700 border-b">Category</th>
<th class="px-4 py-2 text-right text-sm font-medium text-gray-700 border-b">Avg Rating</th>
<th class="px-4 py-2 text-right text-sm font-medium text-gray-700 border-b">Reviews</th>
</tr>
</thead>
<tbody>
<% @products.each do |product| %>
<tr class="hover:bg-gray-50">
<td class="px-4 py-2 text-sm border-b"><%= product.name %></td>
<td class="px-4 py-2 text-sm text-gray-500 border-b"><%= product.sku %></td>
<td class="px-4 py-2 text-sm text-right border-b"><%= number_to_currency(product.price) %></td>
<td class="px-4 py-2 text-sm border-b"><%= product.vendor.name %></td>
<td class="px-4 py-2 text-sm border-b"><%= product.category.name %></td>
<td class="px-4 py-2 text-sm text-right border-b">
<% if product.reviews.any? %>
<%= (product.reviews.sum(&:rating).to_f / product.reviews.size).round(1) %>
<% else %>
N/A
<% end %>
</td>
<td class="px-4 py-2 text-sm text-right border-b"><%= product.reviews.size %></td>
</tr>
<% end %>
</tbody>
</table>
<%== @pagy_products.series_nav %>
<% end %>
# config/environments/development.rb
config.after_initialize do
Prosopite.rails_logger = true
end