# Don't call controller from background job, please. Do it differently!

<p>
  <figure>
    <img src="<%= src_fit("pdf/pdf_rails.jpg") %>" width="100%">
    <details>
      <a href="http://www.flickr.com/photos/mucio/2889899144/sizes/z/">Photo</a>
      remix available thanks to the courtesy of
      <a href="http://www.flickr.com/photos/mucio/">francescomucio</a>.
      <a href="http://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a>
    </details>
  </figure>
</p>

Some time ago while doing Code Review for one of our clients I stumbled
on a very unexpected piece of code. Background job calling a controller.

```ruby


class OrderPdfJob
  def self.perform(order_id, locale)
    controller = PdfController.new
    pdf = controller.generate_pdf_for_order(order_id, locale)
    OrderPdf.create file: pdf, order: order
    # ...
  end
end

class PdfController < ApplicationController
  skip_before_filter :authenticate_user!

  def generate_pdf_for_order(order_id, locale)
    # ...
  end
end
```

I was a bit speechless. Calling controller out of HTTP request/response
loop sounds like an odd thing to do. I had to investigate and refactor.

<!-- more -->

As you can probably imagine, it turned out that the PDF was generated based
on HTML, and the HTML was generated by the controller. After all, controllers
in Rails can render views, that's one of their responsibilities. But they
also take care of HTTP request parsing, managing cookies, session,
authentication, authorization, content negotiation building respone, 
and all the stuff necessary for your webapp to work.

But when you call a controller from background job, in a situation like this,
none of this things happens. What you actually want is not a `Controller` but
just a `Renderer`. If we can achieve that, we won't need to have this ugly
`skip_before_filter` in the code, and our intentions are going to be way more
clear for the rest of the team reading the code.

## Rails, render me this

After a few moments of struggling with rails, reading the doc, and trying
things in `irb`, it turned out that all we need is this.

```ruby
# app/renderers/pdf_renderer.rb
require 'pdfkit'

class PdfRenderer < ActionController::Metal
  include ActionController::Helpers
  include ActionController::Rendering
  include ActionController::Renderers::All
  include AbstractController::Layouts

  append_view_path "app/views"

  View = Struct.new(:layout, :action, :locals)

  def render_pdf(view)
    html = render_to_string(
      layout: view.layout,
      action: view.action,
      locals: view.locals
    )
    kit  = PDFKit.new(html, {
      page_size:     'A4',
      margin_top:    '0.5in',
      margin_bottom: '0.5in',
      margin_left:   '0.5in',
      margin_right:  '0.5in',
    })
    kit.to_pdf
  end
end
```

```ruby
# app/renderers/order_pdf_renderer.rb
class OrderPdfRenderer < PdfRenderer
  helper :orders
  
  def generate_pdf_for_order(order, locale)
    I18n.with_locale(locale) do
      render_pdf(View.new.tap do |v|
        v.layout = "pdf"
        v.action = "order.html.erb"
        v.locals = {order: order}
      end)
    end
  end
end
```

The view for the rendered pdf is in `app/views/order_pdf_renderer/order.html.erb`
file. The layout in `app/views/layouts/pdf.html.erb`. Nothing surprising here. And
if you prefer presenters/decorators over helpers you can just remove
`include ActionController::Helpers` and `helper :orders` and have even less code to
maintain.

During the refactoring this part of code I also extracted a separate layer
responsible for getting all the data required to generate the PDF. As a result the
renderer is called with `order` and not just `order_id`. That makes testing everything
easier.

We could probably try to go [even further](https://gist.github.com/paneq/9ccfb2722c80d2cca77b)
and use less and less of what we don't need from Rails in such situation. Perhaps there is 
a clean way of using `ActionView` part for such purpose without the coupling to
controllers and HTTP-context at all?

## Summary

I hope this technique can be a useful [tool in your refactoring
controllers toolbox](http://rails-refactoring.com/) . Whenever you stumble upon a
controller methods that are not used to deal with HTTP requests-response loop,
think about extracting them into a separate object and giving a proper name. Even if
they relay on Rails controllers features, taking from Rails what you just need,
might be easier than you think.

## Update for Rails 4.1

The list of modules to include is a little different:

```ruby
class PdfRenderer < ActionController::Metal
  include ActionView::ViewPaths
  include AbstractController::Rendering
  include AbstractController::Helpers
  include ActionController::Helpers
  include ActionView::Rendering
  include ActionView::Layouts
  include ActionController::Rendering
  include ActionController::Renderers
  include ActionController::Renderers::All
end
```
