Rails - найкраща практика: Як створити залежні відносини has_one


75

Не могли б ви сказати мені, яка найкраща практика створення відносин has_one?

fe, якщо у мене є модель користувача, і вона повинна мати профіль ...

Як я міг це досягти?

Одним із рішень було б:

# user.rb
class User << ActiveRecord::Base
  after_create :set_default_association

  def set_default_association
    self.create_profile
  end
end

Але це не здається дуже чистим ... Будь-яка пропозиція?

Відповіді:


125

Найкращою практикою створення відносини has_one є використання зворотного виклику ActiveRecord, before_createа не after_create. Або скористайтеся ще більш раннім зворотним викликом і вирішіть проблеми (якщо такі є) дитини, яка не пройшла власний етап перевірки.

Оскільки:

  • при хорошому кодуванні ви маєте можливість перевірки дочірнього запису відображатись користувачеві, якщо перевірки не вдаються
  • він чистіший і явно підтримується ActiveRecord - AR автоматично заповнює зовнішній ключ у дочірньому записі після збереження батьківського запису (при створенні). Потім AR зберігає дочірній запис як частину створення батьківського запису.

Як це зробити:

# in your User model...
has_one :profile
before_create :build_default_profile

private
def build_default_profile
  # build default profile instance. Will use default params.
  # The foreign key to the owning User model is set automatically
  build_profile
  true # Always return true in callbacks as the normal 'continue' state
       # Assumes that the default_profile can **always** be created.
       # or
       # Check the validation of the profile. If it is not valid, then
       # return false from the callback. Best to use a before_validation 
       # if doing this. View code should check the errors of the child.
       # Or add the child's errors to the User model's error array of the :base
       # error item
end

Чи можна цим керувати за допомогою одного рядка? -> before_filter: build_profile?
— BvuRVKyUVlViVIc7

2
@Lichtamberg: Так, але я б додав коментар: "Створює профіль за замовчуванням. ПОВИНЕН завжди перевіряти." ПРИМІТКА: це буде "before_create: build_profile", а не "before_filter". Якби це не перевірило, ви отримаєте дуже заплутане повідомлення про помилку для користувача. Або насправді це НЕ буде створено, що означало б, що у вас опиниться Користувач без профілю. Ви також повинні перевірити кутові випадки у своїх тестах.
— Larry K

Я спробував цей код, але він не вдався, мені довелося зробити profile = build_profile
— jakeonrails

1
Тоді як додати цей об’єкт до нового user, оскільки це так before_create?
— Meekohi

9
Тільки майте на увазі, що Rails 5 використовує функцію before_create, щоб створити залежний запис неможливо без перевизначення за замовчуванням запису принадлежи_to. За замовчуванням тепер очікується, що запис belong_to існує, інакше виникає помилка.
— В'єт

28

Ваше рішення - це, безумовно, гідний спосіб це зробити (принаймні, поки ви це не переростете), але ви можете спростити його:

# user.rb
class User < ActiveRecord::Base
  has_one      :profile
  after_create :create_profile
end

24

Якщо це нова асоціація у існуючій великій базі даних, я керуватиму переходом так:

class User < ActiveRecord::Base
  has_one :profile
  before_create :build_associations

  def profile
    super || build_profile(avatar: "anon.jpg")
  end

private
  def build_associations
    profile || true
  end
end

так що існуючі записи користувачів отримують профіль, коли про це запитують, і разом із ними створюються нові. Це також розміщує атрибути за замовчуванням в одному місці і працює коректно з accept_nested_attributes_for у Rails 4 і далі.


Це чудове рішення, але ми використовуємо супер || create_profile (аватар: "anon.jpg"), щоб переконатися, що новий профіль зберігається негайно.
— sandre89

1
@ sandre89 ви ризикуєте кошмаром зворотного дзвінка, винятками для перевірки та неправильно збереженими записами, а також, крім того, використовуючи метод збереження у зворотному виклику before_create. Авді Грімм написав / серію з чотирьох частин / на RubyTapas, висвітлюючи численні катастрофи, які чекають вас від того, щоб модельний шар зробив будь-яке самозбереження. Я бажаю тобі найкращого, але боюся гіршого.
— inopinatus

9

Можливо, це не найчистіше рішення, але ми вже мали базу даних із півмільйона записів, деякі з яких уже створили модель «Профіль», а деякі - ні. Ми застосували такий підхід, який гарантує, що модель профілю присутня в будь-який момент, без необхідності проходити і ретроактивно генерувати всі моделі профілю.

alias_method :db_profile, :profile
def profile
  self.profile = Profile.create(:user => self) if self.db_profile.nil?
  self.db_profile
end

5

Ось як я це роблю. Не впевнений, наскільки це стандартно, але він працює дуже добре, і він ледачий, оскільки не створює зайвих накладних витрат, якщо це не потрібно для побудови нової асоціації (я радий бути виправленим щодо цього):

def profile_with_auto_build
  build_profile unless profile_without_auto_build
  profile_without_auto_build
end

alias_method_chain :profile, :auto_build

Це також означає, що асоціація з’являється, як тільки вона вам потрібна. Я думаю, альтернативою є підключення до after_initialize, але це, здається, додає трохи накладних витрат, оскільки він запускається кожного разу, коли об'єкт ініціалізується, і можуть бути випадки, коли вам не байдуже отримати доступ до асоціації. Здається, марно витрачати перевірку на його існування.


Я думаю, що ця відповідь є кращим рішенням, ніж інші, оскільки уникайте проблем із перевіренням у моделі профілю. Дякую
— ole

Ви також можете автозберегти:has_one :profile, :autosave => true
— montrealmike

@montrealmike, чи це стосується відсутнього профілю для початку? Тобто, якщо хтось ще не виконав build_profile, чи буде це створювати при збереженні? Я також стикався з цим: github.com/phildionne/associates, який може запропонувати інший спосіб обійти багатомодельні форми.
— Brendon Muir

@BrendonMuir ні, це не створить відсутній профіль. Це просто те, що ви можете додати до свого рішення вище, щоб воно автоматично зберігало профіль при збереженні користувача (а також перевіряло профіль)
— montrealmike,


0

У мене була проблема з цим і accept_nested_attributes_for, тому що якщо вкладені атрибути були передані, пов'язана модель була створена там. Я закінчив робити

after_create :ensure_profile_exists
has_one :profile
accepts_nested_attributes_for :profile


def ensure_profile_exists
  profile || create_profile
end
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.