【问题标题】:Dynamic Method with ActiveRecord, passing in hash of conditions带有 ActiveRecord 的动态方法,传入条件哈希
【发布时间】:2013-01-30 20:12:46
【问题描述】:

我正在努力寻找对动态方法进行元编程的最佳方式,我将根据条件限制结果......例如:

class Timeslip < ActiveRecord::Base

    def self.by_car_trans(car, trans)
        joins(:car)
        .where("cars.trans IN (?) and cars.year IN (?) and cars.model ILIKE ?", trans, 1993..2002, car)
        .order('et1320')
    end

end

假设我没有传入我的参数,而是传入一个条件数组,其中键是字段名,值是字段值。例如,我会做这样的事情:

我会传入 [["field", "value", "operator"],["field", "value", "operator"]]]

def self.using_conditions(conditions)
    joins(:car)
    conditions.each do |key, value|
      where("cars.#{key} #{operator} ?", value)
    end
end

但是,这不起作用,而且不是很灵活...我希望能够检测该值是否为数组,并使用IN()而不是=,也许可以使用ILIKE对于不区分大小写的条件...

感谢任何建议。我在这里的主要目标是拥有一个“列表”模型,用户可以在其中动态构建他们的条件,然后保存该列表以供将来使用。此列表将根据关联的汽车表过滤时间单模型...也许有更简单的方法来解决这个问题?

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-3 postgresql rails-activerecord


    【解决方案1】:

    首先,您可能会对Squeel gem 感兴趣。

    除此之外,使用arel_table 表示 IN 或 LIKE 谓词:

     joins( :car ).where( Car.arel_table[key].in      values )
     joins( :car ).where( Car.arel_table[key].matches value  )
    

    您可以检测值的类型以选择适当的谓词(不是很好的 OO,但仍然):

     column    = Car.arel_table[key]
     predicate = value.respond_to?( :to_str ) ? :in : :matches # or any logic you want
    
     joins( :car ).where( column.send predicate, value )
    

    您可以根据需要链接任意数量的链接:

     conditions.each do |(key, value, predicate)|
       scope = scope.where( Car.arel_table[key].send predicate, value )
     end
     return scope
    

    【讨论】:

    • 是的,我在寻找解决方案时实际上将 squeel gem 包含在我的 gemfile 中......只是希望尝试找到一些简单的东西,或者在没有任何额外 gem 的情况下做到这一点
    • arel 使这种事情变得非常可能,并有助于保护它。但是,您不能拥有“通用魔法和安全查询”……您仍然必须构建特定于您的业务规则的东西。没有这个,我们的开发人员就没有更多的工作了:D
    • 你能给我一些关于我如何在你写的上面构建这个范围行的见解,但是用一个动态关联名称代替“引擎”。 scope = scope.where( Car.engine.arel_table[key].send predicate, value )
    • 正如@AlistairIsrael 建议的那样,如果您有一组有限的可能搜索字段,您可以使用查找表——它可以是一个简单的哈希,其中关联名称作为键,范围名称作为值。如果你真的想要一个“自动”范围,你可以使用:joins( association_name ).where( reflect_on_association( association_name ).klass.arel_table[column].send predicate, value )。但是,您应该意识到这种设计有点危险,因为它的可预测性较差,因此您应该过滤发送给这种方法的参数。
    • 附注:如果您想了解更多关于reflect_on_association 的信息,我认为最好的地方是源代码:github.com/rails/rails/blob/3-2-stable/activerecord/lib/…
    【解决方案2】:

    那么,您希望最终用户可以在运行时指定动态查询(并且可以存储和检索以供以后使用)?

    我认为你在正确的轨道上。唯一的细节是您如何建模和存储您的标准。我不明白为什么以下方法不起作用:

    def self.using_conditions(conditions)
      joins(:car)
      crit = conditions.each_with_object({}) {|(field, op, value), m|
        m["#{field} #{op} ?"] = value
      }
      where crit.keys.join(' AND '), *crit.values
    end
    

    CAVEAT 上述代码按原样 不安全且容易发生 SQL 注入。

    此外,没有简单的方法来指定 ANDOR 条件。最后,大部分简单的"#{field} #{op} ?", value 仅适用于数字字段和二元运算符。

    但这说明这种方法是可行的,只是还有很大的改进空间。

    【讨论】:

    • 是的,你的措辞正是我所需要的。我也担心SQL注入,做这样的事情......有没有最佳实践/安全的方式来做到这一点?我唯一能想到的就是对输入的字段进行某种验证,以确保它们不包含任何类型的 SQL 注入
    • 我们使用 SQL 参数 (?) 的事实已经为value 的参数注入 SQL 提供了一定程度的防御。如果您对它进行建模,使得用户无法直接提供或编辑 fieldop 值,那么您可以实现进一步的强化。例如,不要直接将op 用作#{op},而是使用整数(或符号)op_code 和内部查找表,即#{ops[op_code]}。如果您使用的是一组有限的字段,则可以对字段名称使用相同的技术。
    • 我喜欢运算符查找表的想法...我要试一试
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-13
    • 1970-01-01
    • 1970-01-01
    • 2013-11-26
    • 1970-01-01
    • 2019-07-22
    • 1970-01-01
    相关资源
    最近更新 更多