Loading twitter status...

HowTo: has_many_friends

dnite / 08.Jun.2007

I’ve had a lot of requests for some sort of howto for my has_many_friends plugin for rails. I’m not wondrous at making howto’s, but I’ve been trying to rack my brain thinking of things I could demonstrate in a new post about how to use the has_many_friends plugin. So we’ll see what we can do. There won’t be any large code examples. I’ll just include a few snippets here and there, mainly because I wrote has_many_friends to have enough wiggle room so people can incorporate it in a few different ways. So let’s get started…

If you’ve followed the README, you should have the plugin installed and you will have already added the has_many_friends line to your User model and generated the friendship model with the included generator. If you haven’t done this yet, look at the README where I’ve included lots of information on installing and using the plugin there.

Now that we have the plugin installed, we need to use it. So let’s first think about a few things we might want to do with friends. Off the top of my head, I can think of requesting friendship, accepting friend requests, listing all my friends, checking to see if I’m friends with someone, and deleting friends. I think that’s a decent enough list as demonstrating how to do these actions with my plugin should give you a pretty nice jump on using any of the methods included. I want to make sure everyone understands that this plugin is just an extension to your User model. It provides the database associations and extra helper methods to your User model. So you use it just like any other model. Let’s request friends. For our examples, I’ll have 2 users stored in variables called fred and wilma.

First thing we need to do, Fred needs to send a friend request to Wilma. This is going to go in a controller method that will be handling friend requests.

fred, wilma = User.find(1), User.find(2)
fred.request_friendship_with(wilma)

That’s it. You can set up an observer or do whatever you wish to notify Wilma she has a friend request, but I’m going to leave that up to you. Now Wilma is going to log in and on her dashboard, it’s going to see if she has any pending friends.

wilma = current_user
flash[:notice] = "You have a new friend request!" unless wilma.pending_friends_for_me.blank?

Now that we’ve alerted Wilma of her new friend request, she clicks on the ‘friend request’ link and gets to a list of all pending friends from other users.

def list
  wilma = current_user
  @pending_friends = wilma.pending_friends_for_me
end

You should be able to figure out what to do in your view to display these. The only thing you need to think about is that you need 2 links for each pending friend. One will link to an action to accept the friend request and the other will deny the friend request. Let’s create the accept action first.

def accept_friend
  fred, wilma = User.find(params[:id]), current_user
  wilma.accept_friendship_with(fred)
  flash[:notice] = "Friendship has been accepted with Fred" 
end

Now Wilma and Fred are friends. Keep in mind that this code is very basic and just to show those who are having trouble understanding how to use this plugin. Your code WILL differ from this but this should give you a general idea of what to do. Now that we have a friendship, Fred logs on because he got the email from your uber cool observer that said that Wilma has accepted his friendship. He’s a happy boy. He heads straight for his friend list to see his new addition. As an example, I usually have a separate controller called friends for this with a list action.

def list
  fred = current_user
  @friends = fred.friends
end

Again, you know what to do with @friends in the view. Fred now sees Wilma in that list of friends. Let’s say when 2 people are friends, they get to see more from each other. We’ll need to check for this each time a user visits a page. Fred is going to visit a page of Wilma’s right now.

def show
  fred, wilma = current_user, User.find(params[:id])
  flash[:notice] = "Your friends with this person!" if fred.is_friends_with?(wilma)
end

As just an example, this will show a nice little message if your friends with the person who owns this page. Use your imagination and you should be able to come up with some fancy ways of using this. Now Fred and Wilma have a really big fight. Punches are thrown and Fred is left with a black eye. He’s angry and he’s hell bent on revenge. He’s going to remove Wilma from his friends list! I intentionally left out the ‘deny friendship’ action above because it’s the same as deleting a friendship.

def delete_friendship
  fred, wilma = current_user, User.find(params[:id])
  fred.delete_friendship_with(wilma)
  flash[:notice] = "You and Wilma are no longer friends" 
end

That’ll teach her! And there you have it. A very basic guide to using has_many_friends in your rails application. You will definitely have to elaborate on these examples for your application, but I hope it helps a few of you. Please remember to check out the README for ALL the methods available to use with this plugin. They are all used just like the methods I used in this example and most of them do similar things but you should be able to figure out how they differ by their names. Comment away!

40 Comments

Thanks for this really useful plugin!

Tiny typo in the README: the “is_friends_with?” method is really “friends_with?”

hi, thanks very much for your plug-in, i’ve been looking for something like that for a long time. one thing: i’ve browsed the source and discovered something like “online_friends”. could you explain in a few words what this is and how to use it? is it at all already available for use? what about the “65.seconds.ago”? thanks again bye, ernst

Fantastic job. This will come in really handy in for my new site. Thanks!

this is very kick ass. you’ve done an awesome job. the documentation isn’t quite right but the code itself works very, very nicely.

word to the wise (aka, anyone else who downloads this): look at the code if something’s not working. you’ll most likely see the example code just wasn’t quite right.

Hi

I am a newbie to ruby.

I installed the has_many_friends plugin and now I am confused about this. What should be the fields in friendship table? And do I need a friends table too? If so what should be the fields in the friends table?

Honestly, am not able to understand how it works.

Any help is tremendous.

Cheers Cass

@cass: If you look through the README file you’ll see how to finish up installation. There’s a generator included that will make a migrations for your friendship model for you. You don’t need a friends model because the User’s are self referencing other users.

@byran: Thanks for the compliments.. Just curious, which example code wasn’t quite right? I was trying to keep this tutorial more geared at concepts on how to use the plugin than code examples, but I’d still love to know which examples were ‘off’ to you. Thanks again.

Hi Steve,

Thank you, I looked through them and it works wonderfully, this is an amazing plugin. Way to go!

Cheers

Cass

Hi,

Your plugin is fantastic and has saved me hours today. Thanks!!

May I suggest a small improvement?

In the case where UserA invites UserB, then UserB invites UserA – should this act as an acceptance?

Thanks, Gavin

Also, it is currently possible for duplicate invites to be created. Can we limit to just one?

Great plugin btw, ;)

@gavin: Of course. Both of those things are very doable. It probably wouldn’t take much to add a verification method to the plugin that would check both of these things and your more than welcome to edit the plugin as you wish…

@ernst: I created this plugin for a project I was working on and used the ‘online now’ method’s to check to see if users were online but the implimentation was pretty specialized to my situation so I just left the code commented in case someone wanted to try and get it working in their own case.

Steve – great, I’ll add these checks sometime soon and email you the changes.

I have also added two simple yet useful methods:

def pending_friends_with_by_me?(friend) self.pending_friends_by_me.include? friend end

def pending_friends_with_for_me?(friend) self.pending_friends_for_me.include? friend end

Will you add these to the trunk?

Hi Steve,

Do you have any info on how to use the online function? I don’t see how ‘offline’, ‘status’, ‘updated_at’ and options[:online_method] come into play and how to set these parameters up in a database etc…

Thanks -Richard

@Richard: I currently don’t have any information publicly available for using the online function of this plugin because that part of the plugin was very specialized for my specific needs. If you add a :online_method => true to the has_many_friends statement in your model, u can use that online method but you’ll have to customize everything else to meet yours needs. I won’t have any real documentation about this feature until I can think of an easy way to implement it for everyone. For now, just be creative with it. x=)

Awesome plugin.

One problem: how do I display the number of friends a user has? I’ve tried using @user.friends.count but it just chokes with the following message:

undefined method `count’ for []:Array

Any ideas?

@Jim: Your close.. since the result of my plugin is going to be an array with all the items in it, .count doesn’t work.. you’ll want to do a .length to get the total items in the array..

Steve,

I’m having some problems accessing the: is_friends_with? method. Where is it located in the lib? Any suggestions? When I try to call the method I get a method missing error. The other methods I’ve tried (accept_friendship_with, etc.) seem to be working great.

Thanks for your help! Love your plug-in! Great work! -matthewvb

@mattew: I’m sorry, buddy. I must have updated the README with the method names that I was going to use and forgot to update the method names in the actual plugin. I have since fixed it. If you update to the latest (revision 9), I have updated the methods in the plugin as well.

For those people who have been using the old user.friends_with? method, either don’t update past revision 7 (since the ONLY thing that changed in revision 8 is the addition of is_ to the few methods that state it in the README) or if you do update the plugin, update those methods to use the new is_friends_with? terminology.

Sorry about this, guys.

@Steve

Thanks!! No worries—I really appreciate the awesome plug-in and help!

Steve,

You need to update the plugin lib\has_many_friends.rb @ line 103 to say :friendshipped_for_me => friend) unless self.is_friends_or_pending_with?(friend) || self == friend

(need to add the “is”)

Seems that broke with the update. Thanks! -matthew

two more things to update:

on the readme: the method is called destroy_friendship_with—not delete

in line 115 you need to add the “is” like above.

Hope this helps! -matthewvb

@matthew: Thanks again, buddy. I fixed up those errors in the repository. Should have looked over the whole file but never thought about it at the time cause I was pressed for time. Thanks a lot for bringing it to my attention! x=)

Isn’t the “is” part of the method call implied by the ”?” at the end? When I saw the difference in documentation vs. source I was thinking the source changed first, not the documentation. Why add the is?

-matt

@matt: I think I just added the is_ for the readability of it. Not sure exactly. Just looked fun. x=)

steve: thanks for the updates!

question – is there a way in your current structure to pull say, all the friends of someone based off their extra attributes?

for example: I want to pull all of Bobby’s friends who were added < 30 days_ago.

Do you have a find statement like that? or is the best way just to pull a new find out of the Friendship model and sort through that way?

Thanks! -matthew

Thanks for the plugin! This got me thinking in the right direction for a friends quizzing friends website.

This plugin works great! There is one feature I’d like to see though… The friends method currently returns a regular Array (friends_for_me + friends_by_me). This is problematic when someone has a HUGE number of friends. It’s also klugdy if you want to paginate this array. It can be done using will_paginate, but it’s not very clean.

Is there a way for the friends method to become a normal resultset like User.find :all?

The answer to my own question:

I’ve replaced the friends instance method with this:

def friends
  User.find_by_sql "select distinct users.* from users, friendships
    where ( friendships.user_id = users.id and friendships.friend_id = #{self.id} )
    or ( friendships.friend_id = users.id and friendships.user_id = #{self.id} )
    and friendships.accepted_at is not null" 
end

Hi,

joost, there is a small bug in your ‘friends’ method. There should be additional parentheses around OR statement:

select distinct users.* from users, friendships where ((friendships.user_id = users.id and friendships.friend_id = #{self.id}) or (friendships.friend_id = users.id and friendships.user_id = #{self.id})) and friendships.accepted_at is not null

Hi there,

Is it possible to use the :include function (or any other eagerLoading method) to preload some other user associations (in my case I have a separate model for user_images)

In my user model I have:

has_one: user_image

thnx

Hi Steve,

This is an amazing plugin that am usinf for my upcoming project, but I have a small problem, am able to get a list of all friends a user has with this:

@friends = @user.friends

but when I gives this:

@friends = @user.friends.paginate :page => params[:page], :per_page => 10

I get the following error:

NoMethodError in FriendsController#list_friends

undefined method `to_i’ for {:page=>nil, :per_page=>10}:Hash

RAILS_ROOT: ./script/../config/.. Application Trace | Framework Trace | Full Trace

F:/InstantRails/rails_apps/serious/vendor/plugins/will_paginate/lib/will_paginate/collection.rb:14:in `initialize’ F:/InstantRails/rails_apps/serious/vendor/plugins/will_paginate/lib/will_paginate/collection.rb:21:in `new’ F:/InstantRails/rails_apps/serious/vendor/plugins/will_paginate/lib/will_paginate/collection.rb:21:in `create’ F:/InstantRails/rails_apps/serious/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb:37:in `paginate’ F:/InstantRails/rails_apps/serious/app/controllers/friends_controller.rb:83:in `list_friends’

Could you please guide me with this…

Cheers

Lukey

mike: This happens because the friends method returns an Array. And an Array does not have a paginate method. Use the fix above from joost.

I made the function a little more Rails friendly

def friends
    User.find(:all, :select => "distinct users.*",
                    :from => 'users, friendships',
                    :conditions => ["((friendships.user_id = users.id AND friendships.friend_id = ?) 
                      OR (friendships.friend_id = users.id AND friendships.user_id = ?)) 
                      AND friendships.accepted_at is not null", self.id, self.id])
  end

Also improved the is_friends_with? method. Before that it loaded all the friends to check if a user is included in the Array. This is an expensive operation.

I think this is a little better:

def is_friends_with?(friend)
    Friendship.count(:limit => 1, :conditions => ['((user_id = ? AND friend_id = ?) OR (friend_id = ? AND user_id = ?)) AND friendships.accepted_at is not null', self.id, friend.id, self.id, friend.id]) > 0
end

Thanks for the great plugin Steve! I am using RESTful Authentication w/ email notification (via ActionMailer) in my app. I would like to integrate has_many_friends to add friends capability with the following twist:

—> Fred wants to invite Wilma to be a friend but Wilma has not yet registered on the site

To affect this, I imagine something like this is required:

—> An “invite a Friend” link opens a form to collect username, email address and default password

—> The form input is processed and drives something like “fred.create_user_and_request_friendship_with(wilma)”

—> The “fred.create_user_and_request_friendship_with(wilma)” method creates a user with login “wilma” and initiates creation of a boiler plate invite email

—> The invite email is addressed to Wilma from Fred, and has an “activation link” that includes Wilma and Fred’s usernames, e.g., “Hey Wilma, you really need to join my inner circle on this cool site. Just click THIS link to signup.”

—> The signup link maps to a User controller / activation method that activates Wilma as a new user and accepts Fred’s friendship request

Any pointers on if/how has_many_friends can be extended to allow this?

Actually, I’m new to ROR. In my project I have to create a frendship model . I was thinking of using this has_many_friends. So installed it. But there are no any methods in controller and model as well after installation also??? Did i’ve to write the methods myself???Plz let me know…......Also I’m using the salted_hash_login generator whether this has_many_friends works in the combination with the generator I’ve used or not…......Will be waiting for your suggestions….

Actually, I’m new to ROR. In my project I have to create a frendship model . I was thinking of using this has_many_friends. So installed it. But there are no any methods in controller and model as well after installation also??? Did i’ve to write the methods myself???Plz let me know…......Also I’m using the salted_hash_login generator whether this has_many_friends works in the combination with the generator I’ve used or not…......Will be waiting for your suggestions….

swapnil,

If you use the generator (as described in the readme), it creates the model for you. All you do the is add the has_many_friends to the User.rb file and you’re sorted.

Brilliant plugin – was just about to write all this and happened to stumble upon it. Saved me hours, cheers!

Hey,

I dont know how it happened to only me but when I request_friendship_with a user, it adds two rows to the database and then it gives 500 Internal Server Error.

In the development log I see that rails goes into a infinite loop then mysel breaks up the connection. the infinite loop is: User Load (0.004551) SELECT * FROM `users` WHERE (`users`.`id` = 3)  User Load (0.004514) SELECT * FROM `users` WHERE (`users`.`id` = 3)  User Load (0.004434) SELECT * FROM `users` WHERE (`users`.`id` = 3)  User Load (0.004719) SELECT * FROM `users` WHERE (`users`.`id` = 3)  ..

..

then, Mysql::Error: Lost connection to MySQL server during query: SELECT * FROM `users` WHERE (`users`.`id` = 3)  /!\ FAILSAFE /!\ Sat May 31 17:40:55 +0300 2008 Status: 500 Internal Server Error

I dont know the reason but I use rails 2.0.2 thanks!

I get “uninitialized constant User::Friend” when I try @user.friends

What am I doing wrong?

To answer my own question:

It’s “has_many_friends” and not “has_many :friends”

Doh!

Leave a Comment

back to top

micro theme by seaofclouds, edited by me, and powered with Mephisto