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:
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].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
How to Click on a Specific Coordinates of an Element
Stop Loading Page Watir-Webdriver
Delete Contents of Array Based on a Set of Indexes
How to Create Email with CSS and Images from Rails
How Might I Simulate a Private Browsing Experience in Watir? (Selenium)
How to Setup a Local Ssl Certificate and a Rails Application
Rvm and Osx Lion - Rvm 'Forgets' Gemsets on System Restart
Rails "Action" Params Key Conflict
Interested in What the "<<" Does
Cannot Install Any Version of Ruby on Mojave - Internal Ranlib Command Failed
No Such File to Load -- SQLite3/Sqlite3_Native
How to Set a Hook to Run Code at the End of a Ruby Class Definition
Optional Parens in Ruby for Method with Uppercase Start Letter
Working with Decimals in Ruby on Rails 3