Mark Grealish

the life and code of a crazy cat man


Sometimes it’s not all that bad

in family

Googly-eyed Garrett

Self-referential has_many :through relationship in Rails 5

in code

A self-referential has_many :through relationship is one where a class interacts with itself through a join table. The classic use are followings:

  • I follow another user.
  • Another user follows me.

So a Following is a two-way relationship between the User class and itself. There are a bunch of guides for this that are either outdated or overly-complex. In that light, here is the simplest route to a self-referential relationship in Rails 5:

Followings Database Migrations

There’s an actual :users table, but the :followed_users doesn’t exist. I chose to use a verbose name for future readability.

class CreateFollowedUsers < ActiveRecord::Migration[5.0]
  def up
    create_join_table :users, :followed_users, table_name: :followings do |t|
      t.index [:user_id, :followed_user_id], unique: true
    end
  end

  def down
    drop_table :followings
  end
end

This a simple, stock join otherwise.

Following Model

Again, simple, with one caveat: It belongs to a :user and to a :followed_user through the User class.

class Following < ApplicationRecord
  belongs_to :user
  belongs_to :followed_user, class_name: 'User'
end

The uniqueness (index) constraint on the join table will stop one user from following another more than once, but it won’t prevent a solipsistic self-following:

  validate :realism

  private

  def realism
    return unless user_id == followed_user_id
    errors.add :user, 'Only a solipsist would follow themselves.'
  end

User Model/Following Concern

I separated the User model code into a concern given the amount of helper methods I wrote (follow, followed_by?, etc). This permitted me to neatly encapsulate functionality and testing. Here is my solution with other callbacks and methods removed:

class User < ApplicationRecord
  has_many :followings
  has_many :followed_users, through: :followings

  has_many :followers, foreign_key: :followed_user_id, class_name: 'Following'
  has_many :follower_users, through: :followers, source: :user
end

Useage

@user.follower_users
@user.followed_users

@user.followed_users << @other_user
@other_user.follower_users << @user

@user.followed_users.delete @other_user
@other_user.follower_users.delete @user

Kill puma

in code

Grr, rage, annoyance, rabble.

kill -9 $(lsof -wni tcp:3000 -t)

This is the unseen side of my professional working life

in animals

The struggle is real.

Cookie 'helps' me iron

Oh, Cookie

in animals

“No Cookie! Feet are friends, not food! Feet are not for teeth!”

Sullen Cookie loaf

Add sub-controller modules to Rails 5 routing concerns

in code

Routing concerns have been a thing since Rails 4, but this morning I ran into a real annoyance: the documentation doesn’t adequately explain how to pass a module, in cases where you use separate sub-controllers. More than that, even-what if I want to pass any other options?

Here’s what I came up with, using splats and blocks:

concern :likeable do |options|
  resource :like, only: [:create, :destroy], **options
end

resources :posts do
  concerns :likeable, module: :posts
end

resources :comments do
  concerns :likeable, module: :comments
end

The output routes appear thus:

comment_like DELETE   /comments/:comment_id/like(.:format)    comments/likes#destroy
             POST     /comments/:comment_id/like(.:format)    comments/likes#create
...
   post_like DELETE   /posts/:post_id/like(.:format)          posts/likes#destroy
             POST     /posts/:post_id/like(.:format)          posts/likes#create