Search code examples
ruby-on-railsmodel-associations

Many-to-many associations in a single model (rails)


Lets say I have the following table of Users

id, type
1, "A"
2, "B"
3, "B"
4, "A"
5, "B"
6, "A"

There are two user types. "A" users, and "B" users. "A"s can be connected to many "B"s, and "B"s can be connected to many "A"s. Let's say we have the following 'connections' table

id, A_id, B_id

1, 1, 2
2, 4, 2
3, 4, 3
4, 6, 5
5, 1, 5
6, 4, 5

Which would represent the following graph:

connections graph

I could have an "A"s table that stores the foreign_key indexes of the users with the "A" type (i.e. 'these users are type "A"), and similarly for "B", in which case I could just define a simple has_many association through the connections table from "A" to "B" and vice versa.

I want to be able to type something like a.b to produce all the "B" that the 'a' is connected to, as well as b.a to produce all the "A" that the 'b' is connected to.

My question is: can this many-to-many association between "A" and "B" be defined using a single User model instead? Can a self-join be used?

Thank you for your time


Solution

  • Looks like a pretty standard example of has_many :through and STI.

    Given:

    class CreateUsers < ActiveRecord::Migration
      def change
        create_table :users do |t|
          t.string :type
    
          t.timestamps
        end
      end
    end
    
    class CreateConnections < ActiveRecord::Migration
      def change
        create_table :connections do |t|
          t.integer :a_user_id
          t.integer :b_user_id
    
          t.timestamps
        end
      end
    end
    
    class User < ActiveRecord::Base
    end
    
    class AUser < User
      has_many :connections
      has_many :b_users, :through => :connections
    end
    
    class BUser < User
      has_many :connections
      has_many :a_users, :through => :connections
    end
    
    class Connection < ActiveRecord::Base
      belongs_to :a_user
      belongs_to :b_user
    end
    

    When (rails console output snipped for brevity):

    >> a_user = AUser.create!
    >> b_user = BUser.create!
    >> connection = a_user.connections.build
    >> connection.b_user = b_user
    >> connection.save!
    

    Then:

    >> a_user.b_users
    => ActiveRecord::Associations::CollectionProxy of BUsers
    

    If your AUser and BUser objects will have different sets of attributes then you should use Polymorphic Associations instead, which requires creating a couple more tables but really doesn't complicate things much.