链范围为OR查询

时间:2016-07-16 09:59:18

标签: ruby-on-rails ruby-on-rails-4

模特:

 scope :verified, -> { where(verified: true)}
 scope :active, -> { where(active: true) }

现在,Model.active.verified的结果为active and verified。如何将范围链接为OR?请注意,我不想将两个范围合并为: where("active = ? OR verified = ?", true, true)

1 个答案:

答案 0 :(得分:1)

您无需升级到Rails 5即可使用or。在Rails 4中有很多方法可以做到这一点。虽然这些方法不适合在链式范围内使用,但在构建标准时可以达到类似的便利性。

  1. Arel表。 Arel是ActiveRecord关系代数的底层实现。它支持or以及其他强大的功能。

    arel_table = Model.arel_table
    
    Model.where(
      arel_table[:active].eq(true).
      or(
        arel_table[:verified].eq(true)
      )
    )
    
    # => SELECT "models".* FROM "models" WHERE ("models"."active" = 't' OR "models"."verified" = 't')
    
  2. 正如您所看到的,由于必须在or内部应用where,因此链接标准很复杂。在传递给where之前,可以在外部构建标准,但同样,它们的层次结构使其不那么简单。

    1. Monkeypatching ActiveRecord,添加or实现。将其放入 config / initializers / active_record.rb

      ActiveRecord::QueryMethods::WhereChain.class_eval do
        def or(*scopes)
          scopes_where_values = []
          scopes_bind_values  = []
          scopes.each do |scope|
            case scope
            when ActiveRecord::Relation
              scopes_where_values += scope.where_values
              scopes_bind_values += scope.bind_values
            when Hash
              temp_scope = @scope.model.where(scope)
              scopes_where_values += temp_scope.where_values
              scopes_bind_values  += temp_scope.bind_values
            end
          end
          scopes_where_values = scopes_where_values.inject(:or)
          @scope.where_values += [scopes_where_values]
          @scope.bind_values  += scopes_bind_values
          @scope
        end
      end
      
    2. 通过这种方式,您可以执行or这样的查询:

      Model.where.or(verified: true, active: true)
      
      # => SELECT "models".* FROM "models" WHERE ("models"."verified" = $1 OR "models"."active" = $2)  [["verified", "t"], ["active", "t"]]
      

      您可以添加更多条件:

      Model.where.or(verified: true, active: true, other: false)
      

      查询可以放在这样的类方法中:

      def self.filtered(criteria)
        where.or(criteria)    # This is chainable!
      end
      

      或在范围内,基本相同:

      scope :filtered, lambda { |criteria| where.or(criteria) }
      

      由于criteria只是一个哈希,你可以使用任意数量的元素以方便的方式构建它:

      criteria = {}
      criteria[:verified] = true if include_verified?
      criteria[:active] = true if include_active?
      ...
      
      Model.filtered(criteria).where(... more criteria ...)
      

      你有它。阅读更多详细信息这些SO问题:

      ActiveRecord Arel OR condition

      OR operator in WHERE clause with Arel in Rails 4.2

      最后,如果您不反对第三方解决方案,请查看Squeel gem。