Creating a many to many relationship in Rails
Just complementing coreyward's answer above:
If you already have a model that has a belongs_to
, has_many
relation and you want to create a new relation has_and_belongs_to_many
using the same table you will need to:
rails g migration CreateJoinTableUsersCategories users categories
Then,
rake db:migrate
After that, you will need to define your relations:
User.rb:
class Region < ApplicationRecord
has_and_belongs_to_many :categories
end
Category.rb
class Facility < ApplicationRecord
has_and_belongs_to_many :users
end
In order to populate the new join table with the old data, you will need to in your console:
User.all.find_each do |u|
Category.where(user_id: u.id).find_each do |c|
u.categories << c
end
end
You can either leave the user_id
column and category_id
column from the Category and User tables or create a migration to delete it.
The most popular is 'Mono-transitive Association', you can do this:
class Book < ApplicationRecord
has_many :book_authors
has_many :authors, through: :book_authors
end
# in between
class BookAuthor < ApplicationRecord
belongs_to :book
belongs_to :author
end
class Author < ApplicationRecord
has_many :book_authors
has_many :books, through: :book_authors
end
A has_many :through association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding through a third model. For example, consider a medical practice where patients make appointments to see physicians. Ref.: https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
You want a has_and_belongs_to_many
relationship. The guide does a great job of describing how this works with charts and everything:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
You will end up with something like this:
# app/models/category.rb
class Category < ActiveRecord::Base
has_and_belongs_to_many :users
end
# app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :categories
end
Now you need to create a join table for Rails to use. Rails will not do this automatically for you. This is effectively a table with a reference to each of Categories and Users, and no primary key.
Generate a migration from the CLI like this:
bin/rails g migration CreateCategoriesUsersJoinTable
Then open it up and edit it to match:
For Rails 4.0.2+ (including Rails 5.2):
def change
# This is enough; you don't need to worry about order
create_join_table :categories, :users
# If you want to add an index for faster querying through this join:
create_join_table :categories, :users do |t|
t.index :category_id
t.index :user_id
end
end
Rails < 4.0.2:
def self.up
# Model names in alphabetical order (e.g. a_b)
create_table :categories_users, :id => false do |t|
t.integer :category_id
t.integer :user_id
end
add_index :categories_users, [:category_id, :user_id]
end
def self.down
drop_table :categories_users
end
With that in place, run your migrations and you can connect Categories and Users with all of the convenient accessors you're used to:
User.categories #=> [<Category @name="Sports">, ...]
Category.users #=> [<User @name="UserA">, ...]
User.categories.empty?