web-dev-qa-db-ja.com

Rails-ベストプラクティス:依存するhas_one関係を作成する方法

Has_one関係を作成するためのベストプラクティスを教えてください。

f.e.ユーザーモデルがあり、プロファイルが必要な場合...

どうすればそれを達成できますか?

1つの解決策は次のとおりです。

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

  def set_default_association
    self.create_profile
  end
end

しかし、それはあまりきれいではないようです...何か提案はありますか?

74

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
122
Larry K

あなたのソリューションは間違いなくそれを行うにはまともな方法ですが(少なくともあなたがそれを上回るまで)、それを単純化することができます:

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

これが既存の大規模データベースの新しい関連付けである場合、次のように移行を管理します。

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

既存のユーザーレコードは、要求されたときにプロファイルを取得し、新しいプロファイルが作成されます。これにより、デフォルトの属性も1か所に配置され、accepts_nested_attributes_for in Rails 4以降で正しく動作します。

22
inopinatus

おそらく最もクリーンなソリューションではありませんが、50万件のレコードを持つデータベースが既にあり、その一部は既に「プロファイル」モデルを作成しており、一部は作成していません。このアプローチでは、すべてのプロファイルモデルを遡って生成する必要なく、プロファイルモデルが任意の時点で存在することを保証します。

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

ここに私がそれをする方法があります。これがどれほど標準かはわかりませんが、非常にうまく機能し、新しい関連付けを作成する必要がない限り、余分なオーバーヘッドを作成しないという点で怠zyです(これについて修正できてうれしいです):

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にフックすることですが、オブジェクトが初期化されるたびに実行されるため、かなりのオーバーヘッドが追加されるようです。関連付けにアクセスしたくない場合もあるでしょう。その存在を確認するのはもったいないようです。

5
Brendon Muir

これには宝石があります:

https://github.com/jqr/has_one_autocreate

少し古いようです。 (Rails3では動作しません)

1
linjunhalida