Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)
like image 508
boydenhartog Avatar asked Jul 01 '16 02:07

boydenhartog


3 Answers

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?
like image 200
Ho Man Avatar answered Nov 02 '22 12:11

Ho Man


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.

like image 13
Robert Nubel Avatar answered Nov 02 '22 14:11

Robert Nubel


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
like image 1
Yshmarov Avatar answered Nov 02 '22 14:11

Yshmarov