Rails - лучшая практика: как создать зависимые отношения has_one
Не могли бы вы рассказать мне, какая наилучшая практика для создания отношений has_one?
f.e. если у меня есть пользовательская модель, и у нее должен быть профиль...
Как я мог это сделать?
Одно из решений:
# user.rb
class User << ActiveRecord::Base
after_create :set_default_association
def set_default_association
self.create_profile
end
end
Но это не кажется очень чистым... Любые предложения?
Ответы
Ответ 1
Лучшей практикой создания отношения 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 errors to the User model error array of the :base
# error item
end
Ответ 2
Ваше решение, безусловно, достойный способ сделать это (по крайней мере, пока вы не перерасти его), но вы можете упростить его:
# user.rb
class User < ActiveRecord::Base
has_one :profile
after_create :create_profile
end
Ответ 3
Если это новая ассоциация в существующей большой базе данных, я буду управлять следующим образом:
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
так что существующие пользовательские записи получают профиль, когда его просят, и новые с ним создаются. Это также добавляет атрибуты по умолчанию в одном месте и корректно работает с accepts_nested_attributes_for в Rails 4 и далее.
Ответ 4
Наверное, это не самое чистое решение, но у нас уже была база данных с полмиллиона записей, некоторые из которых уже создали модель "Profile", а некоторые из них этого не сделали. Мы пошли с таким подходом, который гарантирует, что модель профиля присутствует в любой момент, без необходимости проходить и ретроактивно генерировать все модели профиля.
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, но это, кажется, добавляет довольно немного накладных расходов, поскольку они запускаются каждый раз, когда объект инициализируется, и могут быть случаи, когда вы не хотите получать доступ к ассоциации. Кажется, что это отходы, чтобы проверить его существование.
Ответ 6
Для этого есть жемчужина:
https://github.com/jqr/has_one_autocreate
Похоже, что это немного устарело. (не работает с rails3)