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。