Let's say we're building an app that lists a bunch of jokes and allows user to upload their own. Now, imagine we have a new request to allow users to save a joke that they particularly liked so they can quickly look that up later.
Let's assume our app has the following models:
class User < ApplicationRecord has_many :jokes end class Joke < ApplicationRecord belongs_to :user end
When designing models and database schemas, I find it helpful to think about how I want the interaction between the models to work and then let that inform my schema design.
In this case, I'd want to be able to do three things.
1. Allow a user to save a joke
current_user.saved_jokes << @joke
2. Retrieve a user's saved jokes
@jokes = current_user.saved_jokes
3. Get list of users who have saved a joke
@users = @joke.saved_by # should return list of User records
Having considered these requirements, it's clear that we need a many-to-many relationship between the
Joke models. A user can have many saved jokes and a joke can be saved by many users.
Create a join table to model many-to-many relationships
To setup this many-to-many relationship, we need to first create a join table in the database (assuming a SQL database for this example).
Let's generate a migration to create a
jokes_saves join table.
rails g migration CreateJokesSaves user:references joke:references
The corresponding migration should be as follows:
class CreateJokesSaves < ActiveRecord::Migration[6.0] def change create_table :jokes_saves, id: false do |t| t.references :user, null: false, foreign_key: true t.references :joke, null: false, foreign_key: true t.timestamps end add_index :jokes_saves, [:user_id, :joke_id], unique: true end end
Make sure to pass
id: false to
create_table to tell Rails not to create a
id column. Apparently having IDs on join tables can end up causing very subtle issues with ActiveRecord!
Also, make sure to add a
unique index on the combined
joke_id columns to prevent users from favouriting a joke more than once.
has_and_belongs_to_many association between users and jokes
Now, all that is left is to configure the
class User < ApplicationRecord has_many :jokes has_and_belongs_to_many :saved_jokes, join_table: 'jokes_saves', class_name: 'Joke' end class Joke < ApplicationRecord belongs_to :user has_and_belongs_to_many :saved_by, join_table: 'jokes_saves', class_name: 'User' end
You can call the associations anything you want, just make sure the
class_name arguments are set correctly.
Use the associations
Now, it's time to bask in the magic of Rails. Open up the rails console by running
rails console and play around.
user = User.first joke = Joke.first # Save joke user.saved_jokes << joke # Fetch saved jokes user.saved_jokes # [joke] # Fetch list of users who saved a joke joke.saved_by # [user]