Mirror318
Mirror318

Reputation: 12693

Can scopes be chained conditionally in Rails?

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?

Upvotes: 3

Views: 149

Answers (2)

aridlehoover
aridlehoover

Reputation: 3615

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.

Upvotes: 2

user4638556
user4638556

Reputation:

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

Upvotes: 2

Related Questions