使用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
相关产品推荐
相关产品推荐

