rom-sql
rom-sql copied to clipboard
Many-to-many associations work incorrectly when there is similar foreign key
Describe the bug
I have table books with many-to-many relationship with authors via book_authors. However, I also have a column author_id on books to denote the main author.
When querying associations using through:, rom-sql uses the column author_id to combine the associations when query, results in incorrect output.
To Reproduce
Run the below script
require 'rom'
rom = ROM.container(:sql, 'sqlite::memory') do |config|
config.default.create_table(:books) do
primary_key :id
foreign_key :author_id, :authors
end
config.default.create_table(:authors) do
primary_key :id
end
config.default.create_table(:book_authors) do
primary_key :id
foreign_key :book_id, :books
foreign_key :author_id, :authors
end
config.relation(:books) do
schema(infer: true) do
associations do
has_many :book_authors
end
end
auto_struct true
end
config.relation(:authors) do
schema(infer: true) do
associations do
has_many :books, through: :book_authors
end
end
auto_struct true
end
config.relation(:book_authors) do
schema(infer: true) do
associations do
belongs_to :book
belongs_to :author
end
end
auto_struct true
end
end
class AuthorRepo < ROM::Repository[:authors]
def all_with_books
root.combine(books: :book_authors).to_a
end
end
gateway = rom.gateways[:default]
author_repo = AuthorRepo.new(container: rom)
gateway[:authors].insert(id: 1)
gateway[:authors].insert(id: 2)
gateway[:books].insert(id: 1, author_id: 2)
gateway[:books].insert(id: 2, author_id: 2)
gateway[:book_authors].insert(id: 1, book_id: 1, author_id: 1)
gateway[:book_authors].insert(id: 2, book_id: 2, author_id: 2)
gateway[:book_authors].insert(id: 3, book_id: 1, author_id: 2)
puts author_repo.all_with_books.inspect
# output:
# [#<ROM::Struct::Author id=1 books=[]>, #<ROM::Struct::Author id=2 books=[#<ROM::Struct::Book id=1 author_id=2 book_authors=[#<ROM::Struct::BookAuthor id=1 book_id=1 author_id=1>, #<ROM::Struct::BookAuthor id=3 book_id=1 author_id=2>]>, #<ROM::Struct::Book id=1 author_id=2 book_authors=[#<ROM::Struct::BookAuthor id=1 book_id=1 author_id=1>, #<ROM::Struct::BookAuthor id=3 book_id=1 author_id=2>]>, #<ROM::Struct::Book id=2 author_id=2 book_authors=[#<ROM::Struct::BookAuthor id=2 book_id=2 author_id=2>]>]>]
Expected behavior
Author 1 should also have one book from the book_authors record 1
My environment
- Affects my production application: YES
- Ruby version: 3.3
- OS: MacOS