Skip to content

Instantly share code, notes, and snippets.

@hopsoft
Last active September 9, 2022 06:00
Show Gist options
  • Save hopsoft/9c84c354e354c16898a44625df094347 to your computer and use it in GitHub Desktop.
Save hopsoft/9c84c354e354c16898a44625df094347 to your computer and use it in GitHub Desktop.
Render views outside of the standard request context (i.e. ActiveJob) with Devise/Warden
class ExampleJob < ApplicationJob
queue_as :default
def perform(user)
# do some work
# HACK: get around limitations in devise/warden when rendering
# views outside the context of a formal http request
renderer = ::ApplicationController.renderer.new
renderer_env = renderer.instance_eval { @env }
warden = ::Warden::Proxy.new(renderer_env, ::Warden::Manager.new(Rails.application))
renderer_env["warden"] = warden
html = renderer.render(partial: "path/to/partial")
ActionCable.server.broadcast "user-#{user.id}", message_type: "example", html: html
end
end
@fredsifu
Copy link

fredsifu commented Nov 2, 2017

Good hack! Spent a few days looking for the problem and a solution.

I used your code in the parent class like so:

class ApplicationJob < ActiveJob::Base
  before_perform :wardenize
  
  def render(options = nil, extra_options = {}, &block)
    @job_renderer.render(options, extra_options, &block)
  end
  
  private
    def wardenize
      @job_renderer = ::ApplicationController.renderer.new
      renderer_env = @job_renderer.instance_eval { @env }
      warden = ::Warden::Proxy.new(renderer_env, ::Warden::Manager.new(Rails.application))
      renderer_env["warden"] = warden
    end
end

This allows for a standard render call in the job:

class NotificationRelayJob < ApplicationJob
  queue_as :default

  def perform(notification)
    html = render partial: "notifications/notification", locals: {notification: notification}, formats: [:html]
    ActionCable.server.broadcast "notifications:#{notification.recipient_id}", html: html
  end
end

Hopefully, this will help others as well until there's an official way of doing this within rails or the Devise gem.
Thanks!

@kengreeff
Copy link

Epic, saved us a lot of time! Thank you.

We setup a module in lib/modules/view_renderer.rb

class ViewRenderer
  def self.renderer
    renderer = ::ApplicationController.renderer.new
    renderer_env = renderer.instance_eval { @env }

    warden = ::Warden::Proxy.new(renderer_env, ::Warden::Manager.new(Rails.application))
    renderer_env["warden"] = warden

    return renderer
  end
end

Whenever we need to use the renderer we just use ViewRenderer.renderer

@joseramonc
Copy link

Thanks for sharing! Worked flawlessly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment