Search code examples
ruby-on-railspostgresqlrails-activerecordhas-and-belongs-to-many

ActiveRecord::StatementInvalid on two namespaced models


I have to two models in the same namespace which have a habtm relation.

class Resource::Item < ApplicationRecord
    has_and_belongs_to_many :resource_sets, foreign_key: 'resource_item_id', class_name: 'Resource::Set', table_name: 'resource_items_sets'
end

class Resource::Set < ApplicationRecord
    has_and_belongs_to_many :resource_items, foreign_key: 'resource_set_id', class_name: 'Resource::Item', table_name: 'resource_items_sets'
end

The migration has been generated with rails g migration CreateJoinTableResourceItemsResourceSets resource_item resource_set

class CreateJoinTableResourceItemsResourceSets < ActiveRecord::Migration[5.2]
    def change
        create_join_table :resource_items, :resource_sets do |t|
            # t.index [:resource_item_id, :resource_set_id]
            # t.index [:resource_set_id, :resource_item_id]
        end
    end
end

So far everything looks great. The table resource_items_sets is being created with the two columns resource_item_id and resource_set_id.

This is the schema

create_table "resource_items_sets", id: false, force: :cascade do |t|
    t.bigint "resource_item_id", null: false
    t.bigint "resource_set_id", null: false
end

After creating an resource_item I get the following which is as expected.

pry(main)> Resource::Item.first.resource_sets
=> #<Resource::Set::ActiveRecord_Associations_CollectionProxy:0x3fdd08748004>

But doing the following throws an error. I was expecting 0.

pry(main)> Resource::Item.first.resource_sets.count
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR:  column resource_items_sets.set_id does not exist
LINE 1: ...N "resource_items_sets" ON "resource_sets"."id" = "resource_...
                                                         ^
: SELECT "resource_sets"."id" FROM "resource_sets" INNER JOIN "resource_items_sets" ON "resource_sets"."id" = "resource_items_sets"."set_id" WHERE "resource_items_sets"."resource_item_id" = $1 ORDER BY "resource_sets"."name" ASC
from /Users/username/.rvm/gems/ruby-2.6.0/gems/activerecord-5.2.2/lib/active_record/connection_adapters/postgresql_adapter.rb:677:in `async_prepare'
Caused by PG::UndefinedColumn: ERROR:  column resource_items_sets.set_id does not exist
LINE 1: ...N "resource_items_sets" ON "resource_sets"."id" = "resource_...

Where does set_id come from when resource_set_id has been declare everywhere? How can I fix this issue? I want to keep the namespaces for both since I might end up creating items and set for more namespaces.

Thanks so much, guys!


Solution

  • You need to set the foreign keys on both sides of the join table because they can't be inferred in your case.

    In this case the correct has_and_belongs_to_many calls should look like:

      has_and_belongs_to_many :resource_items,
                              foreign_key: 'resource_set_id',
                              association_foreign_key: 'resource_item_id',
                              class_name: 'Resource::Item',
                              join_table: 'resource_items_sets'
    end
    

    and

    class Resource::Item < ApplicationRecord
      has_and_belongs_to_many :resource_sets,
                              foreign_key: 'resource_item_id',
                              association_foreign_key: 'resource_set_id',
                              class_name: 'Resource::Set',
                              join_table: 'resource_items_sets'
    end
    

    Note the added association_foreign_key option specifying the other foreign key in the join table.