Ruby/Rails/Redis Friends/Followers System

Alex Egg,

I found an interesting single-level follow/following/friends system using redis:

http://jimneath.org/2011/03/24/using-redis-with-ruby-on-rails.html

I extracted it out into a module and am documenting it here.

It utilizes sets in Redis. Each user has a set called following and followers. When a User A wants to follow user B, you add the id of User B to user the set of User A. To get all of the users A is following you simple get all the members of the following set of User A.

smembers users:[id]:following

And to get all the people following User A you do the same redis command w/ the respective set name:

smembers users:[id]:followers

Then to see if two users A & B are friends you can employ the power of sets and do an intersection of users:A:following & users:A:followers

venn diagram picture

Since we are using redis sets we get all the other included functionality, such as counting how many followers I have, how many people I follow by using the scard command. I can do one-off checks to see if I follow a particular user w/ the sismember command

In my case I simple include this module into my rails User model file and I get all friending functionality: (User.rb)

class User < ActiveRecord::Base
  include Bearbrandd::Friendable
end

Example usage:

u.following
 => #<ActiveRecord::Relation [#<User id: 2, first_name: "cassee", last_name: "aguirre", email: nil, created_at: "2014-08-25 19:32:53", updated_at: "2014-08-25 19:32:53">]>
u=User.last
 => #<User id: 2, first_name: "cassee", last_name: "aguirre", email: nil, created_at: "2014-08-25 19:32:53", updated_at: "2014-08-25 19:32:53">
u.followers
 => #<ActiveRecord::Relation [#<User id: 1, first_name: "Alex", last_name: "egg", email: nil, created_at: "2014-08-25 18:52:27", updated_at: "2014-08-25 19:33:08">]>
u.follow! User.first
 => [true, true]
 > u.friends
 => #<ActiveRecord::Relation [#<User id: 1, first_name: "Alex", last_name: "egg", email: nil, created_at: "2014-08-25 18:52:27", updated_at: "2014-08-25 19:33:08">]>
u.followed_by? User.first
 => true
u.followers_count
 => 1
u.following_count
 => 1

And there you have it: a simple friending model built on redis w/o any complicated joins that would result if we did it in SQL!

Here is the complete module: (Friendable.rb)

module Bearbrandd
  module Friendable
    # follow a user 
     def follow!(user)
       Redis.current.multi do
         Redis.current.sadd(self.redis_key(:following), user.id)
         Redis.current.sadd(user.redis_key(:followers), self.id)
       end
     end
  
     # unfollow a user
     def unfollow!(user)
       Redis.current.multi do
         Redis.current.srem(self.redis_key(:following), user.id)
         Redis.current.srem(user.redis_key(:followers), self.id)
       end
     end
  
     # users that self follows
     def followers
       user_ids = Redis.current.smembers(self.redis_key(:followers))
       User.where(:id => user_ids)
     end

     # users that follow self
     def following
       user_ids = Redis.current.smembers(self.redis_key(:following))
       User.where(:id => user_ids)
     end

     # users who follow and are being followed by self
     def friends
       user_ids = Redis.current.sinter(self.redis_key(:following), self.redis_key(:followers))
       User.where(:id => user_ids)
     end

     # does the user follow self
     def followed_by?(user)
       Redis.current.sismember(self.redis_key(:followers), user.id)
     end
  
     # does self follow user
     def following?(user)
       Redis.current.sismember(self.redis_key(:following), user.id)
     end

     # number of followers
     def followers_count
       Redis.current.scard(self.redis_key(:followers))
     end

     # number of users being followed
     def following_count
       Redis.current.scard(self.redis_key(:following))
     end
  
     # helper method to generate redis keys
     def redis_key(str)
       "user:#{self.id}:#{str}"
     end
  end
end

Permalink: ruby-rails-redis-friends-followers-system

Tags:

Last edited by Alex Egg, 2016-10-05 19:07:30
View Revision History