Rendering partials / view in a rake task / background job / model in Rails 4

ruby, ruby-on-rails, ruby-on-rails-4

Solution

Here's what I compiled from lots of sources and what works for me in Rails 4.

With this Renderer class, you should be able to render Rails 4 views and partials in any context, like background jobs, service objects, models, workers, you name it.

# app/services/renderer.rb
# Render views and partials in rake tasks,
# background workers, service objects and more
#
# Use:
#
# class MyService
#   def render_stuff
#     result = renderer.render(partial: 'tweets/tweet', locals: {tweet: Tweet.first})
#     # or even
#     result = renderer.render(Tweet.first)
#   end
#
#   private
#
#   def renderer
#     @renderer ||= Renderer.new.renderer
#   end
# end
#
class Renderer
  def renderer
    controller = ApplicationController.new
    controller.request = ActionDispatch::TestRequest.new
    ViewRenderer.new(Rails.root.join('app', 'views'), {}, controller)
  end
end

# app/services/view_renderer.rb
# A helper class for Renderer
class ViewRenderer < ActionView::Base
  include Rails.application.routes.url_helpers
  include ApplicationHelper

  def default_url_options
     {host: Rails.application.routes.default_url_options[:host]}
  end
end

Update:

There seems to be an easier solution: http://makandracards.com/makandra/17751-render-a-view-from-a-model-in-rails

ApplicationController.new.render_to_string(
  :template => 'users/index',
  :locals => { :@users => @users }
)
# Mind the weird syntax to set @ variables in :locals.

Update 2:

There's a gem called `render_anywhere` that allows for calling "render" from anywhere: models, background jobs, rake tasks, etc.

Update 3:

In Rails 5, the renderer has been extracted and can be used standalone from background jobs and other places:

ApplicationController.renderer.render(
  partial: 'messages/message',
  locals: { message: message }
)

For Rails <= 4.2, this functionality can be backported with the backport_new_renderer gem.

Problem

I've read a lot on rendering Rails partials and views in rake tasks / background jobs / models. The vast majority of things I have found on Stackoverflow and the web describe approaches working in Rails 3, but they seem outdated and I didn't get them to work (even with quite some time spent experimenting). So, how can I render a partial in a background job in Rails 4? Here's the best approach I've worked out so far (demonstrated in the console). ``` c = ApplicationController.new result = c.render_to_string(partial: 'tweets/tweet', locals: {tweet: Tweet.first}) # => # Tweet Load (0.8ms) SELECT "tweets".* FROM "tweets" ORDER BY "tweets"."id" ASC LIMIT 1 # Author Load (0.6ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = $1 ORDER BY "authors"."id" ASC LIMIT 1 [["id", 1]] # Status Load (0.6ms) SELECT "statuses".* FROM "statuses" WHERE "statuses"."twitter_id" = 367523226848866304 LIMIT 1 # Rendered tweets/_tweet_body.html.slim (17.5ms) # Rendered tweets/_resolved_tweet.html.slim (23.7ms) # Rendered tweets/_tweet.html.slim (28.1ms) # ActionView::Template::Error: undefined method `tweet_path' for #<#<Class:0x007fb21bf797a0>:0x007fb21cb009e8> # from /Users/thomasklemm/.rbenv/versions/2.0.0-p195/lib/ruby/gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:129:in `polymorphic_url' ``` Any ideas? Thanks in advance! Update: The `tweet_path` mentioned above is indeed not defined. This error resulted from linking to a path `= link_to 'Tweet', [@project, tweet]` (slim templates) using an instance variable that would be present in views inheriting from a certain controller, but not when rendered outside of this context. I solved this going through the appropriate association instead `= link_to 'Tweet', [tweet.project, tweet]`.

Original source