Design pattern for person with multiple roles [closed]

好久不见. 提交于 2019-12-24 21:45:03

问题


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:

  • LoginUser with fields for login, password, last_login, ...
  • CardHolder with fields for 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.


回答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!

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:

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:


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.

Clean and simple.



来源:https://stackoverflow.com/questions/47327546/design-pattern-for-person-with-multiple-roles

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!