我有一些代码想对ActiveRecord数据库执行一些原始SQL。这是一个复杂而奇怪的多表SQL(涉及一个postgres的递归CTE),所以基于特定模型的API似乎都不适合用于指定SQL。 它还需要接受一些参数,这些参数需要被适当地转义/消毒。
在某种程度上,我认为正确的方法是使用Model.connection.select_all ,这将创建一个参数化的准备语句。
我说的对吗?是否有更好的方法来做到这一点?该方法在Rails指南中被简要提及(展示了它是公共API!),但没有关于参数的很多细节。它的API文档非常有限,只是文档中提到。 **select_all**(arel, name = nil, binds = [], preparable: nil, async: false),"返回一个ActiveRecord::Result实例。" 没有解释参数的类型或语义。
在我之前的Rails 7上工作的代码中,调用看起来像。
MyModel.connection.select_all(
"select complicated_stuff WHERE something = $1",
"my_complicated_stuff_name",
[[nil, value_for_dollar_one_sub]],
preparable: true
)
- 是的,
binds的值很奇怪,是一个数组中的二重数组,其中二重数组的第一个值是nil?这在任何地方都没有记录,我可能是从某个地方得到的......也许是StackOverflow的几个答案之一。 - 老实说,我不知道
preparable: true,也不知道它有什么区别。
在Rails 7.0中,这开始失败了,出现了错误。TypeError: can't cast Array.
我根本找不到那个select_all all方法的任何文档,也找不到关于这个问题的其他讨论;我在Rails Changelog 中也找不到提到的任何select_all 的变化。 我试着去看实际的代码历史,但是迷失了方向。我猜测 "不能铸造数组 "是指那个奇怪的binds 值......但它应该是什么?
最后我想到了寻找这个方法的Rails测试,这些测试使用了binds 这个参数,并最终成功地找到了一个!
所以......好吧,我用新的binds 参数重写了这个方法,像这样。
bind = ActiveRecord::Relation::QueryAttribute.new(
"something",
value_for_dollar_one_sub,
ActiveRecord::Type::Value.new
)
MyModel.connection.select_all(
"select complicated_stuff WHERE something = $1",
"my_complicated_stuff_name",
[bind],
preparable: true
)
- 确认这不仅在Rails 7中有效,而且一直到Rails 5.2都没有问题。
- 我想我之前的做法是一些传统的传递参数的方式,在Rails 7中最终被删除了?
- 我还是不明白我在做什么。
ActiveRecord::Relation::QueryAttribute.new的第一个参数,我让它与要进行比较的SQL列相匹配,但我不知道这是否重要,或者它是否有什么用处。第三个参数似乎是一个ActiveRecord类型......我只是把它留在通用的ActiveRecord::Type::Value.new,这似乎对整数或字符串值都很好,不知道在什么情况下你想在这里使用一个特定的类型值,或者它有什么作用。 - 总的来说,我想知道是否有更好的方法让我做我正在做的事情?我觉得很奇怪,在互联网上找不到其他人遇到过这种情况......尽管有stackoverflow的答案建议采用这种方法......也许我做错了?
但无论如何,由于这个问题很难调试,在谷歌上也很难找到文档或解释,而且我发现在Rails 7中根本没有提到这个问题的变化/破坏......我想我应该把它写出来,这样别人就有机会找到这个答案了。