boydenhartog
boydenhartog

Reputation: 752

Best way to conditionally chain scopes

I'm trying to extend the functionality of my serverside datatable. I pass some extra filters to my controller / datatable, which I use to filter results. Currently in my model I am testing whether the params are present or not before applying my scopes, but I'm not convinced this is the best way since I will have a lot of if/else scenario's when my list of filters grows. How can I do this the 'rails way'?

if params[:store_id].present? && params[:status].present?
  Order.store(params[:store_id]).status(params[:status])
elsif params[:store_id].present? && !params[:status].present?
  Order.store(params[:store_id])
elsif !params[:store_id].present? && params[:status].present?
  Order.status(params[:status])
else
  Order.joins(:store).all
end

ANSWER: Combined the answers into this working code:

query = Order.all
query = query.store(params[:store_id]) if params[:store_id].present?
query = query.status(params[:status]) if params[:status].present?
query.includes(:store)

Upvotes: 13

Views: 11764

Answers (3)

Yshmarov
Yshmarov

Reputation: 3729

The top answer above worked for me. Here is an example of its' real-life implementation:

      lessons = Lesson.joins(:member, :office, :group)
      if @member.present?
        lessons = lessons.where(member_id: @member)
      end
      if @office.present?
        lessons = lessons.where(office_id: @office)
      end
      if @group.present?
        lessons = lessons.where(group_id: @group)
      end
      @lessons = lessons.all

Upvotes: 1

Ho Man
Ho Man

Reputation: 2345

You could do it like this:

query = Order
query = query.store(params[:store_id]) if params[:store_id].present?
query = query.status(params[:status]) if params[:status].present?
query = Order.joins(:store) if query == Order

Alternatively, you could also just restructure the status and store scopes to include the condition inside:

scope :by_status, -> status { where(status: status) if status.present? }

Then you can do this instead:

query = Order.store(params[:store_id]).by_status(params[:status])
query = Order.joins(:store) unless (params.keys & [:status, :store_id]).present?

Upvotes: 19

Robert Nubel
Robert Nubel

Reputation: 7522

Since relations are chainable, it's often helpful to "build up" your search query. The exact pattern for doing that varies widely, and I'd caution against over-engineering anything, but using plain-old Ruby objects (POROs) to build up a query is common in most of the large Rails codebases I've worked in. In your case, you could probably get away with just simplifying your logic like so:

relation = Order.join(:store)

if params[:store_id]
  relation = relation.store(params[:store_id])
end

if params[:status]
  relation = relation.status(params[:status])
end

@orders = relation.all

Rails even provides ways to "undo" logic that has been chained previously, in case your needs get particularly complex.

Upvotes: 14

Related Questions