Reputation: 553
I've faced unusual situation with rails association and created simple sample to show that. Assume we have 4 models: 1) list.rb
class List < ApplicationRecord;end
2) show.rb
class Show < List;end
3) joined_table.rb
class JoinedTable < ApplicationRecord
belongs_to :listable, polymorphic: true
belongs_to :work
end
4) work.rb
class Work < ApplicationRecord
has_many :joined_tables, dependent: :destroy
has_many :lists, through: :joined_tables, source: :listable, source_type: 'List'
has_many :shows, through: :joined_tables, source: :listable, source_type: 'Show'
end
Thus we have next schema.rb
create_table "joined_tables", force: :cascade do |t|
t.integer "listable_id"
t.string "listable_type"
t.integer "work_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "lists", force: :cascade do |t|
t.string "type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "works", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Now when you got all the info, could anybody explain me why when I'm trying
Work.first.shows
it generates
Show Load (0.2ms) SELECT "lists".* FROM "lists" INNER JOIN "joined_tables" ON "lists"."id" = "joined_tables"."listable_id" WHERE "lists"."type" IN ('Show') AND "joined_tables"."work_id" = ? AND "joined_tables"."listable_type" = ? [["work_id", 1], ["listable_type", "Show"]]
that looks like a proper request, but when I'm trying to do
Work.first.lists
it generates
List Load (0.1ms) SELECT "lists".* FROM "lists" INNER JOIN "joined_tables" ON "lists"."id" = "joined_tables"."listable_id" WHERE "joined_tables"."work_id" = ? AND "joined_tables"."listable_type" = ? [["work_id", 1], ["listable_type", "List"]]
which IS NOT THE SAME and missing checking lists.type.
I know that is poor architecture and one probably should use scope on associations in Work leaving aside polymorphism in JoinedTable, but that's just something I've faced in project that came up to me. Also I'd love to hear alternative solutions to that, but most of all I'd like to know why rails generates different queries.
P.S. In example I'm using rails 5 and SQLite. But in real project using rails 4 and postgres it's even worse. If I do first Work.first.shows and then Work.first.lists it generates
List Load (0.4ms) SELECT "lists".* FROM "lists" INNER JOIN "list_works" ON "lists"."id" = "list_works"."listable_id" WHERE "list_works"."work_id" = $1 AND "list_works"."listable_type" IN ('List', 'Show') [["work_id", 50]]
but if I do Work.first.lists at once it generates
List Load (0.8ms) SELECT "lists".* FROM "lists" INNER JOIN "list_works" ON "lists"."id" = "list_works"."listable_id" WHERE "list_works"."work_id" = $1 AND "list_works"."listable_type" = 'List' [["work_id", 50]]
Thank you for help!
Upvotes: 1
Views: 74
Reputation: 7034
Work.first.lists
doesn't generate lists.type
because List
is the base class in the hierarchy, so lists are all records of all types in the lists
table. Also, keep in mind that all List
records will have type = nil
in DB, since List
is the base class.
Furthermore, when you use polymorphic association with STI, it by default always assigns the name of the base class to the *_type
column of polymorphic association. In your case listable_type
of JoinedTable
will always be "List"
The second problem is because when you do Work.first.lists
first, Rails doesn't know that there is the child Show
class, since it hasn't been loaded yet at that moment. You should whether make it autoload by accessing it (it happened when you did Work.first.shows
), or use something like require_dependency
.
Here is a working solution for you:
class Work < ApplicationRecord
has_many :joined_tables, dependent: :destroy
has_many :lists, -> { where(type: nil) }, through: :joined_tables, source: :listable, source_type: "List"
has_many :shows, -> { where(type: "Show") }, through: :joined_tables, source: :listable, source_type: "List"
end
Upvotes: 1