0

In my app, I have many "Sites", and I have multiple user types like "Patient", "Provider", "Nurse", "Administrator", etc.

class Patient
  has_many :sites
end

class Provider
  has_many :sites
end

#etc user models...

class Site < ApplicationRecord
  # I don't want to have to name every user model here...
  has_many :patients
  has_many :providers
  has_many :nurses
  # etc. user models...
end

What I would like to do, is have it so that each user type can have many sites, and each site can have many of each user type, but I don't want to do the following because the user type models may increase exponentially...

I thought maybe I could do a polymorphic relationship, but this doesn't satisfy the many_to_many relationship.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Erick Maynard
  • 731
  • 6
  • 18
  • Maybe you could use **the same strategy** used by the `ActiveStorage`, and set up the table `Site` like the `ActiveStorageAttachment`: there is a column for the `record_type` (Patient, Provider, etc) and a column for the `record_id` (the `id` of the correspondent record of the `record_type` table. This post maybe can give an idea: https://stackoverflow.com/a/50775681/5239030 – iGian Jun 11 '18 at 07:34

2 Answers2

0

you probably want to use single table inheritance:

http://edgeguides.rubyonrails.org/association_basics.html#single-table-inheritance

then your patients, providers, nurses, etc. would all inherit from a base class called something like SiteOwner or similar, which would be the only association on the Site class.

cvshepherd
  • 3,947
  • 3
  • 20
  • 14
  • What we do is we have many user types I.E. Patient, Provider, etc. And we have a User model, in which a User can be many types. Then there is an intermidiary model called Role. So I.E. User.roles.where(roleable_type: "Patient"). This is why we can't do basic STI, and why the Sites need to be connected to the User type: A Patient may be at one site, but that Patient may be a Provider at another site. – Erick Maynard Jun 11 '18 at 09:32
0
class Patient < ApplicationRecord
  has_many :sites_siteables, as: :siteable
  has_many :sites, through: :sites_siteables
end

class Provider < ApplicationRecord
  has_many :sites_siteables, as: :siteable
  has_many :sites, through: :sites_siteables
end

# this is the join model between Site and Siteable(which is any model)
# you can generate this model by running the command:
# rails g model sites_siteable site:belongs_to siteable:references{polymorphic}
class SitesSiteable < ApplicationRecord
  belongs_to :site
  belongs_to :siteable, polymorphic: true
end

class Site < ApplicationRecord
  has_many :sites_siteables
  has_many :patients, through: :sites_siteables, source: :siteable, source_type: 'Patient'
  has_many :providers, through: :sites_siteables, source: :siteable, source_type: 'Provider'
end

Usage

Patient.first.sites
# => returns Sites
Provider.first.sites
# => returns Sites

Site.first.patients
# => returns Patients
Site.first.providers
# => returns Providers

Comments

  • the above code: Site has_many :patients, has_many :providers, and has_many :users, etc... is required and you cannot simply collectively put them all as one has_many; i.e. you can't do the following (it will generate an error):

    # app/models/site.rb
    has_many :siteables, through: :sites_siteables, source: :siteable
    

... because if this happens let's say you have Site.first.siteables, then the value returnes will be a collection of differeent kinds of Models: i.e.:

Site.first.siteables[0] # => returns Provider
Site.first.siteables[1] # => returns Provider
Site.first.siteables[2] # => returns Patient

... and this is problematic because there is no single model to represent the query on this, and I guess not compatible with Rails code: i.e. why the following is confusing (at least, in the SQL-string generation side):

Site.first.siteables.where(phone_number: '123456')

... and is probably why Rails specifically does not allow, and raises an error (on a polymorphic association), and that you are required to specify then the source_type:

has_many :siteables, through: :sites_siteables, source: :siteable

However...

If you really intend to not have so many lines of has_many in the Site model like the following:

has_many :patients, through: :sites_siteables, source: :siteable, source_type: 'Patient'
has_many :providers, through: :sites_siteables, source: :siteable, source_type: 'Provider'
has_many :users, through: :sites_siteables, source: :siteable, source_type: 'User'
# ...
# ...

... you can create another "Abstract" table/model to represent the polymorphic record (I'll update the answer if you wish so; let me know). i.e. you can do something like the following instead:

Site.first.abstract_siteables[0].siteable
# => returns a Patient, or a Provider, or a User, etc...
Site.first.abstract_siteables[1].siteable
# => returns a Patient, or a Provider, or a User, etc...
Site.first.abstract_siteables[2].siteable
# => returns a Patient, or a Provider, or a User, etc...
Jay-Ar Polidario
  • 6,463
  • 14
  • 28