通过多表继承的Has_one和多态关联



在我目前正在rails 4.0.0beta1下开发的项目中,我需要一个基于用户的身份验证,其中每个用户都可以链接到一个实体。我对rails有点陌生,所以遇到了一些麻烦。

模型如下:

class User < ActiveRecord::Base
end
class Agency < ActiveRecord::Base
end
class Client < ActiveRecord::Base
  belongs_to :agency
end

我需要的是一个用户能够链接到一个机构或客户端,但不是两者(这两个是我将调用实体)。可以没有链接,也可以最多有一个链接。

我寻找的第一件事是如何在rails中做多表继承(MTI)。但有些事情阻碍了我:

  • 无法开箱使用
  • 对于像我这样的新手来说,MTI看起来有点难以实现
  • 执行解决方案的宝石看起来很旧,要么太复杂,要么不完整
  • 宝石可能在rails4下破裂,因为它们有一段时间没有更新了

所以我寻找另一个解决方案,我发现多态关联。

我从昨天开始就在这上面,花了一些时间使它工作,即使在Rails多态has_many:through和ActiveRecord, has_many:through和多态关联的帮助下

我设法从上面的问题的例子工作,但它花了一段时间,我终于有两个问题:

  1. 如何将user中的关系转换为has_one关联,并能够"盲目"访问被链接实体?
  2. 如何设置约束,使任何用户都不能拥有多个实体?
  3. 有更好的方法来做我想做的吗?

下面是一个完整的工作示例:

迁移文件:

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,命名为"entity"。这是因为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相比,它是一个很好的解决方案,我认为不应该花那么多时间来设置这样的东西。

上面的答案让我有些困惑。在验证唯一性时,使用列名而不是模型名。

将validates_uniqueness_of:user修改为validates_uniqueness_of:user_id。

相关内容

  • 没有找到相关文章

最新更新