Rails: Join on polymorphic association
I recently had to come up with a query that involved joining on a polymorphic association, so naturally I Googled how best to implement such a solution and happened upon the following 2 articles:
and
and based on those articles, I chose to go with the scoped belongs_to
solution.
Let’s take Ana’s example to illustrate what I mean. Given that we have the following models:
class Booking < ApplicationRecord
belongs_to :bookable, polymorphic: true
endclass Accommodation < ApplicationRecord
has_many :bookings, as: :bookable
endclass Office < ApplicationRecord
has_many :bookings, as: :bookable
end
we can update the Booking
model to:
class Booking < ApplicationRecord
belongs_to :bookable, polymorphic: true
belongs_to :accommodation, -> { where(bookings: { bookable_type: 'Accommodation' }) }, foreign_key: 'bookable_id'
belongs_to :office, -> { where(bookings: { bookable_type: 'Office' }) }, foreign_key: 'bookable_id'
end
and now we can create a query that includes a nested join based on the association between Booking
and one of the bookable
models.
Example:
class User < ApplicationRecord
has_many :bookings
endUser.joins(bookings: [ :office ])
.where(offices: { zip_code: 90210 })
Cool.
However, when implementing this solution, I ran into an issue that was not mentioned in those articles. My tests started failing with an error that looks like this:
ActiveRecord::StatementInvalid:
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "bookings"
LINE 1: ...bookings" WHERE "office"."id" = 4484 AND ...
^
# [REDACTED]
# -e:1:in `<main>'
# ------------------
# --- Caused by: ---
# PG::UndefinedTable:
# ERROR: missing FROM-clause entry for table "bookings"
# LINE 1: ...bookings" WHERE "office"."id" = 4484 AND ...
#
One weird thing I noticed was that the error contains references to FactoryBot
and wondered what the connection was. It wasn’t obvious at first why my tests were suddenly failing, when they weren’t even referencing this new association or new code.
After staring at the issue for a couple of hours, and trying to track down the cause, I remembered that one thing that tends to cause headaches with belongs_to
association is their required
nature by default since Rails 5. So, I made a simple adjustment, I added the optional: true
option to the association:
class Booking < ApplicationRecord
belongs_to :bookable, polymorphic: true
...
belongs_to :office, -> { where(bookings: { bookable_type: 'Office' }) }, foreign_key: 'bookable_id', optional: true
end
and everything went back to working as usual.
What I figured was that the original code was still creating a Booking
record with a bookable
association and will never directly reference the office
association in the creation/updating process. Which means that marking that association as optional was the right approach.
I hope this helps.
Cheers!