具有多个角色的人的设计模式

时间:2022-10-03 16:07:47

I want to create a base model Person with some person related attributes like name, address, phone and so on. One Person can be one ore more of the following:

我想创建一个基本模型Person,其中包含一些与人物相关的属性,如姓名,地址,电话等。一个人可以是以下一种或多种:

  • LoginUser with fields for login, password, last_login, ...
  • LoginUser包含登录,密码,last_login,...的字段
  • CardHolder with fields for card_id, last_entrance, ...
  • CardHolder,包含card_id,last_entrance,...的字段
  • Supplier with just a flag whether or not the person is a supplier
  • 只有一个标志的供应商,无论该人是否是供应商
  • Recipient with just a flag whether or not the person is a recipient
  • 无论该人是否是收件人,只收到一个标志的收件人

Is there a common sense or best practise design pattern in Ruby on Rails to represent that inheritance? How it should be represented in the model(s) and table structure so that it is possible to check whether a Person is a LoginUser and to access the corresponding fields. In another project I worked already with STI but in this case this isn't the right pattern.

Ruby on Rails中是否存在常识或最佳实践设计模式来表示继承?它应该如何在模型和表结构中表示,以便可以检查Person是否是LoginUser并访问相应的字段。在另一个项目中,我已经与STI合作,但在这种情况下,这不是正确的模式。

2 个解决方案

#1


1  

What you're looking for is a reverse polymorphic association. Polymorphic associations allow you to link one model to many different ones. A reverse polymorphic association allows you to link many models to one single one. They're a little tricky to set up, but once you get the hang of it it's no problem.

您正在寻找的是反向多态关联。多态关联允许您将一个模型链接到许多不同的模型。反向多态关联允许您将多个模型链接到一个模型。它们设置起来有点棘手,但是一旦掌握了它就没问题了。

In order to accomplish this, you need another model that acts as a go-between for the Person model and each of the different roles. This go-between model is the one that actually has the polymorphic association. Your Person model will has_many that model, and your various role models will has_one of it. You then use :through to make the rest of the necessary associations so your code doesn't know any different. Shazam!

为了实现这一点,您需要另一个模型作为Person模型和每个不同角色的中间人。这种中间模型实际上具有多态关联。您的Person模型将具有该模型,并且您的各种角色模型将具有该模型。然后使用:through来完成其余的必要关联,这样你的代码就不会有任何不同了。快变!

Here's an example of how to do it with the Person and CardHolder models. I'm calling the extra model Role because that seems like an obvious choice:

以下是如何使用Person和CardHolder模型执行此操作的示例。我正在调用额外的模型角色,因为这似乎是一个明显的选择:

class Person < ApplicationRecord
  has_many :roles
  # Reach through the Roles association to get the CardHolders, via polymorphic :rollable.
  # Unfortunately, you can't has_one, so you'll have to enforce uniqueness in Role
  # with a validation.
  has_many :card_holders, through: :roles, source: :rollable, source_type: 'CardHolder'
end

class Role < ApplicationRecord
  belongs_to :person
  # Here is where our actual polymorphic connection is:
  belongs_to :rollable, polymorphic: true
end

class CardHolder < ApplicationRecord
  # The other side of the polymorphic connection, with has_one:
  has_one :role, as: :rollable
  # Get the person via the role, just like the inverse:
  has_one :person, through: :role
end

The database setup is like this:

数据库设置如下:

class CreatePeople < ActiveRecord::Migration[5.1]
  def change
    create_table :people do |t|
      t.string :name
      # put in whatever other Person columns you need

      t.timestamps
    end
  end
end

class CreateRoles < ActiveRecord::Migration[5.1]
  def change
    create_table :roles do |t|
      t.references :person, index: true
      t.references :rollable, polymorphic: true, index: true
      t.timestamps
    end
  end
end

class CreateCardHolders < ActiveRecord::Migration[5.1]
  def change
    create_table :card_holders do |t|
      t.integer :card_id
      t.datetime :last_entrance
      # put in whatever other columns you need

      t.timestamps
    end
  end
end

Using it is quite simple:

使用它很简单:

> p = Person.create(name: "Sven Reuter")
# directly add a card holder
> p.card_holders << CardHolder.create(card_id: 1, last_entrance: Time.current)
# build a role instead
> p.roles.build(rollable: CardHolder.new(card_id: 2, last_entrance: Time.current)
# get all of the roles
> p.roles

#2


0  

I would go with Person table and the PersonAttributes table that is a union of all the attributes the person might have. PersonAttributes might use STI if applicable, e.g. with LoginUser storing logins and CardHolder referencing Cards.

我会使用Person表和PersonAttributes表,该表是该人可能具有的所有属性的并集。 PersonAttributes可以使用STI(如果适用),例如使用LoginUser存储登录和CardHolder引用卡。

Clean and simple.

干净简单。

#1


1  

What you're looking for is a reverse polymorphic association. Polymorphic associations allow you to link one model to many different ones. A reverse polymorphic association allows you to link many models to one single one. They're a little tricky to set up, but once you get the hang of it it's no problem.

您正在寻找的是反向多态关联。多态关联允许您将一个模型链接到许多不同的模型。反向多态关联允许您将多个模型链接到一个模型。它们设置起来有点棘手,但是一旦掌握了它就没问题了。

In order to accomplish this, you need another model that acts as a go-between for the Person model and each of the different roles. This go-between model is the one that actually has the polymorphic association. Your Person model will has_many that model, and your various role models will has_one of it. You then use :through to make the rest of the necessary associations so your code doesn't know any different. Shazam!

为了实现这一点,您需要另一个模型作为Person模型和每个不同角色的中间人。这种中间模型实际上具有多态关联。您的Person模型将具有该模型,并且您的各种角色模型将具有该模型。然后使用:through来完成其余的必要关联,这样你的代码就不会有任何不同了。快变!

Here's an example of how to do it with the Person and CardHolder models. I'm calling the extra model Role because that seems like an obvious choice:

以下是如何使用Person和CardHolder模型执行此操作的示例。我正在调用额外的模型角色,因为这似乎是一个明显的选择:

class Person < ApplicationRecord
  has_many :roles
  # Reach through the Roles association to get the CardHolders, via polymorphic :rollable.
  # Unfortunately, you can't has_one, so you'll have to enforce uniqueness in Role
  # with a validation.
  has_many :card_holders, through: :roles, source: :rollable, source_type: 'CardHolder'
end

class Role < ApplicationRecord
  belongs_to :person
  # Here is where our actual polymorphic connection is:
  belongs_to :rollable, polymorphic: true
end

class CardHolder < ApplicationRecord
  # The other side of the polymorphic connection, with has_one:
  has_one :role, as: :rollable
  # Get the person via the role, just like the inverse:
  has_one :person, through: :role
end

The database setup is like this:

数据库设置如下:

class CreatePeople < ActiveRecord::Migration[5.1]
  def change
    create_table :people do |t|
      t.string :name
      # put in whatever other Person columns you need

      t.timestamps
    end
  end
end

class CreateRoles < ActiveRecord::Migration[5.1]
  def change
    create_table :roles do |t|
      t.references :person, index: true
      t.references :rollable, polymorphic: true, index: true
      t.timestamps
    end
  end
end

class CreateCardHolders < ActiveRecord::Migration[5.1]
  def change
    create_table :card_holders do |t|
      t.integer :card_id
      t.datetime :last_entrance
      # put in whatever other columns you need

      t.timestamps
    end
  end
end

Using it is quite simple:

使用它很简单:

> p = Person.create(name: "Sven Reuter")
# directly add a card holder
> p.card_holders << CardHolder.create(card_id: 1, last_entrance: Time.current)
# build a role instead
> p.roles.build(rollable: CardHolder.new(card_id: 2, last_entrance: Time.current)
# get all of the roles
> p.roles

#2


0  

I would go with Person table and the PersonAttributes table that is a union of all the attributes the person might have. PersonAttributes might use STI if applicable, e.g. with LoginUser storing logins and CardHolder referencing Cards.

我会使用Person表和PersonAttributes表,该表是该人可能具有的所有属性的并集。 PersonAttributes可以使用STI(如果适用),例如使用LoginUser存储登录和CardHolder引用卡。

Clean and simple.

干净简单。