Ad

Why Is My Rails 7 Join Table Not Being Created Until I Enter Console Commands?

- 1 answer

I created a table for items and a table for jobs. I then created a join table, Items Jobs.

This is my migration for the join table and the models:

class CreateItemsJobs < ActiveRecord::Migration[7.0]
  def change
    create_table :items_jobs, id: false do |t|
      t.belongs_to :item
      t.belongs_to :job

      t.timestamps
    end
  end
end

class Item < ApplicationRecord
  belongs_to :part
  belongs_to :employee, optional: true

  has_and_belongs_to_many :jobs
end    

class Job < ApplicationRecord
  belongs_to :client
  belongs_to :employee

  has_and_belongs_to_many :items
end    

class ItemsJobs < ApplicationRecord
    belongs_to :item
    belongs_to :job
end

I then migrate successfully...

rails db:migrate ==>

== 20220210032352 CreateItemsJobs: migrating ==================================
-- create_table(:items_jobs, {:id=>false})
   -> 0.0085s
== 20220210032352 CreateItemsJobs: migrated (0.0086s) =========================

But if I try to seed, I get an error. If I run my rails console, I can't add to the table until I attempt to view a table that doesn't exist.

rails c ==>
Loading development environment (Rails 7.0.1)

2.7.4 :001 > ItemsJobs.all
Traceback (most recent call last):
(irb):1:in `<main>': uninitialized constant ItemsJobs (NameError)
Did you mean?  ItemJob  
    
2.7.4 :002 > ItemJob.all
Traceback (most recent call last):
(irb):2:in `<main>': uninitialized constant ItemJob (NameError)
Did you mean?  ItemsJobs

2.7.4 :003 > ItemsJobs.all
  ItemsJobs Load (0.4ms)  SELECT "items_jobs".* FROM "items_jobs"
 => []  

2.7.4 :004 > ItemsJobs.create(item_id: 1, job_id: 1)
  TRANSACTION (0.2ms)  BEGIN1 LIMIT $2  [["id", 1], ["LIMIT", 1]]                            
  Job Load (0.3ms)  SELECT "jobs".* FROM "jobs" WHERE "jobs"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]                                
  ItemsJobs Create (0.6ms)  INSERT INTO "items_jobs" ("item_id", "job_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4)  [["item_id", 1], ["job_id", 1], ["created_at", "2022-02-10 15:23:44.127164"], ["updated_at", "2022-02-10 15:23:44.127164"]]                                 
  TRANSACTION (1.1ms)  COMMIT                                    
 =>                                                              
#<ItemsJobs:0x00007f33c0aa7a80                                   
 item_id: 1,                                                     
 job_id: 1,                                                      
 created_at: Thu, 10 Feb 2022 15:23:44.127164000 UTC +00:00,     
 updated_at: Thu, 10 Feb 2022 15:23:44.127164000 UTC +00:00>

What is going wrong here? Why can't I view/add to the ItemsJobs table until I've attempted to view the suggested, non-existent ItemJob table?

Ad

Answer

You're mixing up has_and_belongs_to_many and has_many through: and the weird autoloading behavior is most likely because the naming scheme is throwing off how ActiveRecord maps classes to database tables through convention over configuration. The rule of thumb is that ActiveRecord expects models (class names) to be singular and tables to be plural.

HABTM assocations don't use a model for the join table. Rather its just a "headless" assocation where you just implicitly add/remove rows through the assocations on each end. HABTM is really only good for the case where you know that you won't need to query the table directly or access any additional columns - in other words it's quite useless outside of that niche. HABTM is the only place in ActiveRecord where you use the plural_plural naming scheme for tables - using that scheme with a model will cause constant lookup errors unless you explicitly configure everything.

If you want to setup an assocation with a join model (I would recommend this) you need to name the class and tables correctly and use an indirect assocation:

class CreateItemJobs < ActiveRecord::Migration[7.0]
  def change
    # table name should be singular_plural
    create_table :item_jobs do |t|
      t.belongs_to :item
      t.belongs_to :job
      t.timestamps
    end
  end
end

# model class names should always be singular
class ItemJob < ApplicationRecord
  belongs_to :item
  belongs_to :job
end

class Item < ApplicationRecord
  belongs_to :part
  belongs_to :employee, optional: true
  has_many :item_jobs
  has_many :jobs, through: :item_jobs
end    

class Job < ApplicationRecord
  belongs_to :client
  belongs_to :employee
  has_many :item_jobs
  has_many :items, through: :item_jobs
end    

Since you probally haven't run the migration in production and don't have any meaningful data in the table you can roll the bad CreateItemsJobs migration back and delete it. rails g model ItemJob item:belongs_to job:belongs_to will create both the model and migration.

See:

Ad
source: stackoverflow.com
Ad