January 8, 2016
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
.
A typical web request carries its 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.
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.
If this blog was helpful, check out our full blog archive.