Dom Lizarraga

dominiclizarraga@hotmail.com

Follow as Polymorphic

In this blog post I’ll share my findings when developing a Follow model (User can follow another User, Post, Comapny) with a polymorphic property.

With the next models you can create a ‘Following’ feature that can be applied to almost any model you want, just add one line of code.

class User < ApplicationRecord
  # Users that follow this user 'Followers'
  has_many :followers, class_name: 'Follow', as: :followable

  # Entities this user follows 'Following'
  has_many :following, class_name: 'Follow', foreign_key: 'user_id'
end

class Follow < ApplicationRecord
  belongs_to :user # who is making/pushing the button "Follow" (doing the following)
  belongs_to :followable, polymorphic: true

  # This validation doesn't allow that user_1 follow user_2 twice
  validates :user_id, uniqueness: { scope: [:followable_type, :followable_id] }
end

Let’s try out the code in rails console

rails console

u1 = User.first
u2 = User.last

follow = u1.following.create(followable: u2)

Follow Create (3.8ms)  INSERT INTO "follows" ("user_id", "followable_type", "followable_id", "created_at", "updated_at") 
VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["user_id", 1], ["followable_type", "User"], ["followable_id", 18], 
["created_at", "2024-02-29 05:25:28.228037"], ["updated_at", "2024-02-29 05:25:28.228037"]]
  TRANSACTION (0.6ms)  COMMIT
=> #<Follow:0x0000000102d226e0
 id: 6,
 user_id: 1,
 followable_type: "User",
 followable_id: 18,
 created_at: Thu, 29 Feb 2024 05:25:28.228037000 UTC +00:00,
 updated_at: Thu, 29 Feb 2024 05:25:28.228037000 UTC +00:00>

Follow.count
=> 1

u1.following.create(followable: u2)
=> TRANSACTION (0.8ms)  ROLLBACK (due to model validation)

# class User
# def follows?(user)
#   following.exists?(followable: user)
# end

u1.follows? u2
=> true

u2.followers
=> [#<Follow:0x000000012eb126d0
  id: 6,
  user_id: 1,
  followable_type: "User",
  followable_id: 18,
  created_at: Thu, 29 Feb 2024 05:12:17.215129000 UTC +00:00,
  updated_at: Thu, 29 Feb 2024 05:12:17.215129000 UTC +00:00>]

u1.following
[#<Follow:0x0000000102b63fc0
  id: 6,
  user_id: 1,
  followable_type: "User",
  followable_id: 18,
  created_at: Thu, 29 Feb 2024 05:12:17.215129000 UTC +00:00,
  updated_at: Thu, 29 Feb 2024 05:12:17.215129000 UTC +00:00>,
 #<Follow:0x0000000102d226e0
 ...]

And it worked for other models like (Post, Company, etc)

class Post < ApplicationRecord
  has_many :followers, class_name: 'Follow', as: :followable
end
user = User.find(user_id)
post = Post.find(post_id)

user.following.create(followable: post)
  TRANSACTION (0.6ms)  COMMIT
=> 
#<Follow:0x0000000150038080
 id: 8,
 user_id: 1,
 followable_type: "Post",
 followable_id: 92,
 created_at: Thu, 29 Feb 2024 05:56:29.921783000 UTC +00:00,
 updated_at: Thu, 29 Feb 2024 05:56:29.921783000 UTC +00:00>

We’ve explored creating a follower system in Rails, showcasing the flexibility of polymorphic associations and the importance of validations. This guide provides a solid foundation for adding social functionalities to your Rails applications, ensuring a robust and scalable feature set.

February 7, 2024   @domlizarraga_