DEV Community

nicklevenson
nicklevenson

Posted on

Requests and Connections like LinkedIn - How to Implement These Model Relationships in a Rails App

As part of a large project I have been working on, I wanted to allow users to request and accept connections from other users. Very similar to the way LinkedIn works, users could ask others to get connected and be connected with those users if the request was accepted. This proved to be a challenging, but fun exercise in model relationships in a Rails application. There are plenty of other ways to do this, but this is what worked well for me.

First and foremost we needed a User class. We also know we would need a class for a Request, as well as a class for a Connection - it's important to keep these as individual models in order to separate concerns. We will be customizing our associations with these models using the Rails association methods of :class_name and :foreign_key. These are important because they allow us to customize our relationships in a way for this whole thing to work.

Request Model
First let's build out our request model since that functionality will precede the connection model. This model is relatively simple and will look like this:

class Request < ApplicationRecord
  belongs_to :requestor, class_name: :User
  belongs_to :receiver, class_name: :User
end

create_table "requests", force: :cascade do |t|
    t.integer "requestor_id"
    t.integer "receiver_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
end
Enter fullscreen mode Exit fullscreen mode

Basically, we are telling Rails that the Request class belongs to TWO users - one is the requestor and one is the receiver of the request. This is why in our migration we specify their ids as we would with any belongs_to relationship. The only difference is that the User classes that this request belongs to are ALIASED to specify who the requestor is and receiver (as opposed to saying belongs_to :user - this would only allow us to have the request belong to a singular user with no alias).

Now, in our User model we can specify this Request association. We essentially want to tell Rails that a User has many Requests, but with the caveat that this User is labelled as a Receiver or a Requestor. Similar to before, we will add alias names here:

 has_many :connection_requests_as_requestor,
    foreign_key: :requestor_id,
    class_name: :Request

  has_many :connection_requests_as_receiver, 
    foreign_key: :receiver_id, 
    class_name: :Request
Enter fullscreen mode Exit fullscreen mode

Like before, we are specifying two has_many relationships where the User has many Requests as a Receiver, and has many Requests as a Requestor. We need to tell Rails what foreign key to associate this User with in the Request instance, which is why we specify foreign_key in each of these associations. We could then call something like user.connection_requests_as_requestor which would return to us a list of requests where the requestor is the user. That request would also contain the id of whoever the receiver was.

We could then write two instance methods on the User class to gather incoming connection requests or pending (outgoing) connection requests. These would look something like this:

  def incoming_pending_requests
    User.where(id: self.connection_requests_as_receiver.map{|request|request.requestor_id})
  end

  def outgoing_pending_requests
    User.where(id: self.connection_requests_as_receiver.map{|request|request.receiver_id})
  end
Enter fullscreen mode Exit fullscreen mode

The incoming_pending_requests methods will return a list of Users who have requested to connect with the current user. The outgoing_pending_requests method will return a list of users that the current user has requested to connect with. You can imagine what a method to request a connection would look like:

def request_connection(other_user_id)
  Request.create(requestor_id: self.id, receiver_id: other_user_id)
end
Enter fullscreen mode Exit fullscreen mode

Connections

Now that we have our requests figured out. Let's move on to the connections model. This model functions very similarly to the Request model in that we will be using Alias names and foreign keys to allow the Connection instance to belong_to two distinct users.

class Connection < ApplicationRecord
  belongs_to :user_a, class_name: :User
  belongs_to :user_b, class_name: :User
end

create_table "connections", force: :cascade do |t|
  t.integer "user_a_id"
  t.integer "user_b_id"
  t.datetime "created_at", precision: 6, null: false
  t.datetime "updated_at", precision: 6, null: false
end

Enter fullscreen mode Exit fullscreen mode

Almost identical to the Request class, out Connection model holds the foreign key to two separate Users (user_a and user_b) that it belongs to. That way, in our User class we can specify:

class User < ApplicationRecord
...
  has_many :a_users, foreign_key: :user_a_id, class_name: :Connection
  has_many :b_users, foreign_key: :user_b_id, class_name: :Connection

Enter fullscreen mode Exit fullscreen mode

This means that a User has many connected Users where the user is user a, and there are many connected Users where the user is user b. Calling either of these association on the user will return an Active Record association containing a list of Connections where the current user is either a or b. So we should now write a method to accept an incoming request.

  def accept_incoming_connection(requesting_user_id)
    request = Request.find_by(requestor_id: requesting_user_id, receiver_id: self.id)
    requested_user = User.find(requesting_user_id)
    if request
      Connection.find_or_create_by(user_a_id: self.id, user_b_id: requesting_user_id)
      request.destroy
    end
  end
Enter fullscreen mode Exit fullscreen mode

Basically we are finding a request based on whoever has requested to connect with this current user. If there is a request, we then create a connection between the two users and destroy the request. It's that simple! Then to get a list of a user's connected users we write the method:

 def connected_users
    connections = Connection.where("user_a_id = ? OR user_b_id = ?", self.id, self.id)
    User.where(id: connections.map{|c| c.user_a_id != self.id ? c.user_a_id : c.user_b_id})
  end
Enter fullscreen mode Exit fullscreen mode

Here we are getting a list of connections where the user_a_id or user_b_id is the current user. We then get an AR association of users who were included in these connections and who are not the current user.

And that's all! We have successfully implemented a Connected Users relationship similar to something like LinkedIn. I hope you've enjoyed this article and feel free to leave comments or questions.

Top comments (0)