Correct me if I’m wrong, but as far as I understand there’s still no official support in Rails 6.1 for adding an ORDER BY
clause using a column from a joined table. In Rails < 6.1 this wasn’t a huge problem because we could just “guess” the table alias and write raw SQL and most of the time the table would not be aliased anyway. This changed in Rails 6.1, however:
class Foo < ApplicationRecord
belongs_to :bar
end
class Bar < ApplicationRecord
end
Some example queries:
irb(main):001:0> Foo.joins(:bar).to_sql
(0.2ms) SELECT sqlite_version(*)
=> "SELECT \"foos\".* FROM \"foos\" INNER JOIN \"bars\" ON \"bars\".\"id\" = \"foos\".\"bar_id\""
irb(main):002:0> Foo.joins(:bar).where(bar: {id: [1, 2]}).to_sql
=> "SELECT \"foos\".* FROM \"foos\" INNER JOIN \"bars\" bar ON bar.\"id\" = \"foos\".\"bar_id\" WHERE \"bar\".\"id\" IN (1, 2)"
As you can see, the alias for Bar can now change based on the presence of a where
clause. The inconsistency means the ORDER BY
clause also has to change based on the presence of the where
clause. Here’s how raw SQL tends to fail:
irb(main):003:0> Foo.joins(:bar).where(bar: {id: [1, 2]}).order('bars.id DESC')
Foo Load (1.1ms) SELECT "foos".* FROM "foos" INNER JOIN "bars" bar ON bar."id" = "foos"."bar_id" WHERE "bar"."id" IN (?, ?) /* loading for inspect */ ORDER BY bars.id DESC LIMIT ? [[nil, 1], [nil, 2], ["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column: bars.id)
irb(main):004:0> Foo.joins(:bar).order('bar.id DESC')
Foo Load (1.0ms) SELECT "foos".* FROM "foos" INNER JOIN "bars" ON "bars"."id" = "foos"."bar_id" /* loading for inspect */ ORDER BY bar.id DESC LIMIT ? [["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column: bar.id)
Note: I am not sure why the /* loading for inspect */
comment is in the middle of the query.
Even the old tricks of feeding some ARel to the order
method or merging with another relation fail:
irb(main):005:0> Foo.joins(:bar).where(bar: {id: [1, 2]}).order(Bar.arel_table[:id].desc)
Foo Load (1.1ms) SELECT "foos".* FROM "foos" INNER JOIN "bars" bar ON bar."id" = "foos"."bar_id" WHERE "bar"."id" IN (?, ?) /* loading for inspect */ ORDER BY "bars"."id" DESC LIMIT ? [[nil, 1], [nil, 2], ["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column: bars.id)
irb(main):006:0> Foo.joins(:bar).where(bar: {id: [1, 2]}).merge(Bar.order(id: :desc))
Foo Load (1.1ms) SELECT "foos".* FROM "foos" INNER JOIN "bars" bar ON bar."id" = "foos"."bar_id" WHERE "bar"."id" IN (?, ?) /* loading for inspect */ ORDER BY "bars"."id" DESC LIMIT ? [[nil, 1], [nil, 2], ["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column: bars.id)
So… any chance we could get official support for ORDER BY
using columns from joined tables?
These queries have been a huge pain point when upgrading the app to every major version since Rails 3.1.
I apologize in advance if I missed some obvious way to get this to work in Rails 6.1.
Edit: I guess the obvious workaround is:
Foo.joins(:bar).where.not(bar: {id: nil}).order('bar.id DESC')
The where
clause will not filter anything but will force a consistent alias… still it doesn’t seem ideal to me.