You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Arel构建LIKE查询时,如何避免normalize属性移除%字符?

Rails 属性规范化与Arel LIKE查询冲突的解决方案

环境搭建

rails new testing --minimal
cd testing

bin/rails g model Property name handle --no-timestamps
bin/rails db:migrate
sed -i '2i \  normalizes :handle, with: ->(handle) { handle.to_s.underscore.parameterize(separator: "_") }' app/models/property.rb

最终的Property模型代码:

# app/models/property.rb

class Property < ApplicationRecord
  normalizes :handle, with: ->(handle) { handle.to_s.underscore.parameterize(separator: "_") }
end

问题描述

我们为handle字段配置的规范化逻辑,预期在创建/更新记录和等值查询时正常生效:

Property.create(name: "My property", handle: "  My   property ")
#=> #<Property:0x00007f9b3a0e2e50 id: 1, name: "My property", handle: "my_property">
#                                                                      ^^^^^^^^^^^ 已完成规范化

Property.where(handle: "  myProperty  ").to_sql
#=> "SELECT \"properties\".* FROM \"properties\" WHERE \"properties\".\"handle\" = 'my_property'"
#                                                                                   ^^^^^^^^^^^ 查询条件已规范化

但使用Arel构建LIKE模糊查询时,作为通配符的%字符会被规范化逻辑意外移除,导致查询不符合预期:

Property.arel_table[:handle].matches("%prop%").to_sql
#=> "\"properties\".\"handle\" LIKE 'prop'"
#                                    ^^^^ 通配符%丢失

需求是:不使用原生SQL语句where("handle LIKE ?"),同时保留handle属性的规范化逻辑。

解决方案

1. 手动预处理查询参数

利用Rails提供的normalize_value_for方法,先对去掉通配符的字符串做规范化,再重新拼接通配符:

search_term = "%prop%"
# 先移除通配符,再执行规范化
normalized_term = Property.normalize_value_for(:handle, search_term.delete("%"))
# 重新添加通配符后构建Arel查询
arel_query = Property.arel_table[:handle].matches("%#{normalized_term}%")

# 生成的SQL符合预期
Property.where(arel_query).to_sql
#=> "\"properties\".\"handle\" LIKE '%prop%'"

2. 封装模型查询方法

将预处理逻辑封装到模型类方法中,简化调用:

# app/models/property.rb
class Property < ApplicationRecord
  normalizes :handle, with: ->(handle) { handle.to_s.underscore.parameterize(separator: "_") }

  def self.handle_like(term)
    # 移除通配符后规范化,再重新拼接
    normalized_term = normalize_value_for(:handle, term.delete("%"))
    where(arel_table[:handle].matches("%#{normalized_term}%"))
  end
end

# 使用示例
Property.handle_like("%Prop%").to_sql
#=> "\"properties\".\"handle\" LIKE '%prop%'"

3. 调整规范化逻辑(可选)

如果业务场景中handle字段本身不会包含%字符,可以修改规范化逻辑,针对性处理带通配符的查询串:

# app/models/property.rb
class Property < ApplicationRecord
  normalizes :handle, with: ->(handle) {
    str = handle.to_s
    if str.include?("%")
      # 仅规范化通配符之外的内容部分
      str.gsub(/([^%]+)/) { $1.underscore.parameterize(separator: "_") }
    else
      str.underscore.parameterize(separator: "_")
    end
  }
end

# 直接使用Arel查询即可得到预期结果
Property.arel_table[:handle].matches("%  MyProp  %").to_sql
#=> "\"properties\".\"handle\" LIKE '%my_prop%'"

这种方式无需修改查询代码,但会增加规范化逻辑的复杂度,仅推荐在业务明确handle字段原生不会包含%时使用。


内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 16:12:03