Rails ActiveRecord 里面关于 or 查询的时候,有没有比较简单的写法?

suck007 · 2015年05月28日 · 最后由 ericguo 回复于 2015年09月16日 · 4113 次阅读

现在是需要多字段模糊查询,我现在是大概这样做的

def searchUser
    search = params[:search]
    offset = params[:offset]
    rows = params[:limit]

    users = User.where("user_id like ? or user_name like ? or password like ? or gender like ? or
          device_name like ? or os_name like ? or os_version like ? or device_id like ? or user_type like ?",
          "%#{search}%","%#{search}%","%#{search}%","%#{search}%",
          "%#{search}%","%#{search}%","%#{search}%","%#{search}%","%#{search}%")
          .offset(offset)
          .limit(rows)

    result = {:total => User.count, :rows => users}
    render json: result
  end

上面的写法很有种原生 sql 的赶脚 想问下,有没有类似下面这样的写法?查询手册里面关于条件查询就一句话带过了

User.where("xxx" like ?,"%#{search}%") or where("xxx" like ?,"%#{search}%")

search 为什么不用个 ransack 呢? https://github.com/activerecord-hackery/ransack
还有其他的更复杂的查询建议用 arel_table

_field_values = [1,3]
# return Arel::Nodes to_sql
product_by_category_sql = Product.where(category: _field_values).where_values.reduce(:and).to_sql
# literal sql will return string
# return string
product_name_field_sql = Product.where("name in (?)", _field_values).where_values.reduce(:and)

Product.where("#{product_name_field_sql} or #{product_by_category_sql}")

# more generally
User.where(User.where(id: 123).where_values.reduce(:and).or(User.where(name: 123).where_values.reduce(:and))).to_sql
=> "SELECT `users`.* FROM `users`  WHERE ((`users`.`id` = 123 OR `users`.`name` = 123))"
users = User.where(%w[ user_id user_name gender ].map { |f| "#{f} LIKE :q" }.join(' OR '), q: "%#{params[:q]}%")

#4 楼 @huhongda 这个不错,另外 arel_table 也是不错的选择

Arel 是 Rails 里边用来管理生成 AST (Abstract Syntax Tree 抽象语法树) 的组件,负责最底层的 SQL 语句生成,使用 Arel 能够获得更高的灵活度。这个代码明显用 arel 重构就可以了:

def search_user     # Ruby 代码规范中,方法名跟局部变量命名都建议使用下划线形式
  search, offset, rows = *params.values_at(:search, :offset, :limit)
  search_fields = %w(user_id user_name password gender device_name os_name os_version device_id user_type)

  # 每个待查询字段都会有一个对应的 matches 匹配条件,最后这些条件之间用 `or` 运算合并,语义即“user_id matches xxx or user_name matches xxx or ...”
  condition = search_fields.map do |field|
    User.arel_table[field].matches("%#{search}%")    # matches 的源码请看 https://github.com/rails/arel/blob/master/lib%2Farel%2Fpredications.rb#L121-L123
  end.inject(:or)

  # 使用 Arel 构造的查询子句可以直接用于更高层级的 Query Method,也就是 `where` 方法
  users = User.where(condition).offset(offset).limit(rows)

  result = {:total => User.count, :rows => users}
  render json: result
end

另外研究了下, #4 楼 @huhongda 提到的 where_values 方法其实也是回归到了 Arel 的方式,因为 ActiveRecord::Relation#where_values 的源码如下:

def #{name}_values                   # def select_values
  @values[:#{name}] || []             #   @values[:select] || []
end                                       # end

其中 @values 里边实际上就存储了很多 QueryMethod 需要用到的 Arel 节点 ( Arel::Nodes::Equality 等类型实例)。

所以 where_values 方法其实返回的就是一个由 Arel::Nodes::Equality 类型的实例组成的数组,跟我上边提到的这段代码返回的内容在结构上是一致的:

condition = search_fields.map do |field|
  User.arel_table[field].matches("%#{search}%")
end

唯一不同的只是直接用 Arel 写还可以在查询中使用通配符 %

用这么多 like 还不如用 elasticsearch 来处理

如果用的是 Rails 4.2.3+,可以考虑用这个where-or gem,就是Rails 5 的 where.or

需要 登录 后方可回复, 如果你还没有账号请 注册新账号