Rendering partials with the new Rails 5 ActionController::Renderer
provides a nice shortcut to render templates outside the normal controller flow without defining own proxy classes. This is useful for a variety of use cases, like pushing partials down a MessageBus pipe to frontend or mobile, or PDF generation with WicketPDF.
But to access authentication stuff, like Clearance/Devise (current_user
) or CanCan (current_ability
) is not obvious, as the whole controller environment and middlewares are missing.
Here, I am using Clearance, which needs to store it’s authentication stuff in the rack request env under the key :clearance
.
(tested with Rails 5.0.0.beta3)
Clearance
Unfortunately, at the moment there is no direct access to the env from the ApplicationController.renderer.render
method. We need to initialize the renderer manually and even need to whitelist the rack key name :clearance
(otherwise, it will be replaced with nil
atm).
class ApplicationController < ActionController::Base
...
def self.render_with_signed_in_user(user, *args)
ActionController::Renderer::RACK_KEY_TRANSLATION[:clearance] ||= :clearance
renderer_class = self.renderer
session = Clearance::Session.new({}).tap{|i| i.sign_in(user) }
renderer = self.renderer.renderer_class.new(clearance: session)
renderer.render(*args)
end
Use it like:
ApplicationController.render_with_signed_in_user(user, 'posts/post', locals: { post: @post })
Devise
Solving the same problem with Devise 4.0.0.rc2
ActionView::Template::Error: undefined method `authenticate' for nil:NilClass
from ../gems/devise-4.0.0.rc2/lib/devise/controllers/helpers.rb:124:in `current_user'
from ../gems/actionpack-5.0.0.beta3/lib/abstract_controller/helpers.rb:67:in `current_user'
We solve that in a similar way by initializing a Warden Proxy:
def self.render_with_signed_in_user(user, *args)
ActionController::Renderer::RACK_KEY_TRANSLATION['warden'] ||= 'warden'
proxy = Warden::Proxy.new({}, Warden::Manager.new({})).tap{|i| i.set_user(user, scope: :user) }
renderer = self.renderer.new('warden' => proxy)
renderer.render(*args)
end
Backported New Renderer
Using the backport_new_renderer
Gem for Rails 4.2 is a little different, as the class api is deviated from Rails master: Assign the env directly through a acessor.
def self.render_with_signed_in_user(user, *args)
renderer = ApplicationController.renderer.new
renderer.env[:clearance] = Clearance::Session.new({}).tap{|i| i.sign_in(user) }
renderer.render(*args)
end
In this blog article there are also some tips for general debugging & access full url from rendered partials