The Magic of the acts_as_follower Gem
Having recently finished my first big group project at the Flatiron School, I decided to write about one of the most useful resources I found during my time working on it. My teammates and I were initially planning on using a Facebook model for relationships between users on our websites, wherein people could add and accept friends through friend requests. We then later realized that the way our site was set up would cater itself more to the Twitter model, wherein people could simply follow other people.
We spent a solid hour or so planning out how our new follower-followee idea would work in terms of models, realizing that it would actually be a relatively complicated relationship. Just as I was about to start generating migrations, I stumbled across a life-saving gem that takes care of most of this for you: the acts_as_follower gem.
This gem allows you to set up a follower-followee relationship between any number of models. Setting it up is incredibly simple. You first add the gem to your gemfile:
You then run the generator in your terminal using the command “rails g acts_as_follower.” This will generate two resources for your new Follow class, a migration:
And a model:
The last step is setting up the relationship in the appropriate models. You simply add the mix-in “acts_as_followable” to the model(s) you want to serve as a followee, and “acts_as_follower” to the model(s) you want to serve as a follower. There are many cases where you’ll want to set these up in two or more different models (e.g. a website like ThoughtCatalog where readers could follow authors), but for my purposes, I wanted a single model (our User model) to have both functions.
As simple as that! Now, not only is the relationship set up, but the gem provides you with a bunch of helper methods that will save you a whole bunch of time. Making an object follow another is as easy as calling #follow on your follower object with an argument of your followable object. This generates a new follow object which belongs to both the follower and followable objects. Here’s how we set it up in our Users controller:
Unfollowing is just as easy; you simply replace #follow with #stop_following:
Another useful method is #following?, which returns true if an object is following another object, and false if it’s not. This was crucial for us in rendering the follow and unfollow buttons on our user show page:
And the result:
And since this gem provides you with a Follow model, you can obviously call #follows on an object to get an ActiveRecord list of all the follows that object has (combining both followee and follower relationships if they’re the same model). This was especially useful for us since our site has achievements, two of them being following a user for the first time and getting a follow for the first time:
Some of the many useful methods this gem provides you with that we didn’t end up using are:
- #follow_count, which returns the total number of objects another object is following.
- #all_following, which returns an ActiveRecord relation of all the objects an object is following.
- #followers, which returns an ActiveRecord relation of all the objects an object is being followed by.
- #block, which prevents an object from following another object
- #unblock, which restores that ability.
At first, a few of my instructors were hesitant about me using this gem because it seemed too easy; they wanted to ensure that I fully understood the mechanics behind it before I started using it. I decided to review the gem’s code carefully to accomplish this.
This gem functions by creating a double polymorphic relationship on the follow model. A polymorphic association means that a model can belong to multiple models using one association. This is set up in the Follow model generated by the gem, as seen on lines 7 and 8:
By setting up two polymorphic associations, the gem allows an infinite amount of models to serve as either a follower, or a ‘followable’, or both. But what’s going on behind the scenes? Let’s take a look inside the gem’s library.
The entire functionality of the program exists in the module ActsAsFollower, which has three main submodules, a FollowScopes module (which contains the Follow class and instance methods), a Follower module and a Followable module. The latter two modules each come with a class method (“acts_as_follower” and “acts_as_followable”, respectively). When placed inside a model (as I did with the User class), this method sets up the other side of relationship with the Follow class and gives you access to all those instance methods.
NOTE: In the case of DrinkUp, none of our relationships were technically polymorphic, since we didn’t have multiple models acting as a follower or as a followable. (One could imagine where this would be useful in the case of, say, an academic website where a student would want to follow classes and other students).
Let’s take a look at how some of these instance methods work:
As can be seen, nearly all of these methods rely on other methods, creating what is essentially a highly efficient, cyclical, readable code library. At the same time, it makes it slightly more difficult to really see the mechanics and logic behind most of these methods since they’re so spread out. For instance, let’s focus on the #follow_count instance method.
This method calls on two Follow class methods:
The #unblocked method (pretty straightforward):
And the #for_follower method (not as straightforward):
This last one calls on yet another method, #defined in a different module (FollowerLib) that gets the name of the Follower class (User in my case).
There’s obviously some heavy backtracing to be done to really understand the logic behind all these methods; however, none of these methods are really that complicated in and of themselves (and all are pretty easy to read). I think it’s the way they’re related to each other that’s the most impressive thing about the structure of this gem, and by delegating responsibilities so effectively, it makes for an extremely efficient gem. Of course, it takes away some of the fun that can be had in figuring out these models, associations, and methods for yourself — but when you’re knee-deep in project mode with a looming deadline and a billion other things to get done, it can save you a whole lot of time.