在我目前正在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和多态关联的帮助下
我设法从上面的问题的例子工作,但它花了一段时间,我终于有两个问题:
- 如何将user中的关系转换为has_one关联,并能够"盲目"访问被链接实体?
- 如何设置约束,使任何用户都不能拥有多个实体?
- 有更好的方法来做我想做的吗?
下面是一个完整的工作示例:
迁移文件:
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相比,它是一个很好的解决方案,我认为不应该花那么多时间来设置这样的东西。
上面的答案让我有些困惑。在验证唯一性时,使用列名而不是模型名。