10

usersテーブルとテーブルを備えたアプリケーションがありuser_profilesます。ユーザーhas_oneユーザープロファイルとユーザープロファイルbelongs_toユーザー。

アソシエーションシナリオが常に真であることを確認したいので、両方の外部キーの存在を検証しました。問題は、私が「鶏が先か卵が先か」という状況に陥ったことです。ユーザーを作成すると、ユーザープロファイルがまだ存在しないために機能しません。また、ユーザープロファイルを作成すると、ユーザーがまだ存在しないために機能しません。したがって、ユーザーの作成内にユーザープロファイルを作成する必要があります。複雑なことに、クライアントを作成するときに、after_createコールバックでユーザーも作成します。十分に話す(または読む/書く)、ここにいくつかのコードがあります:

class User < ActiveRecord::Base
    has_one :user_profile
    validates :user_profile_id, presence: true
end

class UserProfile < ActiveRecord::Base
  belongs_to :user
  validates :user_id, presence: true
end

class Client < ActiveRecord::Base
  after_create :create_client_user

  private

  def create_client_user
    User.create!(
      email: "admin@example.com",
      password: "admin",
      password_confirmation: "admin",
      client_id: self.id
      # I need to create a user profile dynamically here
    )
  end
end

私がやりたいことをすることは可能ですか?

アップデート

@cdesrosiersが提案した解決策を試しましたが、仕様に合格できません。私は主に3つのエラーがあります。まず、更新されたモデルを紹介します。

class User < ActiveRecord::Base
  has_one :user_profile, inverse_of: :user
  before_create { build_user_profile }

  validates :user_profile, presence: true

  def client=(client)
    self.client_id = client.id
  end

  def client
    current_database = Apartment::Database.current_database
    Apartment::Database.switch
    client = Client.find(self.client_id)
    Apartment::Database.switch(current_database)
    client
  end
end

class UserProfile < ActiveRecord::Base
  belongs_to :user

  validates :user, presence: true
end

class Client < ActiveRecord::Base
  attr_accessible :domain, :name

  after_create :create_client_database
  after_create :create_client_user
  after_destroy :drop_client_database

  # Create the client database (Apartment) for multi-tenancy
  def create_client_database
    Apartment::Database.create(self.domain)
  end

  # Create an admin user for the client
  def create_client_user
    Apartment::Database.switch(self.domain)

    User.create!(
      email: "admin@example.com",
      password: "admin",
      password_confirmation: "admin",
      client: self
    )

    # Switch back to the public schema
    Apartment::Database.switch
  end

  def drop_client_database
    Apartment::Database.drop(self.domain)
  end
end

FactoryGirlを使用してファクトリを作成しています。ファクトリファイルは次のとおりです。

FactoryGirl.define do
  factory :client do
    sequence(:domain) { |n| "client#{n}" }
    name              Faker::Company.name
  end

  factory :user do
    sequence(:email)      { |n| "user#{n}@example.com"}
    password              "password"
    password_confirmation "password"
    client
    #user_profile
  end

  factory :credentials, class: User do
    email       "user@example.com"
    password    "password"
  end

  factory :user_profile do
    forename       Faker::Name.first_name
    surname        Faker::Name.last_name
    birthday       (5..90).to_a.sample.years.ago
    #user
  end
end

ユーザーファクトリとユーザープロファイルファクトリのそれぞれuser_profileとアソシエーションのコメントを解除すると、が表示されます。userWARNING: out of shared memory

これらのファクトリの1つを作成すると、次の3つのエラーのいずれかが発生します。

Failure/Error: @user = create(:user)
     ActiveRecord::RecordInvalid:
       Validation failed: User profile A user profile is required
     # ./app/models/client.rb:41:in `create_client_user'
     # ./spec/controllers/users_controller_spec.rb:150:in `block (4 levels) in <top (required)>'

Failure/Error: create(:user_profile).should respond_to :surname
    ActiveRecord::RecordInvalid:
      Validation failed: User A user is required
    # ./spec/models/user_profile_spec.rb:29:in `block (4 levels) in <top (required)>'

Failure/Error: let(:client) { create(:client) }
     ActiveRecord::RecordInvalid:
       Validation failed: User profile A user profile is required
     # ./app/models/client.rb:41:in `create_client_user'
     # ./spec/controllers/sessions_controller_spec.rb:4:in `block (2 levels) in <top (required)>'
     # ./spec/controllers/sessions_controller_spec.rb:7:in `block (2 levels) in <top (required)>'

したがって、ユーザーモデルの変更は機能しなかったと思います。user_profile_idまた、usersテーブルからを削除したことにも注意してください。

4

4 に答える 4

9

モデルAhas_oneモデルBの場合、これは、モデルChas_manyモデルDが外部キーをCに格納することを意味するのと同じように、Bが外部キーをAに格納することを意味しhas_oneます。特定の外部キーをAに保持します。その場合、スキーマは使用されないためuser_profile_id、スキーマから削除する必要がありusersます。user_idfromのみUserProfileが使用されます。

Userの存在を確認することはできますが、代わりにUserProfile使用してください。validates_presence_of :user_profileこれにより、ユーザーオブジェクトにuser_profileオブジェクトが関連付けられていることが確認されます。

新しいuser-user_profileペアを作成するときにこのIDはまだ存在しないUserProfileため、オブジェクトは直接チェックしないでください。user_id代わりに、を使用してください。これは、オブジェクトを保存する前に、に関連付けられたオブジェクトがvalidates_presence_of :userあることを確認します。次に、書き込みを行います。これにより、オブジェクトが永続化されてIDが割り当てられる前であっても、オブジェクトの存在を知ることができます。UserProfileUserhas_one :user_profile, :inverse_of => :userUserUserProfileUser

最後に、新しいユーザーを作成するときに、before_createブロックを含めUserて関連を構築できます。UserProfile(私は信じています)新しいuser_profileを構築した後に検証を実行するので、これらは合格するはずです。

要約すれば、

class User < ActiveRecord::Base
    has_one :user_profile, :inverse_of => :user
    validates_presence_of :user_profile

    before_create { build_user_profile }
end

class UserProfile < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :user
end

アップデート

検証とコールバックの順序について間違えました。検証は、before_createコールバックが呼び出される前に実行されます。つまり、コールバックが作成される前にUser、その存在をチェックしUserProfileます。

1つの解決策は、userモデルとuser_profileモデルを別々にすることでどのような価値が得られるかを自問することです。それらが非常に緊密にバインドされているため、一方が他方なしでは存在できないことを考えると、それらを1つのモデルに結合することは理にかなっていますか(そしておそらくコードの多くを単純化する)?

一方、2つの別々のモデルを持つことに価値があることが本当にわかった場合は、相互の存在を維持するために検証を使用するべきではないかもしれません。私の意見では、モデル検証は通常、送信したデータに修正が必要なエラーがあることをユーザーに知らせるために使用する必要があります。ただし、オブジェクトにauser_profileuserないことは、修正できるものではありません。したがって、おそらくより良い解決策は、オブジェクトがない場合はuserオブジェクトをビルドさせることです。が存在しないuser_profile場合に単に文句を言うのではなく、さらに一歩進んでビルドするだけです。user_profileどちらの側でも検証は必要ありません。

class User < ActiveRecord::Base
  has_one :user_profile

  before_save { build_user_profile unless user_profile }
end

class UserProfile < ActiveRecord::Base
  belongs_to :user
end
于 2012-11-02T22:10:21.460 に答える
0

user_profile_idが存在しないため、その存在を検証できません。has_oneが意味するのは、他のモデルがそれへの外部キー参照を持っているということです。

私が一般的にあなたが求めている振る舞いを確実にする方法は、参照されているモデルが作成されるときに外部キー参照を使用してモデルを条件付きで作成することです。after_createあなたの場合、次のようにユーザーのプロファイルを作成します。

class User < ActiveRecord::Base
  ...
  after_create :create_profile
  private
  def create_profile
    self.user_profile.create
  end
end
于 2012-11-02T21:32:29.623 に答える
0

このレールキャストは、ネストされたフォームの作成を超えます(user / user_profileの両方を一緒に作成するため)。http://railscasts.com/episodes/196-nested-model-form-part-1 has_manyをカバーしているため、いくつかの変更を行う必要がありますが、理解できるはずです。

于 2012-11-02T21:33:18.217 に答える
0

ここにネストの問題があります。これは、ユーザーフォームにuser_profileのフォームフィールドをネストし、両方を同時に作成することで解決できます。

class User
  accepts_nested_attributes_for :user_profile
  attr_accesible :user_profile_attributes
  validates_presence_of :user_profile_attributes

#user/new.haml
form_for @user do |f|
  fields_for @user.user_profile do |fields|
    fields.label "Etc"
    #......

http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for

于 2012-11-02T21:34:19.310 に答える