Can scopes be chained conditionally in Rails?

Viewed 136

Consider this code:

class Car
  scope :blue, -> { where(color: "blue") }
  scope :manual, -> { where(transmission: "manual") }
  scope :luxury, -> { where("price > ?", 80000) }
end

def get_cars(blue: false, manual: false, luxury: false)
  cars = Car.all
  cars = cars.blue if blue
  cars = cars.manual if manual
  cars = cars.luxury if luxury
end

Is there a way to chain these scopes like Car.blue.manual.luxury conditionally? I.e. only scope if the arg is true?

2 Answers

ActiveRecord scopes can be applied conditionally, like this:

scope :blue, -> { where(color: 'blue') if condition }

Where condition is something you define that returns true or false. If the condition returns true, the scope is applied. If the condition is false, the scope is ignored.

You can also pass values into a scope:

scope :blue, ->(condition) { where(color: 'blue') if condition }

So, you could do something like this:

Task.blue(color == 'blue')

Which is similar to what the OP requested. But, why would you?

A better approach is something like this:

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

Which would be called like this:

Car.color('blue')  # returns blue cars
Car.color(nil)     # returns all cars

Car.color(params[:color])  # returns either all cars or only cars of a specific color, depending on value of param[:color]

Car.color(params[:color]).transmission(params[:transmission]).price(params[:price])

Your mileage may vary.

You can use yield_self(read more here), new functionality added in ruby 2.5 for it.

In your example:

class Car
  scope :blue, -> { where(color: "blue") }
  scope :manual, -> { where(transmission: "manual") }
  scope :luxury, -> { where("price > ?", 80000) }
end

def get_cars(blue: false, manual: false, luxury: false)
  cars = Car.all
            .yield_self { |cars| blue ? cars.blue : cars }
            .yield_self { |cars| manual ? cars.manual : cars }
            .yield_self { |cars| luxury ? cars.luxury : cars }
end
Related