Calling Rails Render Partial in a Model or Background Task

Why?

You want to store raw HTML in the database for a given model and you want to do this with the ease of partials and not a bunch of nasty string manipulation.

Rails makes this difficult for you by not giving you access to the render method when not called from ActionController.  However, you’d like to call the partial helper from within a model, or a background task (such as BackgrounDRb or Starling).

How?

As an example, let’s say you have a Page model made up of a title and a body.  You’d like to cache what a rendered page would look like in the model as the attribute cached_content.

The old way.  This really sucks.

class Page < ActiveRecord::Base

  def write_cache
    the_content = "<div class='title'>"
    the_content += "<h1>#{self.title}</h1>"
    the_content += "</div>"
    the_content += "<div class='body'>"
    the_content += "#{self.body}"
    the_content += "</div>"

    self.cached_content = the_content
    self.save
  end

end

The new way.  This is so much easier and cleaner.

class Page < ActiveRecord::Base

  def write_cache
    self.cached_content = ActionView::Base.new(Rails::Configuration.new.view_path).render(:partial => "pages/show", :locals => {:page => self})
    self.save
  end

end

app/views/pages/_show.html.erb

<div class="title">
  <h1>
    <%= page.title %>
  </h1>
</div>

<div class="body">
  <%= page.body %>
</div>
Wednesday, October 29th, 2008 Programming

11 Comments to Calling Rails Render Partial in a Model or Background Task

  • Joe Goggins says:

    Love the tip, it breaks though when your view code uses helper methods, to do this, I stole from Rails internal private method, initialize_template_class:

    template_instance = ActionView::Base.new(Rails::Configuration.new.view_path)

    template_instance.extend ApplicationController.master_helper_module

    content = template_instance.render(:partial => self.view_path(self.profile) + ‘/show’, :locals => {:item => self})

  • Joel says:

    I take it link_to/url_for would fit into the situation Joe Goggins mentions above?

    I’m trying to render a partial with some links to nested resources and I keep getting this error:

    The error occurred while evaluating nil.url_for

    I tried out Joe’s sample code as well, still no dice. Anyone have any thoughts?

  • yzhanginca says:

    I wanted to use similar technique to render :update. But it gave me an error:

    NoMethodError: You have a nil object when you didn’t expect it!
    The error occurred while evaluating nil.with_output_buffer

    Not sure if I need to include any modules.

  • you can use in views part,

    “partialfolder/partialname” %>

    Regards,

    Thiyagarajan Veluchamy
    Test Lead

  • Dave Lynam says:

    It looks as though if you add:
    include ActionView::Helpers::UrlHelper
    include ActionController::UrlWriter
    The nil / noMethodEroor gets resolved.

  • Felix says:

    I got the same “undefined method `url_for’ for nil:NilClass”.

    Ttried to add “include ActionView::Helpers::UrlHelper” and “include ActionController::UrlWriter” as Dave suggested but no luck(undefined method ‘include’), I changed “include” to “require” but rails complains “can’t convert Module into String”.

    Besides, I need also to use some functions defined in application_helper.rb in the model, how can I do that?

    Thanks.

  • Leave a Reply

    Justin has been obsessing over writing simple Web software using Ruby on Rails since 2007. He's also an entrepreneur and Lean Startup expert. Learn more

    View Justin Britten's profile on LinkedIn

    Subscribe to Justin Britten's blog Follow Justin Britten on Twitter Network with Justin Britten on LinkedIn
     
    Prefinery: Simple, online beta management software'

    Launch a private beta for your Web application in minutes. Prefinery takes care of collecting e-mail addresses, generating invitation codes, and sending invitations for your private beta. Your customers never leave your site, and e-mail invitations are sent from your address.

    Justin is Founder and CEO of Prefinery.