问题
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 supplierRecipient
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 Card
s.
Clean and simple.
来源:https://stackoverflow.com/questions/47327546/design-pattern-for-person-with-multiple-roles