Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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].

like image 233
Thomas Klemm Avatar asked Aug 15 '13 22:08

Thomas Klemm


People also ask

How do I change the render behavior in rails?

There are a variety of ways to customize the behavior of render. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well.

How to use partials in rails?

How to use Partials in Rails 1 Go to app > views. 2 Create a folder called shared. 3 Create a file to store your code in and name it using the syntax: _ [partial].html.erb Insert partial ⬇️ More ...

How does rails render a view as a response?

When Rails renders a view as a response, it does so by combining the view with the current layout, using the rules for finding the current layout that were covered earlier in this guide. Within a layout, you have access to three tools for combining different bits of output to form the overall response:

How do I get rails to render with a specific location?

You can use the :layout option to tell Rails to use a specific file as the layout for the current action: You can also tell Rails to render with no layout at all: You can use the :location option to set the HTTP Location header: Rails will automatically generate a response with the correct HTTP status code (in most cases, this is 200 OK ).


2 Answers

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.

like image 82
Thomas Klemm Avatar answered Oct 18 '22 02:10

Thomas Klemm


Make sure you're loading the rails environment in the job. If that is already done you can try something like:

include Rails.application.routes.url_helpers
like image 23
Hderms Avatar answered Oct 18 '22 03:10

Hderms