In my Rails application, I have users , which can have many invoices , which in turn can have many payments .
Now in the dashboard view, I want to summarize all payments a user that have ever been received, sorted by year, quarter or month. payments also divided into gross, net and tax.
user.rb
class User < ActiveRecord::Base has_many :invoices has_many :payments def years (first_year..current_year).to_a.reverse end def year_ranges years.map { |y| Date.new(y,1,1)..Date.new(y,-1,-1) } end def quarter_ranges ... end def month_ranges ... end def revenue_between(range, kind) payments_with_invoice ||= payments.includes(:invoice => :items).all payments_with_invoice.select { |x| range.cover? x.date }.sum(&:"#{kind}_amount") end end
invoice.rb
class Invoice < ActiveRecord::Base belongs_to :user has_many :items has_many :payments def total items.sum(&:total) end def subtotal items.sum(&:subtotal) end def total_tax items.sum(&:total_tax) end end
payment.rb
class Payment < ActiveRecord::Base belongs_to :user belongs_to :invoice def percent_of_invoice_total (100 / (invoice.total / amount.to_d)).abs.round(2) end def net_amount invoice.subtotal * percent_of_invoice_total / 100 end def taxable_amount invoice.total_tax * percent_of_invoice_total / 100 end def gross_amount invoice.total * percent_of_invoice_total / 100 end end
dashboards_controller
class DashboardsController < ApplicationController def index if %w[year quarter month].include?(params[:by]) range = params[:by] else range = "year" end @ranges = @user.send("#{range}_ranges") end end
index.html.erb
<% @ranges.each do |range| %> <%= render :partial => 'range', :object => range %> <% end %>
_range.html.erb
<%= @user.revenue_between(range, :gross) %> <%= @user.revenue_between(range, :taxable) %> <%= @user.revenue_between(range, :net) %>
Now the problem is that this approach works, but also creates a lot of SQL queries. In a typical dashboard view, I get 100+ SQL queries. Before adding .includes(:invoice) there were even more requests.
I assume that one of the main problems is that each subtotal , total_tax and total invoice is not stored anywhere in the database, but is calculated with every request.
Can someone tell me how to speed things up here? I am not very familiar with SQL and the inner workings of ActiveRecord, so this is probably the problem here.
Thanks for any help.