Search code examples
ruby-on-railshas-and-belongs-to-many

Implement a friendship model with has_and_belongs_to_many in rails


I have user model and I am using has_and_belongs_to_many on Rails to make a relation between user and friend model.
User can has many friends and friend can has many friends. I need to get all friends of specific user, how can I do that?

In user.rb file:

has_and_belongs_to_many :friendships, class_name: "User", join_table:  :friendships,
                          foreign_key: :user_id,
                          association_foreign_key: :friend_user_id}

In 20180309142447_create_friendships_table.rb file:

class CreateFriendshipsTable < ActiveRecord::Migration[5.1]
  def change
    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
end

I need to get all friends of specific user, how can I do that ?


Solution

  • Implementing a friendship between two users

    I assume you are willing to implement a friendship pattern like Facebook:

    1. User requests friendship for another user
    2. Other other has to accept the friendship request
    3. Only after these two steps the users are real friends

    For this we need a friendship model which replaces your has_many_and_belongs_to-built in function. The friendship models will help us to identify active and pending friendship requests between users. The friendship model only has a user (initiator) and a friend (whom the user sent the request).

    Scenario:

    1. You send a request to Joe -> friendship model created, you are the 'user', joe is the 'friend'
    2. Joe accepts your friendship -> friendship model created, joe is the 'user', you are the 'friend'
    3. With 2 helper functions active_friends and pending_friends you can get the data for your views or API

    # new migration
    # $ rails g migration create_friendships
    def change
      create_table :friendships do |t|
        t.integer :user_id
        t.integer :friend_id
        t.timestamps null: false
      end
    end
    

    create a new friendship model

    # friendship.rb
    class Friendship < ActiveRecord::Base
    
      # - RELATIONS
      belongs_to :user
      belongs_to :friend, class_name: 'User'
    
      # - VALIDATIONS
      validates_presence_of :user_id, :friend_id
      validate :user_is_not_equal_friend
      validates_uniqueness_of :user_id, scope: [:friend_id]
    
      def is_mutual
        self.friend.friends.include?(self.user)
      end
    
      private
      def user_is_not_equal_friend
        errors.add(:friend, "can't be the same as the user") if self.user == self.friend
      end
    
    end
    

    in your User model you can handle the friendships rails-like

    # user.rb
    has_many :friendships, dependent: :destroy
    has_many :friends, through: :friendships
    

    to get the friendships someoneelse sent to "you"

    has_many :received_friendships, class_name: 'Friendship', foreign_key: 'friend_id'
    has_many :received_friends, through: :received_friendships, source: 'user'
    
    def active_friends
      friends.select{ |friend| friend.friends.include?(self) }  
    end
    
    def pending_friends
      friends.select{ |friend| !friend.friends.include?(self) }  
    end