Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a many to many relationship in Rails

This is a simplified example of what I am trying to achieve, I'm relatively new to Rails and am struggling to get my head around relationships between models.

I have two models, the User model and the Category model. A user can be associated with many categories. A particular category can appear in the category list for many users. If a particular category is deleted, this should be reflected in the category list for a user.

In this example:

My Categories table contains five categories:

 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ID | Name                       | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 1  | Sports                     |  | 2  | News                       | | 3  | Entertainment              | | 4  | Technology                 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

My Users table contains two users:

 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ID | Name                       | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 1  | UserA                      |  | 2  | UserB                      | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

UserA may choose Sports and Technology as his categories

UserB may choose News, Sports and Entertainment

The sports category is deleted, both UserA and UserB category lists reflect the deletion

I've toyed around with creating a UserCategories table which holds the ids of both a category and user. This kind of worked, I could look up the category names but I couldn't get a cascading delete to work and the whole solution just seemed wrong.

The examples of using the belongs_to and has_many functions that I have found seem to discuss mapping a one-to-one relationship. For example, comments on a blog post.

  • How do you represent this many-to-many relationship using the built-in Rails functionality?
  • Is using a separate table between the two a viable solution when using Rails?
like image 448
fletcher Avatar asked Feb 25 '11 17:02

fletcher


People also ask

How would you choose between Belongs_to and Has_one?

They essentially do the same thing, the only difference is what side of the relationship you are on. If a User has a Profile , then in the User class you'd have has_one :profile and in the Profile class you'd have belongs_to :user . To determine who "has" the other object, look at where the foreign key is.

What is ActiveRecord in Ruby on Rails?

Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database.


2 Answers

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? 
like image 191
coreyward Avatar answered Sep 21 '22 06:09

coreyward


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

like image 28
Darlan D. Avatar answered Sep 25 '22 06:09

Darlan D.