Cross modal join objects

What?

Sometimes what making an application, you have multiple user types. Like, you could have an insurance company agent and a provider, or you have a lender and a borrower. The point is that each user type is in your system for drastically different reasons. Often these entities have a relationships with each other and you probably modeled that with a join table and a model covering the join table. The join table will then certainly gain a bunch of columns describing the relationship.

There is a very common problem that happens here. User type A will have vastly different needs for the join table than user type B and this usually results in a bunch of overly complex code and difficult to solve bugs. If you notice this too, read on.

A solution

You can resolve much of this tension by creating two join models instead of one. One will be for the A-to-B direction and the other will be for the B-to-A direction. With this you can specify things like attributes that will only be visible to A or B; You can have logic that only applies to one or the other; You can create controllers and views for each direction.

Example

Here we have a simple setup, where a provider has clients. By splitting the join into ProvidersClient and ClientsProvider, we can have a flagged attribute that only shows up when starting with a provider and viewing the clients as a simple example.

|
class Provider < AppModel
  has_many :providers_clients
  has_many :clients, through: :providers_clients

class Client < AppModel
  has_many :clients_providers
  has_many :providers, through: :clients_providers

class ProvidersClient < AppModel
  belongs_to :client
  belongs_to :provider

  singleton_class.alias_method :old_all, :all
  def self.all
    old_all.joins(:provider).select('clients.*, providers_clients.plan, providers_clients.flagged')
  end

class ClientsProvider < AppModel
  belongs_to :client
  belongs_to :provider

  singleton_class.alias_method :old_all, :all
  def self.all
    old_all.joins(:provider).select('providers.*, clients_providers.plan')
  end