Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: self join scheme with has_and_belongs_to_many?

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?

like image 856
fakub Avatar asked Nov 04 '13 15:11

fakub


1 Answers

Here are some resources which may be helpful:

  • RailsCasts episode #163 Self-Referential Association regarding self-referential many-to-many relationships
  • RailsCasts episode #47 Two Many-to-Many. This might be more relevant to what you're attempting to accomplish
  • A gist someone created for self-referential relationships using HABTM

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.

like image 179
Paul Richter Avatar answered Oct 10 '22 07:10

Paul Richter