Using Rails 5 new renderer with Clearance and Devise

on under developer
1 minute read

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