Search code examples
ruby-on-railsactiverecordarel

Ruby on Rails scopes containing OR


Using OR in ActiveRecord models' scopes can lead to some weird behaviour when these scopes are chained. Assume we have two conditions that we want to disjunctively check, and to also have the scope in which the check occurs be chainable. Given a model with the following scopes:

class Test < ActiveRecord::Base
  scope :test1, lambda { where(SOME_CONDITION).or(SOME_OTHER_CONDITION) }
  scope :test2, lambda { where(id: Test.where(SOME_CONDITION).or(SOME_OTHER_CONDITION)) }
end

Then there's a subtle difference between them. The former behaves correctly when called as: Test.test1, but if prior to test1 any other scopes are called, the or causes some unintended behaviour, since the prior scopes now only work conjunctively with SOME_CONDITION. Using test2 fixes this issue, but generates a very hacky looking query, even for the simple case Test.test2. Additionally, the latter just looks very hacky. Is there any recommended way to tackle this problem?


Solution

  • Due to the fact that the alternative would likely be to try and determine whether or not conditions already existed (e.g. where was already called), which would be even "hackier" (sp?) than the sub-query in my opinion, your best bet is probably to use Arel to create the condition.

    class Test < ActiveRecord::Base
      scope :test, -> (val1,val2) {
        where(
          arel_table[:column].in(val1).or(
            arel_table[:column2].in(val2)
          )
        )
      }  
    end
    

    This option will be order independent when chaining scopes together