0
votes

I am trying to set up a polymorphic has-many-through relationship with ActiveRecord. Here's the end goal:

  • Users can belong to many organizations and many teams
  • Organizations have many users and many teams
  • Teams have many users and belong to an organization

I am trying to use has-many-through instead of has-and-belongs-to-many, since I need to associate some information along with the relationships (like user role in the organization or team), so I made a join table Membership.

How would I implement this?

3

3 Answers

2
votes

I would design the schema like this: enter image description here

  • Organization has many Team
  • Team has many TeamMember
  • User has many TeamMember
  • TeamMember belongs to User and Team

The models will be:

organization.rb

class Organization < ActiveRecord::Base
  has_many :teams
  has_many :team_members, through: :teams
  has_many :users, through: :team_members
end

team.rb

class Team < ActiveRecord::Base
  belongs_to :organization # fk: organization_id
  has_many :team_members
  has_many :users, through: :team_members
end

user.rb

class User < ActiveRecord::Base
  has_many :team_members
  has_many :teams, through: :team_members
  has_many :organizations, though: :teams
end

team_member.rb

class TeamMember < ActiveRecord::Base
  belongs_to :team      # fk: team_id
  belongs_to :user      # fk: user_id
  attr_accessible :role # role in team
end

So, compare with your requirements:

Users can belong to many organizations and many teams

=> Okay

Organizations have many users and many teams

=> Okay

Teams have many users and belong to an organization

=> Okay

Btw, we don't use any polymorphic here, and TeamMember stands for Membership in your early idea!

1
votes

For polymorphic association,

class User
  has_many :memberships
end

class Team
  belongs_to :organization
  has_many :memberships, :as => :membershipable #you decide the name
end

class Organization
  has_many :memberships, :as => :membershipable
  has_many :teams
end

class Membership
  belongs_to :user
  belongs_to :membershipable, polymorphic: true
end

Note that User is indirectly associated to Team and Organization, and that every call has to go through Membership.

0
votes

In my projects, I use a Relationship class (in a gem I've named ActsAsRelatingTo) as the join model. It looks something like this:

# == Schema Information
#
# Table name: acts_as_relating_to_relationships
#
#  id                  :integer          not null, primary key
#  owner_id            :integer
#  owner_type          :string
#  in_relation_to_id   :integer
#  in_relation_to_type :string
#  created_at          :datetime         not null
#  updated_at          :datetime         not null
#

module ActsAsRelatingTo
  class Relationship < ActiveRecord::Base

      validates :owner_id,                  presence: true
      validates :owner_type,                presence: true
      validates :in_relation_to_id,         presence: true
      validates :in_relation_to_type,       presence: true

      belongs_to  :owner,                   polymorphic: true
      belongs_to  :in_relation_to,          polymorphic: true

  end
end

So, in your User model, you would say something like:

  class User < ActiveRecord::Base

    has_many :owned_relationships,
      as: :owner,
      class_name: "ActsAsRelatingTo::Relationship",
      dependent: :destroy

    has_many :organizations_i_relate_to, 
      through: :owned_relationships, 
      source: :in_relation_to, 
      source_type: "Organization"

    ...

  end

I believe you may be able to leave the source_type argument off since the joined class (Organization) can be inferred from :organizations. Often, I'm joining models where the class name cannot be inferred from the relationship name, in which case I include the source_type argument.

With this, you can say user.organizations_i_relate_to. You can do the same set up for a relationship between any set of classes.

You could also say in your Organization class:

class Organization < ActiveRecord::Base

    has_many :referencing_relationships,
      as: :in_relation_to,
      class_name: "ActsAsRelatingTo::Relationship",
      dependent: :destroy

    has_many :users_that_relate_to_me, 
      through: :referencing_relationships, 
      source: :owner, 
      source_type: "User"

So that you could say organization.users_that_relate_to_me.

I got tired of having to do all the set up, so in my gem I created an acts_as_relating_to method so I can do something like:

class User < ActiveRecord::Base
  acts_as_relating_to :organizations, :teams
  ...
end

and

class Organization < ActiveRecord::Base
  acts_as_relating_to :users, :organizations
  ...
end

and

class Team < ActiveRecord::Base
  acts_as_relating_to :organizations, :users
  ...
end

and all the polymorphic associations and methods get set up for me "automatically".

Sorry for the long answer. Hope you find something useful in it.