Повний посібник по асоціаціям один-до-одного в Ruby on Rails (has_one та belongs_to)

У цьому посібнику ми розглянемо один-до-одного асоціації в Rails за допомогою has_one та belongs_to. Ми охопимо:

1️⃣ Вступ

2️⃣ Коли використовувати has_one та belongs_to
3️⃣ Налаштування один-до-одного асоціації
4️⃣ Визначення асоціацій
5️⃣ Створення та доступ до даних
6️⃣ Налаштування асоціацій
7️⃣ Запит один-до-одного асоціацій
8️⃣ Обробка видалення (dependent опція)
9️⃣ Повний приклад з міграціями
🔟 Висновок

1️⃣ Вступ

У Rails один-до-одного відношення означає, що один запис в таблиці пов'язаний з рівно одним записом в іншій таблиці.
Це реалізовано за допомогою has_one та belongs_to.

2️⃣ Коли використовувати has_one та belongs_to

| Асоціація | Модель | Чужий ключ? | Мета |
| --- | --- | --- | --- |
| belongs_to | Дочірня модель | ✅ Так | Вказує, що модель належить іншій моделі. |
| has_one | Батьківська модель | ❌ Ні | Вказує, що модель має один пов'язаний запис. |

Приклад: Користувач та Профіль

  • Користувач має один профіль (has_one)
  • Профіль належить користувачу (belongs_to і зберігає чужий ключ user_id)

3️⃣ Налаштування один-до-одного асоціації

Крок 1: Створення моделей

Запустіть наступну команду для генерації моделей User та Profile:

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

user:references автоматично додає чужий ключ user_id до таблиці profiles.

Крок 2: Міграція бази даних

rails db:migrate

4️⃣ Визначення асоціацій

Модель Користувача (has_one)

class User < ApplicationRecord  
 has_one :profile, dependent: :destroy  
end
  • has_one :profile → Користувач має один профіль.
  • dependent: :destroy → Видаляє профіль, коли користувач видаляється.

Модель Профілю (belongs_to)

class Profile < ApplicationRecord  
 belongs_to :user  
end

belongs_to :user → Профіль належить користувачу (містить чужий ключ user_id).

5️⃣ Створення та доступ до даних

Створення користувача з профілем

user = User.create(name: "Alice", email: "[email protected]")  
profile = Profile.create(bio: "Software Developer", user: user)

або за допомогою Active Record Association Helper:

user.create_profile(bio: "Software Developer")

Доступ до пов'язаних записів

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

Видалення пов'язаних записів

user.destroy # Видаляє користувача та пов'язаний профіль завдяки `dependent: :destroy`

6️⃣ Налаштування асоціацій

Користувацький чужий ключ

За замовчуванням, Rails використовує {model}_id (наприклад, user_id), але ви можете змінити його:

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

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

Користувацька назва таблиці

Якщо назва таблиці не відповідає стандартам Rails, вкажіть її вручну:

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

Користувацький первинний ключ

Якщо User використовує uuid замість id як первинний ключ:

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️⃣ Запит один-до-одного асоціацій

Попереднє завантаження даних (includes)

Уникайте запитів типу N+1:

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

Об'єднання таблиць (joins)

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

Пошук пов'язаних записів

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

8️⃣ Обробка видалення (dependent опція)

| Опція | Поведінка |
| --- | --- |
| dependent: :destroy | Видаляє пов'язаний профіль, коли користувач видаляється |
| dependent: :nullify | Встановлює user_id у profiles на NULL |
| dependent: :restrict_with_error | Запобігає видаленню користувача, якщо профіль існує |

Приклад:

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

9️⃣ Повний приклад з міграціями

Міграція для 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

Міграція для 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

🔟 Висновок

✅ Основні моменти

  • Використовуйте has_one на батьківській моделі (не зберігає чужий ключ).
  • Використовуйте belongs_to на дочірній моделі (зберігає чужий ключ).
  • Налаштуйте чужі ключі, назви таблиць та первинні ключі, якщо це необхідно.
  • Використовуйте dependent: :destroy для управління видаленням.
  • Оптимізуйте запити за допомогою includes, joins, та попереднього завантаження (eager loading).

Перекладено з: Ruby on Rails One-to-One Association (hasone & belongsto) - Complete Guide