11

在我目前正在rails 4.0.0beta1下開發的項目中,我需要一個基於用戶的身份驗證,其中每個用戶都可以鏈接到一個實體。我有點新的軌道,並有一些麻煩這樣做。has_one through和通過多表繼承的多態關聯

該模型如下:

class User < ActiveRecord::Base 
end 

class Agency < ActiveRecord::Base 
end 

class Client < ActiveRecord::Base 
    belongs_to :agency 
end 

我需要的是用戶能夠鏈接到任何一個機構或一個客戶端,但不能同時(這兩個是什麼,我會打電話實體)。它完全沒有鏈接,最多隻有一個鏈接。

我期待的第一件事是如何在軌道中做Mutli-Table inheritance(MTI)。但有些事情阻止我:

  • 這不是現成可用的
  • MTI看上去有點難以實現的新手如我
  • 落實解決似乎老不是太配合物或寶石不完整的
  • 寶石將根據rails4可能已經打破了,因爲他們還沒有更新了一段時間

所以我找了另一種解決方案,我發現polymorphic associations

我已經是這個,因爲昨天花了一些時間,使其更具有Rails polymorphic has_many :through的幫助和ActiveRecord, has_many :through, and Polymorphic Associations

我設法讓從上述工作問題的例子工作,但過了好一會兒,我最後有兩個問題:

  1. 如何在用戶關係轉變爲HAS_ONE協會和能夠訪問「盲目」的鏈接的實體?
  2. 如何設置一個約束,以便沒有用戶可以有多個實體?
  3. 有沒有更好的方法來做我想要的?

回答

11

這裏是一個完全工作的例子:

遷移文件:

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 

正如你可以看到我添加一個getter和setter方法,我命名爲「實體」。這是因爲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相比,它是一個很好的解決方案,我認爲它不應該花那麼多時間來設置類似的東西。

+0

@Crystark測試上述文件時出現以下錯誤NameError:未初始化的常量UserWithClient – 2016-02-06 02:42:19

0

上面的答案給了我一些麻煩。驗證唯一性時,使用列名稱而不是模型名稱。將validates_uniqueness_of:user更改爲validates_uniqueness_of:user_id。