RoR Associations
Associations define relationships between models. They tell Rails how records in different tables relate to each other. With associations, you can easily navigate from a user to their posts, from a post to its comments, or from an order to its items — all without writing complex queries.
The Core Concept: Related Records
Real World Relationship Rails Association ------------------------------------------------- One author writes many posts User has_many :posts Each post belongs to one author Post belongs_to :user One post has many comments Post has_many :comments Each comment belongs to one post Comment belongs_to :post
belongs_to and has_many
These two always come as a pair. The model that holds the foreign key column uses belongs_to. The model on the "one" side uses has_many.
Database Tables: users table: id | name | email 1 | Alice | alice@example.com 2 | Bob | bob@example.com posts table: id | title | body | user_id 1 | "Rails Intro" | "..." | 1 ← belongs to user 1 (Alice) 2 | "MVC Guide" | "..." | 1 ← belongs to user 1 (Alice) 3 | "Ruby Tips" | "..." | 2 ← belongs to user 2 (Bob)
The user_id column in posts is the foreign key. It connects each post to a user.
Setting Up Associations in Models
app/models/user.rb class User < ApplicationRecord has_many :posts, dependent: :destroy end app/models/post.rb class Post < ApplicationRecord belongs_to :user end
Setting Up the Foreign Key with a Migration
rails generate migration AddUserToPosts user:references
This generates:
class AddUserToPosts < ActiveRecord::Migration[7.1]
def change
add_reference :posts, :user, null: false, foreign_key: true
end
end
Run rails db:migrate and the user_id column appears in the posts table.
Using Associations in Code
alice = User.find(1) # Get all posts written by Alice alice.posts # SQL: SELECT * FROM posts WHERE user_id = 1 # Get the count alice.posts.count # => 2 # Create a post that already belongs to Alice alice.posts.create(title: "New Post", body: "Content here") # Automatically sets user_id = 1 # Get the author of a post post = Post.find(1) post.user # => Returns the User object for Alice post.user.name # => "Alice"
Association Diagram
User (id: 1, name: "Alice")
|
| has_many
|
+-- Post (id: 1, user_id: 1, title: "Rails Intro")
| |
| | has_many
| |
| +-- Comment (id: 1, post_id: 1, body: "Great!")
| +-- Comment (id: 2, post_id: 1, body: "Thanks!")
|
+-- Post (id: 2, user_id: 1, title: "MVC Guide")
has_many :through
Use has_many :through when two models connect through a third model (a join table).
Example: Doctors have many Patients through Appointments class Doctor < ApplicationRecord has_many :appointments has_many :patients, through: :appointments end class Appointment < ApplicationRecord belongs_to :doctor belongs_to :patient end class Patient < ApplicationRecord has_many :appointments has_many :doctors, through: :appointments end
Database Tables: doctors: id | name patients: id | name appointments: id | doctor_id | patient_id | date Usage: doctor = Doctor.find(1) doctor.patients ← all patients this doctor has seen patient.doctors ← all doctors this patient has visited
has_one
Use has_one when a record has exactly one related record:
class User < ApplicationRecord has_one :profile end class Profile < ApplicationRecord belongs_to :user end user = User.find(1) user.profile ← the user's single profile user.profile.bio ← access the profile's bio user.create_profile(bio: "Developer from NYC")
The dependent Option
Control what happens to child records when the parent is deleted:
has_many :posts, dependent: :destroy ← deletes all posts when user is deleted has_many :posts, dependent: :nullify ← sets user_id to NULL (posts survive) has_many :posts, dependent: :restrict_with_error ← blocks deletion if posts exist
Always set dependent to avoid orphaned records floating in your database.
Querying Through Associations
# Find all posts for user 1 Post.where(user_id: 1) # Better — use the association user = User.find(1) user.posts # Filter through the association user.posts.where(published: true) user.posts.order(created_at: :desc) user.posts.limit(5) # Does Alice have any posts? user.posts.any? # true or false user.posts.empty? # true or false
Association Validations
By default, belongs_to requires the parent to exist. A post cannot be saved without a valid user_id.
post = Post.new(title: "Orphan Post") post.save # => false # Error: "User must exist"
To make the association optional:
belongs_to :user, optional: true
Associations remove the need to manually manage foreign keys and write JOIN queries. They let your code read like plain English: user.posts, post.user, doctor.patients.
