When working on another project for my boot camp, I decided to build an application centered around saving and updating your current reading list. I wanted the reading list to be private for each user, but also have a social media aspect where users can share their book recommendations publicly. In the early planning process, I considered setting up a mutual friendship relationship for the users. However, after some research, the most logical way to set up this feature for my application was through a follower/following relationship.
Luckily, ActiveRecord makes setting up these relationships possible with a limited amount of code. Most of the work is done through a join table to alias the users into 'followers' and 'following'.
# in db/migrate/(file_name such as 001_create_follows.rb) class CreateFollows < ActiveRecord::Migration[6.0] def change create_table :follows do |t| t.integer :follower_id t.integer :following_id end end end
Both follower_id and following_id are aliases for user_id and are necessary to distinguish between the two. So the basic idea is to be able to create a new 'follow' entry in the database through the code like:
Follow.create(follower_id: User.first.id, following_id: User.second.id) # then the hope is that these would be true: User.first.following.include?(User.second) User.second.followers.include?(User.first)
To make these associations work properly, we need to do a little aliasing magic. Let's start with the Follow model.
# in app/models/follow.rb class Follow < ApplicationRecord belongs_to :follower, foreign_key: 'follower_id', class_name: 'User' belongs_to :following, foreign_key: 'following_id', class_name: 'User' end
This code tells the Follow model that the foreign keys in the database of 'following_id' and 'follower_id' are actually user_ids. It also tells the model that we will be referring to these instances as 'follower' and 'following' such as:
Follow.first.follower #=> instance of the User model
Now that we have that setup, here comes the real magic. In the User model, we need to alias the Follow model.
# in app/models/user.rb class User < ApplicationRecord has_many :follows # Allows association to view list of users who follow a given user i.e. user.followers has_many :follower_relationships, foreign_key: :following_id, class_name: 'Follow' has_many :followers, through: :follower_relationships, source: :follower # Allows association to view list of users who follow a given user i.e. user.following has_many :following_relationships, foreign_key: :follower_id, class_name: 'Follow' has_many :following, through: :following_relationships, source: :following end
The magic here is in 'follower_relationships' and 'following_relationships'. These are kind of like pseudo tables that are aliasing the follows table data. This way the model can basically sub out the 'follower_id' or the 'following_id' with that user_id, relate it to the inverse, then interpret that inverse as a user as well. If I didn't explain that well enough, just know that this is necessary mostly because ActiveRecord doesn't allow a 'foreign_key:' in a macro with a 'through:' relationship.
These macros can be difficult to understand when starting out (I'm still wrapping my head around them!), but they are very useful in making simple, clean code. Once set up properly, they do all the heavy lifting for you, so they're definitely worth learning. In practice, I always find it best to understand how and why they work as well to save time debugging down the road as well as just having a better understanding of my own code.