superluminary
superluminary

Reputation: 49182

How do I create a rails scope with an or or a not?

I have a two scopes in my user model:

scope :hard_deactivated, where(:hard_deactivated => true)
scope :soft_deactivated, where(:soft_deactivated => true)

So far so good

OR

I want to create a scope :deactivated, which will include all users where hard_deactivated is true OR soft deactivated is true. Obviously I could just do this:

scope :deactivated, where("hard_deactivated = ? or soft_deactivated = ?", true, true)

but this does not feel very dry.

NOT

Also I would like to create an inverse scope :not_hard_deactivated. I could do this:

scope :not_hard_deactivated, where(:hard_deactivated => false)

but again, this feels bad, especially if my scope becomes more complex. There should be some way or warpping the SQL generated by the previous scope in a not clause.

Upvotes: 2

Views: 904

Answers (3)

Leventix
Leventix

Reputation: 3859

For the "NOT" part, you can do something like this:

extend ScopeUtils

positive_and_negative_scopes :deactivated do |value|
  where(:hard_deactivated => value)
end

And implement this method in a separate module:

module ScopeUtils
  def positive_and_negative_scopes(name)
    [true, false].each do |filter_value|
      prefix = ("not_" if filter_value == false)
      scope :"#{prefix}#{name}", yield(filter_value)
    end
  end
end

Regarding the "OR" case, you might be something similar, depending on what your recurring pattern is. In the simple example above it's not worth it, as doesn't help readability.

scopes_with_adjectives_and_negatives :deactivated, [:soft, :hard]

module ScopeUtils
  def scopes_with_adjectives_and_negatives(name, kinds)
    kinds.each do |kind|
      positive_and_negative_scopes name do |filter_value|
        where("#{kind}_#{name}" => filter_value)
      end
    end
    scope :"#{name}", where(kinds.map{|kind| "#{kind}_#{name} = ?"}.join(" OR "), true, true)
    scope :"not_#{name}", where(kinds.map{|kind| "#{kind}_#{name} = ?"}.join(" AND "), false, false)
  end
end

Upvotes: 1

Chris Salzberg
Chris Salzberg

Reputation: 27374

Use an arel table:

hard_deactivated_true = arel_table[:hard_deactivated].eq(true)
soft_deactivated_true = arel_table[:soft_deactivated].eq(true)

scope :deactivated, where(hard_deactivated_true.and(soft_deactivated_true))
scope :not_hard_deactivated, where(hard_deactivated_true.not)

See: Is it possible to invert a named scope in Rails3?

Upvotes: 3

mikdiet
mikdiet

Reputation: 10018

You should use sql snippet in where method (like in your second example), or more 'sugar' gems like squeel

Upvotes: 0

Related Questions