Rails 4中的参数化连接

22

我正在进行手动的join,并且需要将参数传递给其ON子句:

Foo.joins("LEFT OUTER JOIN bars ON foos.id = bars.foo_id AND bars.baz = #{baz}")

有没有一种方法可以将baz作为参数传递,以避免潜在的注入问题?有一个sanitize_sql_array的方法,但我不确定如何在这种情况下使用它。

注意:我不能使用where,因为它不一样。


所以baz是另一个连接的模型?你能告诉我你对baz的值是什么吗? - Meri Alvarado
是的,了解baz可能是什么将会很棒。 - Kulgar
baz是一个变量,保存着一个字符串字面值或者一个数字,例如。它不是另一个模型,而是一个我想要传递给外连接ON条件的值。 - Mladen Jablanović
5个回答

21

使用sanitize_sql_array,代码应该是这样的:

# Warning: sanitize_sql_array is a protected class method, be aware of that to properly use it in your code
ar = ["LEFT OUTER JOIN bars ON foos.id = bars.foo_id AND bars.baz = %s", baz]

# Within a class method in foo model:
sanitized_sql = sanitize_sql_array(ar)
Foo.joins(sanitized_sql)

尝试过了,它起作用了。


注意,我必须在我的模型类上访问 sanitize_sql_array,例如 Foo.sanitize_sql_array - Joshua Pinter
我觉得反过来说更准确:在旧版本中可以直接访问^^' 但是请注意,我在模型中使用了“sanitize_sql_array”。 我不记得我在测试时是否将其放在类方法中... - Kulgar
很有趣。你现在引起了我的兴趣,所以我快速进行了测试,你的直觉是正确的。因为它是一个类方法,只需要调用 sanitize_sql_array 就可以在类方法中使用它,但在实例方法中使用时,你需要从类中调用它,如 Foo.sanitize_sql_array。感谢你为我和其他人澄清了这一点。 :) - Joshua Pinter
1
@JoshuaPinter 谢谢你的测试,我刚刚编辑了我的答案并进行了澄清 ^^' - Kulgar
如果您需要从模型外部调用它,可以通过 ActiveRecord::Base 类来访问它: ActiveRecord::Base.sanitize_sql_array(['INNER JOIN foo on bar = ?', 123]) - zinovyev
显示剩余2条评论

13

Active record 模型有 sanitize 类方法,所以你可以这样做:

Foo.joins("LEFT OUTER JOIN bars ON foos.id = bars.foo_id AND bars.baz = #{Foo.sanitize(baz)}")

自rails 5.1版本开始,它已被移除,请改用ActiveRecord::Base.connection.quote()


3
对于最近的观众来说,这种方法已经不再适用了,因为 sanitize 不再是公共 API 的一部分,它在 Rails 5.1 中被移除了(请参阅https://github.com/rails/rails/issues/28947中的讨论和解决方法)。 - nerfologist

2
Arel能够完成这个任务。
baz = "i am a baz"
foos = Arel::Table.new('foos') # or Foo.arel_table, if this is an AR model
bars = Arel::Table.new('bars')
join = foos.outer_join(bars).on(
  foos[:id].eq(bars[:foo_id]),
  bars[:baz].eq(baz))
puts join.to_sql

这会产生:
SELECT FROM "foos"
LEFT OUTER JOIN "bars" ON
  "foos"."id" = "bars"."foo_id"
  AND "bars"."baz" = 'i am a baz'

现在,如果您想将其转换为ActiveRecord查询而不仅仅是打印SQL语句:
Foo.joins(join.join_sources)

那应该就是答案了。使用 Foo.arel_table 可以在代码中不使用表名。 - Mark Huk

-4

我认为你应该尝试这样做:

Foo.joins("LEFT OUTER JOIN bars ON foo.id = bars.foo_id AND bars.baz = ?", baz)

带更多参数:

Foo.joins("LEFT OUTER JOIN bars ON foo.id = ? AND bars.baz = ? AND ...", foo, baz, etc...)

9
不幸的是,这并不起作用。baz不会被解释为参数值,而是作为另一个连接模型。你可以自己尝试一下。https://github.com/rails/rails/blob/9b67cb3d394692aa7feb7510aab0871e557d3dd0/activerecord/lib/active_record/relation/query_methods.rb#L1036 - Mladen Jablanović
据我所知,即使在4.0版本,Rails也不支持在JOIN子句中使用参数。正如Mladen得出的结论,必须自己生成SQL语句。我认为@Kulgar说得对,可以安全地执行该操作。 - Old Pro
你确定 baz 是作为字符串字面量或整数传递的,而不是另一个 AR 对象吗? - Roman Kovtunenko

-5

如果您传递值的哈希,AR 将保护您免受 SQL 注入攻击,这样您就可以放心使用:

Foo.joins("LEFT OUTER JOIN bars ON foo.id = bars.foo_id").where(bars: {baz: baz})

在编写 SQL 语句时,不要使用容易受到 SQL 注入攻击的行字符串。


很遗憾,这不是我想要的。WHERE中的条件与ON中的条件不等价。 - Mladen Jablanović
4
由于使用了外连接,将条件放在JOIN子句中与放在WHERE子句中是不同的。详情请见http://sqlfiddle.com/#!9/c827b7/5。 - Old Pro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接