Rails filtering with chained cumulative .OR scopes

Viewed 16

I have a model Product_Item which has can have many line_items. Each line_items represent a payment that is either 'pending' or 'paid'. A Product_Item can be paid in multiple payments.

The Product_Item is partially_paid if the sum of it's "paid" line_items is inferior to its price

The Product_Item is paid if the sum of it's "paid" line_items is equal or superior to its price

The Product_Item is not_paid if it has no paid line_items

I want to create a filtering search where we can select multiple scopes like so enter image description here

How could I perform a chained OR query like this ? I want the scopes to be cumulative - grab all the product_items that have been paid or partially_paid or ... ?

ProductItem.partially_paid.or(paid).or(not_paid).or(etc... if more cumulative scopes)

I have it running like this for the moment, but I think there should be a cleaner approach

view.html.erb

<%= form_with url: product_items_path(@pool), local: true, {}, method: :get do |form|  %>
  <% ['paid', 'partially_paid', 'not_paid'].each do |scope| %>
    <%= check_box("scope", scope, { checked: (params[:scope].present? && params[:scope][scope] == '1') } %>
  <% end %>
  <%= form.submit 'Apply' %>
<% end %>

ProductItemsController.rb

product_items_ids = []
['paid', 'partially_paid', 'not_paid'].each do |scope|
  product_items_ids += ProductItem.send(scope).pluck(:id) if params[:status].present? && params[:status][scope.to_sym] == "1"
end
@product_items    = ProductItem.all.where(id: product_items_ids)

ProductItem.rb

has_many :line_items

scope :paid,           -> { joins(:line_items)
                            .where("line_items.state LIKE 'paid'")
                            .having("SUM(line_items.amount_with_taxes_cents) >= price_with_taxes_cents")
                            .group("product_items.id")
                          }
scope :partially_paid, -> { joins(:line_items)
                            .where("line_items.state LIKE 'paid'")
                            .having("SUM(line_items.amount_with_taxes_cents) < price_with_taxes_cents")
                            .group("product_items.id") 
                            & where.not(id: self.paid.pluck(:id))
                          }
scope :not_paid,       -> { where.not(id: self.paid.pluck(:id)).or(self.where.not(id: self.partially_paid }
  

LineItem.rb

AVAILABLE_STATES = ['pending', 'paid']

belongs_to :product_item

Schema.rb

create_table "line_items", force: :cascade do |t|
    t.integer "amount_with_taxes_cents"
    t.text "state", default: "pending"
    t.bigint "product_item_id"
    t.index ["product_item_id"], name: "index_line_items_on_product_item_id"
end

create_table "product_items", force: :cascade do |t|
    t.text "name"
    t.integer "price_with_taxes_cents"
  end
0 Answers
Related