Jonathan Bennett

Clean up your associations using Association Extensions

Sometimes having a custom method on an association would be handy. Well good news, you can do that with Rails!

I recently used this to reorganize and simplify some deeply nested has_many through associations with complex self-referential joins. It was something like this:

class User < ApplicationRecord
	has_many :memberships
	has_many :teams, through: :memberships
	has_many :tasks
end

class Team < ApplicationRecord
	has_many :memberships
	has_many :users, through: :memberships
end

class Membership < ApplicationRecord
	enum :role, %w[peon manager].index_by(&:itself)
	
	belongs_to :user
	belongs_to :team
end

class Task < ApplicationRecord
	belongs_to :user
end

Accessing my tasks is done easily via User#tasks, but if you wanted to get all tasks for all members of the teams you are a manager of, the query could be a bit complicated. However, another option is to add it the association itself using an extension:

class User < ApplicationRecord
	has_many :teams, through: :memberships do
		def managed_users
			team_ids = where(membership: { role: :manager})	.pluck(:id)
			
			User
				.joins(:memberships)
				.where(memberships: { team_id: team_ids})
		end
		
		def managed_user_ids
			managed_users.pluck(:id)
		end
		
		def managed_tasks
			Task.where(user_id: managed_user_ids)
		end
	end
end

# usage
User.find(123).teams.managed_tasks

This is a good way of adding functionality closer to the responsible entity, users instead of teams or tasks in this example.