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.