read

There are a few ways you can create a twitter-like-followers feature with rails and mongo. You can create an entire collection to hold all your relationships of followers by their ids. You could also create a has_many relationship between a Follow model. I didn’t like these solutions. I didn’t feel like the ruby way nor the mongo way.

I wanted a simple solution of:

  • User A needs to follow User B
  • User B needs to have User A as their follower
  • Store in the same User document as follower_ids and following_ids

Now, we should be able to pull back my user from our API (using Grape) and it should contain all the user ids i’m following and the user ids that are following me. Simple stupid.

Add has_and_belongs_to_many relationship to your User model

/user.rb

class User
  include Mongoid::Document

  field :first_name, :type => String

  has_and_belongs_to_many :following, :class_name => 'User', inverse_of: :followers, autosave: true
  has_and_belongs_to_many :followers, :class_name => 'User', inverse_of: :following

  ....
end

Add methods to help us follow and unfollow users

class User
  include Mongoid::Document

  field :first_name, :type => String

  has_and_belongs_to_many :following, :class_name => 'User', inverse_of: :followers, autosave: true
  has_and_belongs_to_many :followers, :class_name => 'User', inverse_of: :following

  def follow(user)
    if self.id != user.id && !self.following.include?(user)
      self.following << user
    end
  end

  def unfollow(user)
    self.following.delete(user)
  end
end

Add endpoint on your API to make this accessible

module YourApp
  class Users < Grape::API
    # localhost/api/user
    resource :user do

      ....

      desc 'follow a user'
      params do
        requires :id, type: String, desc: 'User id'
      end
      post :follow do
        authenticate!

        user = User.where(id: params[:id]).first

        if current_user.follow(user)
          sanitize_one(user)
        else
          not_found('Unable to follow user.')
        end
      end

      desc 'unfollow a user'
      params do
        requires :id, type: String, desc: 'User id'
      end
      delete :unfollow do
        authenticate!

        user = User.where(id: params[:id]).first

        if current_user.unfollow(user)
          user
        else
          not_found('Unable to unfollow user.')
        end
      end
    end
  end
end

That should do it. As soon as you follow a user. A bunch of magic happens and you’re left with an array populated on your user.

Tweet me if you have questions!

Blog Logo

Taylor Jones


Published

Image

taylorqj

A collection of software development, thoughts, and vices.

Back to Overview