Rails 5 render set instance variables in locals
Asked Answered
O

2

6

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?

Old answered 30/1, 2017 at 21:14 Comment(7)
:@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)Jerrybuilt
@TarynEast wouldn't that be setting a local variable instead of an instance variable inside the view?Old
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?Jerrybuilt
@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.Old
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)... ?Jerrybuilt
@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.Old
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 ;)Jerrybuilt
O
3

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'
Old answered 31/1, 2017 at 0:25 Comment(0)
B
12

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.

Bury answered 31/1, 2017 at 0:40 Comment(0)
O
3

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'
Old answered 31/1, 2017 at 0:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.