This blog is part of our Rails 5 series.

Rails request-response cycle is very easy to understand. A request hits the app, a route is matched to a controller action from routes.rb, and finally controller action processes the request and renders HTML or JSON based on the type of the request.

But sometimes we want to render our HTML or JSON response outside of this request-response cycle.

For example let’s say user is allowed to download PDF version of a report on web. This can be done using request-response cycle. We also need to send a weekly report to managers and the email should have the report as an attachment. Now we need to generate the same PDF but since emails are sent using background job the request-response cycle is missing.

Rails 5 has this feature baked in.

Let’s say we have a OrdersController and we want to render individual order outside of controller.

Fire up rails console and execute following command.

OrdersController.render :show, assigns: { order: Order.last }

This will render app/views/orders/show.html.erb with @order set to Order.last. Instance variables can be set using assigns in the same way we use them in controller actions. Those instance variables will be passed to the view that is going to be rendered.

Rendering partials is also possible.

OrdersController.render :_form, locals: { order: Order.last }

This will render app/views/orders/_form.html.erb and will pass order as local variable.

Say I want to render all orders, but in JSON format.

OrdersController.render json: Order.all
# => "[{"id":1, "name":"The Well-Grounded Rubyist", "author":"David A. Black"},
       {"id":2, "name":"Remote: Office not required", "author":"David & Jason"}]

Even rendering simple text is possible.

>> BooksController.render plain: 'this is awesome!'
  Rendered text template (0.0ms)
# => "this is awesome!"

Similar to text, we can also use render file and render template.

Request environment

A typical web request carries it’s own environment with it. We usually handle this environment using request.env in controllers. Certain gems like devise depends on env hash for information such as warden token.

So when we are rendering outside of controller, we need to make sure that the rendering happens with correct environment.

Rails provides a default rack environment for this purpose. The default options used can be accessed through renderer.defaults.

>> OrdersController.renderer.defaults
=> {:http_host=>"example.org", :https=>false, :method=>"get", :script_name=>"", :input=>""}

Internally, Rails will build a new Rack environment based on these options.

Customizing the environment

We can customize environment using method renderer. Let’s say that we need “method as post” and we want “https to be true” for our background job processing.

renderer = ApplicationController.renderer.new(method: 'post', https: true)
# => #<ActionController::Renderer:0x007fdf34453f10 @controller=ApplicationController, @defaults={:http_host=>"example.org", :https=>false, :method=>"get", :script_name=>"", :input=>""}, @env={"HTTP_HOST"=>"example.org", "HTTPS"=>"on", "REQUEST_METHOD"=>"POST", "SCRIPT_NAME"=>"", "rack.input"=>""}>

Now that we have our custom renderer we can use it to generate view.

renderer.render template: 'show', locals: { order: Order.last }

Overall this is a nice feature which enables reuse of existing code.