Question

Rails 3.2.13 with PostgreSQL

We are trying to optimize the below model code, which retrieves a set of e-commerce Orders from the database, based on user defined filtering criteria.

The user can select to filter orders based on Order Status, Payment Status & Shipping Status. They can set these to either be == or <> a particular value for each.

After this we filter out any digital orders then paginate the results.

This all works well, except that the Order has many associations, such as products, addresses, payments etc. This causes N+1 queries and makes the process extremely slow.

Order.rb:

belongs_to :shop
has_many :shipping_lines , :dependent => :destroy
has_many :line_items , :dependent => :destroy
has_many :taxlines , :dependent => :destroy
has_many :fulfillments , :dependent => :destroy
has_many :notes , :dependent => :destroy
has_many :discounts , :dependent => :destroy
has_one :billing_address , :dependent => :destroy
has_one :shipping_address , :dependent => :destroy
has_one :payment_detail, :dependent => :destroy
has_many :order_histories, :dependent => :destroy

Any ideas on how we can include the associations eager loaded in the below query?

Example Input: The user could want to see Any (Open or Closed), Paid orders that are not yet Shipped

Order Status == 'open'

Payment Status == 'any'

Shipping Status <> 'shipped'

The shop is identified by the subdomain

def by_filter(subdomain, filter,page)
  shop_condition = "shop_id = #{subdomain.shops[0].id}"
  if filter.name != "All"
    if filter.order_status == "any"
      status_condition =  "status in (?)"
      order_status = Filter::ORDER_STATUS.values - ["any"]
    else
      status_condition =  "status #{Filter::STATUS_OPERATORS_MAPPING[filter.order_status_operator]} ?"
      order_status = filter.order_status
    end

    if filter.payment_status == "any"
      payment_status_condition =  "payment_status in (?)"
      payment_status = Filter::PAYMENT_STATUS.values - ["any"]
    else
      payment_status_condition =  "payment_status #{Filter::STATUS_OPERATORS_MAPPING[filter.payment_status_operator]} ?"
      payment_status = filter.payment_status
    end

    if filter.fulfillment_status == "any"
      fulfillment_status_condition =  "fulfillment_status in (?)"
      fulfillment_status = Filter::FULFILLMENT_STATUS.values - ["any"]
    else
      fulfillment_status_condition = "fulfillment_status #{Filter::STATUS_OPERATORS_MAPPING[filter.fulfillment_status_operator]} ?"
      fulfillment_status = filter.fulfillment_status
    end

    conditions = "#{status_condition} AND #{payment_status_condition} AND #{fulfillment_status_condition} AND #{shop_condition} "
  else
    conditions = shop_condition
  end


  orders = self.where([conditions, order_status, payment_status, fulfillment_status]).order("order_created_at #{filter.sort_by}")


  if filter.digital_orders
    orders_arr = ((orders.all.collect(&:shipping_address) - [nil]).collect(&:order)).flatten
    orders = Kaminari.paginate_array(orders_arr).page(page).per(filter.show)
  else
    orders = orders.page(page).per(filter.show)
  end
  return orders
end

The SQL Query Generated looks like this:

SELECT "orders".* FROM "orders" WHERE (status = 'open' AND payment_status in ('authorized', 'pending', 'paid', 'partially_paid', 'partially_refunded', 'refunded', 'voided') AND fulfillment_status <> 'shipped' AND shop_id = 58 ) ORDER BY order_created_at DESC

We have tried to do the regular .includes but this seems to have no effect. For example:

orders = self.includes(:shipping_address).where([conditions, order_status, payment_status, fulfillment_status]).order("order_created_at #{filter.sort_by}")
Was it helpful?

Solution

Use named scopes, instead of defining the method for the Order model.

class Order

named_scope :with_address,
:include    => :shipping_address

...

I would do the filtering and ordering with scopes also.

You can read more about :include and :joins from http://venkatev.wordpress.com/2010/02/04/activerecord_joins_and_include/

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top