16

I would like to create a structure of Users having many friends, also of class User:

class User < ActiveRecord::Base
  has_and_belongs_to_many :friends, class_name: "User"
end

I do not need any details of their relationship thus I do not use :through with kind of class Friendship. But now I cannot find any way how to create corresponding database (neither with migration file nor using rails g model User username:string ... command). Any ideas?

fakub
  • 327
  • 2
  • 13
  • I'm not quite sure what you mean by "I do not need any details of their relationship". Do you mean you simply want to get back an array of `User` objects? – Paul Richter Nov 04 '13 at 16:13
  • 1
    Exactly. I would like to connect them directly, not through another class. – fakub Nov 04 '13 at 16:53

1 Answers1

39

Here are some resources which may be helpful:

I'll summarize the information found in those links:

Given that you're describing a self-referential many-to-many relationship, you will of course end up with a join table. Normally, the join table should be deliberately named in such a way that Rails will automatically figure out which models the table is joining, however the "self-referential" part makes this a tad awkward, but not difficult. You'll merely have to specify the name of the join table, as well as the joining columns.

You'll need to create this table using a migration that will probably look something like this:

class CreateFriendships < ActiveRecord::Migration
  def self.up
    create_table :friendships, id: false do |t|
      t.integer :user_id
      t.integer :friend_user_id
    end

    add_index(:friendships, [:user_id, :friend_user_id], :unique => true)
    add_index(:friendships, [:friend_user_id, :user_id], :unique => true)
  end

  def self.down
      remove_index(:friendships, [:friend_user_id, :user_id])
      remove_index(:friendships, [:user_id, :friend_user_id])
      drop_table :friendships
  end
end

I'm not certain whether there is a shortcut way of creating this table, but bare minimum you can simply do rails g migration create_friendships, and fill in the self.up and self.down methods.

And then finally in your user model, you simply add the name of the join table, like so:

class User < ActiveRecord::Base
  has_and_belongs_to_many :friends, 
              class_name: "User", 
              join_table: :friendships, 
              foreign_key: :user_id, 
              association_foreign_key: :friend_user_id
end

As you can see, while you do have a join table in the database, there is no related join model.

Please let me know whether this works for you.

XtraSimplicity
  • 5,704
  • 1
  • 28
  • 28
Paul Richter
  • 10,908
  • 10
  • 52
  • 85
  • 8
    Could you explain the reason behind adding two indexes with `:user_id` and `:friend_user_id` in different order? Why not only one index? – Diego Milán Aug 03 '14 at 22:23
  • "A gist someone created for self-referential relationships using HABTM" returns a 404 on Github. – dmmfll May 12 '16 at 10:27
  • The two indexes are for uniqueness in each direction, bob has a friendship with alice, but maybe alice doesn't have a friendship with bob, but bob can't have multiple friendships with alice, and alice can't have multiple friendships with bob. – Joe May 31 '18 at 02:17
  • 1
    @Joe but since they're different indexes, do they really solve the issue that there might be a friendship(user_id=, friend_id=) and another one friendship(user_id=, friend_id=)? Semantically, this shouldn't be possible (they're the same friendship), but these two indexes allow that to happen. – Agis Mar 26 '22 at 20:02