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

но это не кажется очень чистым... Есть предложения?

6 64

6 ответов:

Лучшая практика, чтобы создать has_one отношения заключается в использовании модели обратного вызова 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

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

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

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

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

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

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

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

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

есть драгоценный камень для этого:

https://github.com/jqr/has_one_autocreate

похоже, что это немного старый сейчас. (не работает с rails3)