Ruby on Rails One-to-One Association (has_one & belongs_to) - Complete Guide

Umar Farooque Khan
3 min read4 hours ago

In this tutorial, we will explore one-to-one associations in Rails using has_one and belongs_to. We will cover:

1️⃣ Introduction
2️⃣ When to Use has_one and belongs_to
3️⃣ Setting Up One-to-One Association
4️⃣ Defining Associations
5️⃣ Creating & Accessing Data
6️⃣ Customizing Associations
7️⃣ Querying One-to-One Associations
8️⃣ Handling Deletion (dependent Option)
9️⃣ Full Example with Migrations
🔟 Conclusion

1️⃣ Introduction

In Rails, a one-to-one relationship means that one record in a table is associated with exactly one record in another table. This is implemented using has_one and belongs_to.

2️⃣ When to Use has_one and belongs_to

AssociationPlacementStores Foreign Key?Purposebelongs_toChild model✅ Yes Indicates that the model belongs to another model.has_oneParent model❌ No Specifies that a model has one associated record.

Example: User & Profile

  • A User has one Profile (has_one)
  • A Profile belongs to a User (belongs_to and stores user_id foreign key)

3️⃣ Setting Up One-to-One Association

Step 1: Create Models

Run the following command to generate User and Profile models:

rails g model User name:string email:string
rails g model Profile bio:text user:references

The user:references automatically adds a user_id foreign key to the profiles table.

Step 2: Migrate the Database

rails db:migrate

4️⃣ Defining Associations

User Model (has_one)

class User < ApplicationRecord
has_one :profile, dependent: :destroy
end
  • has_one :profile → A user has one profile.
  • dependent: :destroy → Deletes profile when user is deleted.

Profile Model (belongs_to)

class Profile < ApplicationRecord
belongs_to :user
end

belongs_to :user → Profile belongs to a user (holds user_id).

5️⃣ Creating & Accessing Data

Creating a User with a Profile

user = User.create(name: "Alice", email: "alice@example.com")
profile = Profile.create(bio: "Software Developer", user: user)

or using Active Record Association Helper:

user.create_profile(bio: "Software Developer")

Accessing Associated Records

user.profile.update(bio: "Senior Developer")

Destroying Associated Records

user.destroy  # Deletes user & associated profile due to `dependent: :destroy`

6️⃣ Customizing Associations

Custom Foreign Key

By default, Rails uses {model}_id (e.g., user_id), but you can change it:

class Profile < ApplicationRecord
belongs_to :user, foreign_key: "account_id"
end

class User < ApplicationRecord
has_one :profile, foreign_key: "account_id"
end

Custom Table Name

If the table name doesn’t match Rails conventions, specify it manually:

class Profile < ApplicationRecord
self.table_name = "user_profiles"
end

Custom Primary Key

If User uses uuid instead of id as primary key:

class User < ApplicationRecord
self.primary_key = "uuid"
has_one :profile, foreign_key: "user_uuid"
end

class Profile < ApplicationRecord
belongs_to :user, foreign_key: "user_uuid", primary_key: "uuid"
end

7️⃣ Querying One-to-One Associations

Preloading Data (includes)

Avoid N+1 queries:

User.includes(:profile).where(profiles: { bio: "Developer" })

Joining Tables (joins)

User.joins(:profile).where(profiles: { bio: "Developer" })

Finding Associated Records

User.find(1).profile
Profile.find(1).user

8️⃣ Handling Deletion (dependent Option)

OptionBehaviordependent: :destroyDeletes associated profile when user is deleteddependent: :nullifySets user_id in profiles to NULLdependent: :restrict_with_errorPrevents user deletion if profile exists

Example:

class User < ApplicationRecord
has_one :profile, dependent: :nullify
end

9️⃣ Full Example with Migrations

Migration for users

class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name
t.string :email
t.timestamps
end
end
end

Migration for profiles

class CreateProfiles < ActiveRecord::Migration[7.0]
def change
create_table :profiles do |t|
t.text :bio
t.references :user, null: false, foreign_key: true
t.timestamps
end
end
end

🔟 Conclusion

✅ Key Takeaways

  • Use has_one on the parent (does not store foreign key).
  • Use belongs_to on the child (stores foreign key).
  • Customize foreign keys, table names, and primary keys if needed.
  • Use dependent: :destroy to manage deletion.
  • Optimize queries with includes, joins, and eager loading.

--

--

Umar Farooque Khan
Umar Farooque Khan

Written by Umar Farooque Khan

Experienced software developer with a passion for clean code and problem-solving. Full-stack expertise in web development. Lifelong learner and team player.

No responses yet