61

Не могли бы вы рассказать мне, какая наилучшая практика для создания отношений has_one?Rails - Best-Practice: Как создать зависимые отношения has_one

f.e. если у меня есть модель пользователя, и у нее должен быть профиль ...

Как я мог это сделать?

Одно из решений:

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

    def set_default_association 
    self.create_profile 
    end 
end 

Но это не кажется, очень чистый ... Все говорит?

ответ

107

Лучшая практика для создания 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 
+3

+1 для осознания проверки ребенка. – PeterWong

+0

Могло ли это также управляться с помощью одной строки? -> before_filter: build_profile? – Lichtamberg

+1

@Lichtamberg: Да, но я бы добавил комментарий: «Создает профиль по умолчанию. ДОЛЖЕН всегда проверять». ПРИМЕЧАНИЕ: это будет «before_create: build_profile», а не «before_filter». Если это не подтвердилось, вы получите очень запутанную ошибку msg для пользователя. Или это НЕ было бы на самом деле создано, что означало бы, что вы попадете в Пользователь без профиля. Вы также должны проверить угловые случаи в своих тестах. –

24

Ваше решение, безусловно, достойный способ сделать это (по крайней мере, пока вы не перерастают его), но вы можете упростить:

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

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

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

Вот как я это делаю. Не знаю, как стандарт это, но она работает очень хорошо, и его ленивый в том, что он не создает дополнительные накладные расходы, если это не необходимо, чтобы построить новую ассоциацию (я счастлив быть исправлено на этом):

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, но это, по-видимому, добавляет довольно немного накладных расходов, поскольку оно запускается каждый раз, когда объект инициализируется, и могут быть случаи, когда вы не хотите получать доступ к ассоциации. Кажется, что это отходы, чтобы проверить его существование.

+0

Я думаю, что этот ответ лучше, чем другие, потому что избегайте проблем с проверкой в ​​модели профиля. Спасибо – ole

+0

Вы также можете иметь автосохранение: 'has_one: profile,: autosave => true' – montrealmike

+0

@montrealmike, с этим связано с отсутствующим профилем? То есть если вы еще не выполнили build_profile, это создало бы один из них? Я также столкнулся с этим: https://github.com/phildionne/associates, который может предоставить другой способ создания многомодельных форм. –

19

Если это новое объединение в существующих обширных базах данных, я буду управлять переходом, как это:

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 и далее.

Смежные вопросы