如何在 rails 中构建动态范围

How to build a dynamic scope in rails

提问人:willyab 提问时间:10/5/2023 最后编辑:mechnicovwillyab 更新时间:10/5/2023 访问量:35

问:

在给定要排除的可变长度元素数组的情况下,如何构造动态范围搜索,如下所示:

class Participant < ApplicationRecord

scope exclude_names, -> (['%name1%', '%name2%', '%name3%', ...]) {
  where.not(Participant.arel_table[:name_search].matches('%name1%').or(
   Participant.arel_table[:name_search].matches('%name2%').or(
     Participant.arel_table[:name_search].matches('%name3%').or(
...
}

但动态完成,因为name_list是可变长度的。

Ruby-on-Rails Rails-ActiveRecord Arel

评论

0赞 mechnicov 10/5/2023
是否要传递百分比字符串?
0赞 max 10/5/2023
请注意,此方法确实会使您容易受到 SQL 注入攻击,如果输入来自用户,则不应使用此方法。match 方法实际上并没有 AFAIK 使用占位符的好方法,这是 SQL 字符串实际上是一个完全可以接受的解决方案的地方之一。
0赞 mechnicov 10/5/2023
@max有sanitize_sql_like方法

答:

0赞 Siim Liiser 10/5/2023 #1

在一个循环中创建所有条件,然后将它们与 -s 组合到您的条件中。or

scope :exclude_names, -> (names) do
  clauses = names.map do |name|
    Participant.arel_table[:name_search].matches(name)
  end
  condition = clauses.inject(:or)
  where.not(condition)
end
0赞 mechnicov 10/5/2023 #2

我建议使用方法并累积条件,遍历排除的名称。您也不需要在作用域内显式调用类名,因为它是类方法does_not_matchAND

class Participant < ApplicationRecord
  scope :exclude_names, ->(*names_to_exclude) {
    query = names_to_exclude.reduce(nil) do |q, name|
      condition = arel_table[:name_search].does_not_match("%#{name}%")
      q&.and(condition) || condition
    end

    where(query)
  }
end

之后,您可以调用此作用域

Participant.exclude_names('name1')
# SELECT * FROM participants
# WHERE name_search NOT LIKE '%name1%'

Participant.exclude_names('name1', 'name2')
# SELECT * FROM participants
# WHERE name_search NOT LIKE '%name1%'
# AND name_search NOT LIKE '%name2%'

Participant.exclude_names(%w[name1 name2])
# SELECT * FROM participants
# WHERE name_search NOT LIKE '%name1%'
# AND name_search NOT LIKE '%name2%'

当然,您可以在您的问题中使用 like,在这种情况下,它将是这样的OR

class Participant < ApplicationRecord
  scope :exclude_names, ->(*names_to_exclude) {
    query = names_to_exclude.reduce(nil) do |q, name|
      condition = arel_table[:name_search].matches("%#{name}%")
      q&.or(condition) || condition
    end

    where.not(query)
  }
end

之后,您可以调用此范围,与以前的查询进行比较

Participant.exclude_names('name1')
# SELECT * FROM participants
# WHERE NOT (name_search LIKE '%name1%')

Participant.exclude_names('name1', 'name2')
# SELECT * FROM participants
# WHERE NOT (name_search LIKE '%name1%' OR name_search LIKE '%name2%')


Participant.exclude_names(%w[name1 name2])
# SELECT * FROM participants
# WHERE NOT (name_search LIKE '%name1%' OR name_search LIKE '%name2%')