Rails Current Attributes: Usage, Pros and Cons

Rails introduced ActiveSupport::CurrentAttributes in version 5.2. Since its addition there has been a lot of discussion about it, with advocates both for and against it making valid points. The merits of ActiveSupport::CurrentAttributes actually depend heavily on how it is used.

It is always nice to access attributes like current user in models, and CurrentAttributes provides us with ease to access them in models. The other point is that it gets reset on every request as it uses thread local global variables, so you don’t have to worry about inconsistent data, at least when it comes to controllers.

Here’s an example that is not very different from Rails documentation. Assume we are building a blog system where people are subscribed, and when someone creates a post, a notification is sent to all their followers.

1
2
3
4
5
6
7
8
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :user

  def user=(user)
    super
  end
end

Now you can access the user simply by Current.user in everywhere in the application. Create app/controllers/concerns/authentication.rb and put the following code, courtesy of Rails documentation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# app/controllers/concerns/authentication.rb
module Authentication
  extend ActiveSupport::Concern

  included do
    before_action :authenticate
  end

  private
    def authenticate
      if authenticated_user = User.find_by(id: cookies.encrypted[:user_id])
        Current.user = authenticated_user
      else
        redirect_to new_session_url
      end
    end
end

Everything is set up now, so it’s time for the real work. In application_controller.rb, put this:

1
2
3
class ApplicationController < ActionController::Base
  include Authentication
end

In posts_controller.rb, put the following code:

1
2
3
4
5
class PostsController < ApplicationController
  def create
    Current.posts.create(post_params)
  end
end

Now, in post.rb, we have to make an after_create callback. That will initiate a NotificationService object and will take current user and the post as an attribute like this:

1
2
3
4
5
6
7
8
class Post < ApplicationRecord
  belongs_to :creator, default: -> { Current.user }
  after_create :send_notifications_to_followers

  def send_notifications_to_friends
    NotificationService.new(Current.user.followers, self).call
  end
end

There are areas where using CurrentAttributes might end up causing problems. Some bad practices are always discouraged, such as using global state. Generally speaking, global state may lead to inconsistent data when two or more modules are using it and one of them changes it; the other may not have the appropriate value, or coding will be required to change the value in all places, which is next to impossible. Therefore using this practice in background jobs is discouraged; it can take longer to execute and may lead to problems like inconsistent data.