'Parametrized join in Rails 4

I am doing manual join and I need to pass a parameter to its ON clause:

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

Is there a way to pass baz as a parameter, to avoid potential injection problems? There is a method sanitize_sql_array, but I'm not sure how to make use of it in this case.

Note: I can't use where because it's not the same.



Solution 1:[1]

With sanitize_sql_array, that would be:

# 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)

Tried it and it worked.

Solution 2:[2]

Active record models have sanitize class method, so you could do:

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

__

It's been removed starting rails 5.1, use ActiveRecord::Base.connection.quote() instead

Solution 3:[3]

Arel can do it.

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

This produces

SELECT FROM "foos"
LEFT OUTER JOIN "bars" ON
  "foos"."id" = "bars"."foo_id"
  AND "bars"."baz" = 'i am a baz'

Now if you want to get that back into an ActiveRecord query and not just print the SQL:

Foo.joins(join.join_sources)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Phil Frost