Customized Activity Feeds in Rails

Kerry Sheldon
4 min readAug 25, 2016

For a personal project in my third module at the Turing School (Turing’s program consists of four 6-week modules), I built an application that would allow beginning programmers to set goals and track practice sessions for various programming skills they were working to develop. The app was intended as a motivation and productivity tool for beginning programmers.

As part of the app, I wanted to help users feel like they were part of a community of learners. I also wanted users to see the practice sequences of other users, in order to help them direct their own learning. During the two week project, I was able to incorporate an activity feed that showed the activity of all users using the Public Activity gem. However, I wanted to take this a step further.

Specifically, I wanted users to be able to follow, and be followed by, like-minded users — users working on similar goals or skills, etc. And I wanted users to get a customized feed of activity performed by the users they were following.

This addition required me to make three primary changes:

  1. Introduce the concept of followers to the business logic — using a self-referential relationship.
  2. Customize the activity feed to the user — showing only the activity of the people that the user was following.
  3. Allow users to search for other users by username and keywords, and to follow/unfollow individual users.

In this post, I will cover some of the key aspects of implementing the first two additions.

So… Followers are just Users

Followers weren’t a new “thing” that needed a model or database table. They were just users. But I needed to treat them like a “thing” — I needed to be able to call user.followers and user.users_followed and get a collection of users back.

Thankfully, we had a memorable lesson at Turing on self-referential relationships, where we built a binary search tree in Rails. In a binary search tree, each node has a left node and a right node, which are instances of other nodes. In Rails 5, it looked like this (note that ApplicationRecord is a Rails 5 thing — ActiveRecord::Base for Rails 4 and earlier):

class Node < ApplicationRecord
belongs_to :right, class_name: Node
belongs_to :left, class_name: Node
end

In my case, I needed to add a UserFollower model that carried references to a User and Follower. And, it needed to know that the Follower was just an instance of User:

class UserFollower < ApplicationRecord
belongs_to :user
belongs_to :follower, class_name: User
end

With that addition, I could get what I needed from the User model (i.e. user.followers and user.followed_users):

class User < ApplicationRecord
has_many :user_followers
has_many :followers, through: :user_followers
def followed_users
User.joins(:user_followers)
.where("user_followers.follower_id = #{self.id}")
end
end

Who “owns” the activity?

However, now I needed to figure out how to get a customized activity feed. My activity feed shows activity related to skills, goals, and practice sessions. Any create action on these three models is incorporated into the activity feed. When I was showing a feed of all users, all I needed on these models was the following:

class Skill < ApplicationRecord
include PublicActivity::Model
tracked
end

Now, I needed to limit the activities to a selection of users. Fortunately, Public Activity objects carry owner_type and owner_id attributes. These attributes default to nil, unless an owner is established on the model. I simply needed to give skills, goals and practice sessions an owner. In essence, I needed activities to be owned by their associated user.

Easy enough:

class Skill < ApplicationRecord
include PublicActivity::Model
tracked owner: :user
belongs_to :user
end

However, while skills belong to a user in my business logic, goals and practice sessions belong to a skill. Notably, I can’t get from goals or practice sessions back to a user, because of the direction of the relationship. That is, a skill does not hold on to a reference to a goal or a practice session. So goal.skill.user does not work, and belongs_to :user, through: : skill is simply not a thing.

But, in order to establish a user as an owner of a goal activity, I needed to get there. Fortunately, I was able to use the delegate method to achieve this result:

class Goal < ApplicationRecord
include PublicActivity::Model
tracked owner: :user
belongs_to :skill
delegate :user, to: :skill
end

And, with that, I can customize an activity log for the user. The following method gives me the 20 most recent activities performed by users that the current user is following:

PublicActivity::Activity.order("created_at desc")
.where(owner_type: "User",
owner_id: current_user.users_following.map {|u| u.id})
.limit(20)

--

--

Kerry Sheldon

Software Developer. Graduate of Turing School of Software and Design.