Ruby on Rails One-to-One Association (has_one
& belongs_to
) - Complete Guide
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_to
Child model✅ Yes Indicates that the model belongs to another model.has_one
Parent 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 storesuser_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: :destroy
Deletes associated profile when user is deleteddependent: :nullify
Sets user_id
in profiles
to NULLdependent: :restrict_with_error
Prevents 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.