Rails 4 Migration: Has_And_Belongs_To_Many Table Name

Rails 4 Migration: has_and_belongs_to_many table name

I fixed the problem by adding the join_table name to every model like:

has_and_belongs_to_many :foo_urls,  :join_table => :foo_clips_foo_urls

Rails changing table name in has_and_belongs_to_many

You have to follow the Rails naming convention for naming the joining table while using has_and_belongs_to_many. Alternatively, you can specify a joining table name using join_table option.

In your particular case, change the stage_batches_and_stage_items's name to stage_batches_stage_items. By default, the joining table has both table names (pluralized) in lexical order.

From the Rails guide [Section 3.3.2]:

If you create a has_and_belongs_to_many association, you need to
explicitly create the joining table. Unless the name of the join table
is explicitly specified by using the :join_table option, Active Record
creates the name by using the lexical book of the class names. So a
join between author and book models will give the default join table
name of "authors_books" because "a" outranks "b" in lexical ordering.

I would recommend avoiding has_and_belongs_to_many if this is not a hard requirement due to it's limitations (e.g., you can't add any extra columns to this joining table.) Instead, use has_many :through association. In that case, you can customize the joined table name easily. Also, you can add extra columns to the joining table as you need.

Here is how the code will look like using has_many :through association:

class StageItem < ApplicationRecord
has_many :stage_item_batches
has_many :stage_batches, through: :stage_item_batches
end

# joining model
class StageItemBatch < ApplicationRecord
belongs_to :stage_item
belongs_to :stage_batch
end

class StageBatch < ApplicationRecord
has_many :stage_item_batches
has_many :stage_items, through: :stage_item_batches
end

The migrations for original two models will stay the same. You just need a migration for the joining model:

rails g model StageItemBatch stage_item:references stage_batch:references

has_and_belongs_to_many Association table name

HABTM

has_and_belongs_to_many associative tables generally have the naming convention of [plural_first_alphabetical_model]_[plural_second_alphabetical_model] with any shared naming prepended

Whilst this is the Rails standard, you can change the name to be your own by using the join_table argument:

#app/models/item.rb
class Item < ActiveRecord::Base
has_and_belongs_to_many :categories, join_table: "test"
end

#app/models/category.rb
class Category < ActiveRecord::Base
has_and_belongs_to_many :items, join_table: "test"
end

You can see how this works here:

If you create a has_and_belongs_to_many association, you need to
explicitly create the joining table. Unless the name of the join table
is explicitly specified by using the :join_tableoption, Active Record
creates the name by using the lexical order of the class names. So a
join between customer and order models will give the default join
table name of "customers_orders" because "c" outranks "o" in lexical
ordering.

--

has_many :through

has_many :through tables are generally called [alphabetical_model_name_singular]_[alphabetical_model_name_plural]

The difference with has_many :through is that since there's a model behind this association, you need to appreciate that your table will be tied to the model itself; meaning you can actually call the join table anything you want / need


Recommendation

I would actually recommend you remove the "menu" from your models, unless, of course, they are part of a much broader spectrum of data.

The problem you have is that you're going to have to call menu_ every time you wish to call the associations or data:

@item = MenuItem.find 1
@item.menu_categories

This is too verbose. You'll be MUCH better keeping your code DRY, allowing you to call the specific objects by their "natural" names:

#app/models/item.rb
Class Item < ActiveRecord::Base
has_and_belongs_to_many :categories
end

#app/models/category.rb
Class Category < ActiveRecord::Base
has_and_belongs_to_many :items
end

Your HABTM table, by default, will be categories_items, allowing you to call the following:

@item = Item.find 1
@item.categories

--

OOP

You have to remember that Ruby / Rails is object orientated, meaning that every time you create any functionality in your application, you basically have to craft it around the objects in your application

Without going into too much detail here, let me explain that one of the main problems you have is that you're basically shirking the object orientated process, by calling your objects menu_x and menu_y. You need to call objects by their "plain" name - a simple name which describes what they are, associating them if required with another module.

You goal of object orientated development is to do the likes of the following:

@item = Item.find 1
@item.switch_menus 2
@item.reload!

See how this is extremely "human"? OOP development needs to craft experience around objects - allowing you to manipulate & change the object as required

Rails has_and_belongs_to_many migration

You need to add a separate join table with only a restaurant_id and user_id (no primary key), in alphabetical order.

First run your migrations, then edit the generated migration file.

Rails 3

rails g migration create_restaurants_users_table

Rails 4:

rails g migration create_restaurants_users

Rails 5

rails g migration CreateJoinTableRestaurantUser restaurants users

From the docs:

There is also a generator which will produce join tables if JoinTable
is part of the name:


Your migration file (note the :id => false; it's what prevents the creation of a primary key):

Rails 3

class CreateRestaurantsUsers < ActiveRecord::Migration
def self.up
create_table :restaurants_users, :id => false do |t|
t.references :restaurant
t.references :user
end
add_index :restaurants_users, [:restaurant_id, :user_id]
add_index :restaurants_users, :user_id
end

def self.down
drop_table :restaurants_users
end
end

Rails 4

class CreateRestaurantsUsers < ActiveRecord::Migration
def change
create_table :restaurants_users, id: false do |t|
t.belongs_to :restaurant
t.belongs_to :user
end
end
end

t.belongs_to will automatically create the necessary indices. def change will auto detect a forward or rollback migration, no need for up/down.

Rails 5

create_join_table :restaurants, :users do |t|
t.index [:restaurant_id, :user_id]
end

Note: There is also an option for a custom table name that can be passed as a parameter to create_join_table called table_name. From the docs

By default, the name of the join table comes from the union of the
first two arguments provided to create_join_table, in alphabetical
order. To customize the name of the table, provide a :table_name
option:

Custom Join Table Name for Rails

How do I rename the second join table name?

Pass the table_name: option:

create_join_table :users, :books, table_name: :favorite_books do |t|
t.index [:user_id, :book_id]
end

You also need to use a unique names for the associations and tell rails whats going on since it can be derived from the name:

class User < ApplicationRecord
has_and_belongs_to_many :books
has_and_belongs_to_many :favorite_books,
join_table: 'favorite_books',
class_name: 'Book',
inverse_of: :favorite_books
end

class Book
has_and_belongs_to_many :users
# for lack of a better name?
has_and_belongs_to_many :favorite_users,
join_table: 'favorite_books',
class_name: 'User',
inverse_of: :favorite_books
end

The unique names are necessary since you would just be clobbering the previous associations if you used the same names.

But...

has_and_belongs_to_many and create_join_table are pretty useless as they won't let you access any of the additional columns on the table and don't provide such niceties like primary keys or timestamps. The documentation says:

Use has_and_belongs_to_many when working with legacy schemas or when you never work directly with the relationship itself.

But how are you ever supposed to know if you're going to want those features down the line? And you're stuck there with a table with just two foreign keys. Its a much better idea to go with has_many through: and switch to has_and_belongs_to_many if the memory usage ever becomes a problem (it ain't gonna happen).

TLDR; has_and_belongs_to_many sucks. Use has_many through: instead.

Rails migration for has_and_belongs_to_many join table

This HABTM section of the Associations Rails Guide is great, but doesn't explain exactly how to create a join table.

However, the Migrations Rails Guide explains how to make a join table:

The migration method create_join_table creates an HABTM (has and belongs to many) join table. A typical use would be:

create_join_table :products, :categories

By default, the name of the join table comes from the union of the first two arguments provided to create_join_table, in alphabetical order.

Rails 4 HABTM how to set multiple ids in console?

Ok, so it took me 3 days of all kinds of pain to work this out.

There was nothing wrong with my original code, except that I needed to remove the group_id from the user table.

roo's answer was correct, except that using 'group' as a variable name in the console confused Rails. This had led me to believe there was something wrong with my code, but there wasn't. You learn the hard way.

So, Students can be pushed into Groups like this:

  1. To push a student into one group:

    student = Student.first

OR

student = Student.find(1)

(or whatever number the id is)

group1 = Group.first

OR

group1 = Group.find(1)

student.groups << group1

  1. To push into multiple groups (which was the original goal of this whole debacle:

    student = Student.first

OR

student = Student.find(1)

allclasses = Group.all

student.groups << allclasses

To view your handywork:

student.groups

Works beautifully. The only problem I can see with my code is that it's possible to push the same student into a group twice, resulting in two duplicates of that student in one group. If anyone knows how to prevent this happening, I'm all ears.

Do I need to manually create a migration for a HABTM join table?

You should do this in a migration of one of the tables, or in a separate migration if those migrations have been ran:

create_table :articles_tags, :id => false do |t|
t.references :article, :tag
end

add_index :articles_tags, [:article_id, :tag_id]

This will create the table for you and the :id => false tells Rails not to add an id field to this table. There's an index also, which will speed up lookups for this join table.

You could also generate a model (ArticlesTag) for this and do:

# article.rb
has_many :articles_tags
has_many :tags, :through => :articles_tags

# tag.rb
has_many :articles_tags
has_many :articles, :through => :articles_tags

# article_tag.rb
belongs_to :tag
belongs_to :article

And then create the table in the migration generated from the script/generate model articles_tag call.

Inserting a value has_and_belongs_to_many associated table on Rails

To generate the migration:

rails g migration create_join_table :roles, :users

Then, you can simply save association like so:

@user.roles << @role

Where @user is User object and @role is Role object.

Note: Things that you should always keep in mind here is that join table names are arranged in alphabetical order, in this case roles should come before users, Hence: roles_users will be the table that should be created to make HABTM work as per Rails convention.



Related Topics



Leave a reply



Submit