How to Model a Mutual Friendship in Rails

Mutual relationships in rails 4

What you're looking for is a has_and_belongs_to_many relation, but to the same table, kind of like as described in detail by Many-to-many relationship with the same model in rails?. However, since you want the relation to be bi-directional ("my friends are all also friends with me"), you have two options:

  1. Use a single join table, each row of which links two user_ids, but insert two rows for each friendship.

    # no need for extra columns on User
    class User < ActiveRecord::Base
    has_many :friendships
    has_many :friends, through: :friendships
    end

    # t.belongs_to :user; t.belongs_to :friend
    class Friendship < ActiveRecord::Base
    belongs_to :user
    belongs_to :friend, class_name: "User"
    end

    u1 = User.create!
    u2 = User.create!
    u3 = User.create!

    # make users 1 and 2 friends
    u1.friendships.create(friend: u2)
    u2.friendships.create(friend: u1)

    # make users 2 and 3 friends
    u2.friendships.create(friend: u3)
    u3.friendships.create(friend: u2)

    # and now, u1.friends returns [u1],
    # u2.friends returns [u1, u3] and
    # u3.friends returns [u2].
  2. Use a single record, but hackery to locate who you're friends with:

    # no need for extra columns on User
    class User < ActiveRecord::Base
    has_many :friendships_as_a, class_name: "Friendship", foreign_key: :user_a_id
    has_many :friendships_as_b, class_name: "Friendship", foreign_key: :user_b_id

    def friends
    User.where(id: friendships_as_a.pluck(:user_b_id) + friendships_as_b.pluck(:user_a_id))
    end
    end

    # t.belongs_to :user_a; t.belongs_to :user_b
    class Friendship < ActiveRecord::Base
    belongs_to :user_a, class_name: "User"
    belongs_to :user_b, class_name: "User"
    end

That's not the cleanest way to do it, but I think you'll find there isn't really a particularly clean way when set up like that (with a denormalized table). Option 1 is a much safer bet. You could also use a SQL view to hit the middle ground, by generating the mirror entries for each friendship automatically.

Edit: Migration & usage in an API

Per the OP's comment below, to use Option 1 fully, here's what you'd need to do:

rails g migration CreateFriendships

Edit that file to look like:

class CreateFriendships < ActiveRecord::Migration
create_table :friendships do |t|
t.belongs_to :user
t.belongs_to :friend
t.timestamps
end
end

Create the Friendship model:

class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: "User"
end

Then on your User model:

class User < ActiveRecord::Base
# ...

has_many :friendships
has_many :friends, through: :friendships, class_name: 'User'

# ...
end

And in your API, say a new FriendshipsController:

class FriendshipsController < ApplicationController
def create
friend = User.find(params[:friend_id])

User.transaction do # ensure both steps happen, or neither happen
Friendship.create!(user: current_user, friend: friend)
Friendship.create!(user: friend, friend: current_user)
end
end
end

Which your route for looks like (in config/routes.rb):

resource :friendships, only: [:create]

And a request to would look like:

POST /friendships?friend_id=42

Then you can refer to current_user.friends whenever you want to find who a user is friends with.

Accept Friendship is not updating (Mutual Friendships)

Yes, indeed, it has to do with that

mutual_friendship.update_attribute(:state, 'accepted')

You are creating two entries in the UserFriendship table when you do

    friendship1 = create!(user: user1, friend: user2, state: 'pending')
friendship2 = create!(user: user2, friend: user1, state: 'requested')

but you are only updating one of them when the request is accepted.

I've added a line to the accept method in the controller that should solve the problem... give it a try and let me know if it works

def accept
@user_friendship = current_user.user_friendships.find(params[:id])
@user_friendship.accept_mutual_friendship!
@user_friendship.friend.user_friendships.find_by(friend_id: current_user.id).accept_mutual_friendship!
flash[:success] = "You are now friends with #{@user_friendship.friend.name}"
redirect_to user_friendships_path
end

newbie : getting mutual likes and only if they are mutual with active record?

To add to jexact's answer this is one way you can do this using AR:

Like.joins('join likes l on likes.user_id=l.likes_id and l.user_id=likes.likes_id')

or

Like.select('l.*').joins('join likes l on likes.user_id=l.likes_id and l.user_id=likes.likes_id')

An alternative (slower? but imho looks cleaner) way is to do this in your User model (NOT tested, but should give you an idea):

class User < ActiveRecord::Base
has_many :likes
has_many :friends, :through => :likes

has_many :liked_by, :foreign_key => 'likes_id', :class_name => 'Like'
has_many :followers, :through => :liked_by, :source => :user

def mutually_likes?(user)
self.friends.include?(user) && self.followers.include?(user)
end
end

rails activerecord, friend relation + inverse_friend relation how to get the mutual relation? code included

I don't think you can define a mutual friends association. So, let's look at a mutual friends class method or scope.

I assume that we want all our friends, for whom we are their friend.

class User
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user

def mutual_friends
inverse_friends.joins(:friendships).where("friendships.user_id = users.id and friendships.friend_id = :self_id", :self_id => id).all
end
end

To do it as an association, this would be what you are trying to do:

has_many :mutual_friends,
:through => :inverse_friendships,
:source => :user,
:conditions => ["friendships.user_id = users.id and friendships.friend_id = :self_id", :self_id => id]

The problem is with the id method call in the has_many :mutual_friends association definition.

Creating a mutual friends list

Received some help elsewhere. I'm guilty of overthinking the problem.

@common_friends = current_user.friends & @user.friends

Then used:

  <% @common_friends.each do |friend|%>
<%= friend.profile_name %>
<% end %>

Rails, how to query a friendship model when there are two equal fields: user_id & friend_id?

I built a rails app that needs to maintain a genealogy of relationships between people. What I did was create the equal and opposite map for each and every relationship, e.g. [a, b, father], [b, a, son] using the after_create, after_update and after_destroy triggers. The reason I did this was I wanted very fast search from person to person. Works great.

The create code looks like this:

def after_create(record)
if ! record.relative.has_relative(record.person_id)
new_relationship = Relationship.new(:relative => record.person,
:relationship => hantai(record.relative.gender, record.person.gender, record.relationship))
record.relative.relationships << new_relationship
record.relative.save
end
end

Where the Relationship object is the same as your Friendship object and the record in this case is the person.



Related Topics



Leave a reply



Submit