5

I have some complicated PDF generation logic that requires rendering a view outside of a controller and then passing the HTML into WickedPDF:

ActionView::Base.send(:define_method, :protect_against_forgery?) { false }
av = ActionView::Base.new
av.view_paths = ActionController::Base.view_paths

income_statement_html = av.render :template => "reports/income_statement.pdf.erb", :layout => 'layouts/report.html.erb',
                                  locals: {:@revenue_accounts => revenue_accounts,
                                           :@expense_accounts => expense_accounts,
                                           :@start_date => start_date,
                                           :@end_date => end_date,
                                           :@business => business}

This all works fine on Rails 4 but has stopped working when we upgraded to Rails 5.

All the instance variables we are setting here end up as nil inside the view. Is there still a way to set instance variables from the render call like this?

Deekor
  • 9,144
  • 16
  • 69
  • 121
  • `:@revenue_accounts` seems pretty weird... try just: `:revenue_accounts` (and in your template, refer to them as local variables eg `revenue_accounts` instead of the more global `@revenue_accounts`) – Taryn East Jan 30 '17 at 22:37
  • @TarynEast wouldn't that be setting a local variable instead of an instance variable inside the view? – Deekor Jan 30 '17 at 22:38
  • Yes... and that should also work. The idea is you want that data to go into the template so that it renders properly right? the fact that it's a local variable vs instance variable should be irrelevant to that end goal? – Taryn East Jan 30 '17 at 22:39
  • @TarynEast well the template is used for other things around the app which relies on instance variables. So for this PDF I'm trying to write DRY code by reusing the template and setting the instance variables through the locals option. – Deekor Jan 30 '17 at 22:42
  • You can change those other things to also use the local variables (pass in the instance variables in the other places that call this template)... ? – Taryn East Jan 30 '17 at 22:43
  • @TarynEast Ya.. just trying to avoid that. This was working fine in Rails 4 so I was hoping there was a quick solution without a bunch of refactoring. – Deekor Jan 30 '17 at 23:07
  • IMO it's better to refactor it so it all works smoothly (and the way you expect) than try to mangle/patch the way you're doing it in this one instance so that you don't have to go changing other code... YMMV ;) – Taryn East Jan 30 '17 at 23:30

2 Answers2

11

Rails 5 introduced ActionController::Base.render, which allows you to do this instead:

rendered_html = ApplicationController.render(
  template: 'reports/income_statement',
  layout: 'report',
  assigns: {
    revenue_accounts: revenue_accounts,
    expense_accounts: expense_accounts,
    start_date: start_date,
    end_date: end_date,
    business: business
  }
)

Which you can then pass to WickedPDF:

WickedPdf.new.pdf_from_string(rendered_html)

You can read more about .render and using it with WickedPDF, as well get some examples of how to extract this functionality into reusable objects on this blog post.

coreyward
  • 77,547
  • 20
  • 137
  • 166
2

ActionView::Base has a method assign which can be called to set the instance variables.

    av.assign({revenue_accounts: revenue_accounts,
           expense_accounts: expense_accounts,
           start_date: start_date,
           end_date:  end_date,
           business: business})

income_statement_html = av.render :template => "reports/income_statement.pdf.erb", :layout => 'layouts/report.html.erb'
Deekor
  • 9,144
  • 16
  • 69
  • 121