13

私が現在Rails4.0.0beta1で開発しているプロジェクトでは、各ユーザーをエンティティにリンクできるユーザーベースの認証が必要でした。私はレールに少し慣れていないので、そうするのにいくつかの問題がありました。

モデルは次のとおりです。

class User < ActiveRecord::Base
end

class Agency < ActiveRecord::Base
end

class Client < ActiveRecord::Base
  belongs_to :agency
end

私が必要としているのは、ユーザーが代理店またはクライアントのいずれかにリンクできるようにすることですが、両方にリンクすることはできません(これら2つは私がエンティティと呼ぶものです)。リンクをまったく持たず、多くても1つのリンクを持つことができます。

私が最初に探したのは、RailsでMutli-Table継承(MTI)を実行する方法でした。しかし、いくつかのことが私をブロックしました:

  • 箱から出して利用できませんでした
  • MTIは、私のような初心者のために実装するのはちょっと難しいように見えました
  • ソリューションを実装している宝石は古く、複雑すぎるか完全ではないように見えました
  • 宝石はしばらく更新されていなかったので、おそらくrails4の下で壊れていたでしょう。

だから私は別の解決策を探しました、そして私はポリモーフィックな関連を見つけました。

私は昨日からこれに取り組んでおり、Railsポリモーフィックhas_many:throughActiveRecord、has_many:through、およびポリモーフィックアソシエーションの助けを借りても機能するようにするのに少し時間がかかりました

上記の質問から例を作成することができましたが、時間がかかり、最終的に2つの問題が発生しました。

  1. ユーザーの関係をhas_oneアソシエーションに変換し、リンクされたエンティティに「ブラインド」でアクセスできるようにするにはどうすればよいですか?
  2. ユーザーが複数のエンティティを持つことができないように制約を設定するにはどうすればよいですか?
  3. 私がやりたいことをするためのより良い方法はありますか?
4

2 に答える 2

15

完全に機能する例を次に示します。

移行ファイル:

class CreateUserEntities < ActiveRecord::Migration
  def change
    create_table :user_entities do |t|
      t.integer :user_id
      t.references :entity, polymorphic: true

      t.timestamps
    end

    add_index :user_entities, [:user_id, :entity_id, :entity_type]
  end
end

モデル:

class User < ActiveRecord::Base
  has_one :user_entity

  has_one :client, through: :user_entity, source: :entity, source_type: 'Client'
  has_one :agency, through: :user_entity, source: :entity, source_type: 'Agency'

  def entity
    self.user_entity.try(:entity)
  end

  def entity=(newEntity)
    self.build_user_entity(entity: newEntity)
  end
end

class UserEntity < ActiveRecord::Base
  belongs_to :user
  belongs_to :entity, polymorphic: true

  validates_uniqueness_of :user
end

class Client < ActiveRecord::Base
  has_many :user_entities, as: :entity
  has_many :users, through: :user_entities
end

class Agency < ActiveRecord::Base
  has_many :user_entities, as: :entity
  has_many :users, through: :user_entities
end

ご覧のとおり、「エンティティ」という名前のゲッターとセッターを追加しました。これhas_one :entity, through: :user_entityは、次のエラーが発生するためです。

ActiveRecord::HasManyThroughAssociationPolymorphicSourceError: Cannot have a has_many :through association 'User#entity' on the polymorphic object 'Entity#entity' without 'source_type'. Try adding 'source_type: "Entity"' to 'has_many :through' definition.

最後に、これが私が設定したテストです。これらのオブジェクト間でデータを設定してアクセスできる方法を誰もが理解できるように、それらを提供します。FactoryGirlモデルの詳細は説明しませんが、かなり明白です

require 'test_helper'

class UserEntityTest < ActiveSupport::TestCase

  test "access entity from user" do
    usr = FactoryGirl.create(:user_with_client)

    assert_instance_of client, usr.user_entity.entity
    assert_instance_of client, usr.entity
    assert_instance_of client, usr.client
  end

  test "only right entity is set" do
    usr = FactoryGirl.create(:user_with_client)

    assert_instance_of client, usr.client
    assert_nil usr.agency
  end

  test "add entity to user using the blind rails method" do
    usr = FactoryGirl.create(:user)
    client = FactoryGirl.create(:client)

    usr.build_user_entity(entity: client)
    usr.save!

    result = UserEntity.where(user_id: usr.id)
    assert_equal 1, result.size
    assert_equal client.id, result.first.entity_id
  end

  test "add entity to user using setter" do
    usr = FactoryGirl.create(:user)
    client = FactoryGirl.create(:client)

    usr.client = client
    usr.save!

    result = UserEntity.where(user_id: usr.id)
    assert_equal 1, result.size
    assert_equal client.id, result.first.entity_id
  end

  test "add entity to user using blind setter" do
    usr = FactoryGirl.create(:user)
    client = FactoryGirl.create(:client)

    usr.entity = client
    usr.save!

    result = UserEntity.where(user_id: usr.id)
    assert_equal 1, result.size
    assert_equal client.id, result.first.entity_id
  end

  test "add user to entity" do
    usr = FactoryGirl.create(:user)
    client = FactoryGirl.create(:client)

    client.users << usr

    result = UserEntity.where(entity_id: client.id, entity_type: 'client')

    assert_equal 1, result.size
    assert_equal usr.id, result.first.user_id
  end

  test "only one entity by user" do

    usr = FactoryGirl.create(:user)
    client = FactoryGirl.create(:client)
    agency = FactoryGirl.create(:agency)

    usr.agency = agency
    usr.client = client
    usr.save!

    result = UserEntity.where(user_id: usr.id)
    assert_equal 1, result.size
    assert_equal client.id, result.first.entity_id

  end

  test "user uniqueness" do

    usr = FactoryGirl.create(:user)
    client = FactoryGirl.create(:client)
    agency = FactoryGirl.create(:agency)

    UserEntity.create!(user: usr, entity: client)

    assert_raise(ActiveRecord::RecordInvalid) {
      UserEntity.create!(user: usr, entity: agency)
    }

  end

end

これが誰かの助けになることを願っています。私はここにソリューション全体を置くことにしました。MTIと比較して良いソリューションのように思えるので、そのようなものを設定するのにそれほど時間はかからないはずです。

于 2013-03-14T17:45:42.600 に答える
0

上記の答えは私にいくつかの問題を与えていました。一意性を検証するときは、モデル名の代わりに列名を使用してください。validates_uniqueness_of:userをvalidates_uniqueness_of:user_idに変更します。

于 2013-08-15T22:16:18.023 に答える