ruby-on-railsruby-on-rails-3partialscustom-tagredcloth

Using helpers (or render partials) from custom RedCloth tags


I'm trying to call a view helper from a custom RedCloth tag and am having absolutely no luck at all. All of the examples I can find have the custom tag method create and return a literal string without calling any other method at all.

I am trying to create a custom RedCloth tag to output a weekly schedule; the code for which has already been written in a helper as it is also used in other areas of the application.

So here's the (massively reduced) code that my problem boils down to:

app/views/page/show.html.erb:

<%= raw redcloth @page.content %

app/helpers/pages_helper.rb:

module PagesHelper
  def redcloth(content)
    r = RedCloth.new content
    r.extend ScheduleTag
    r.to_html
  end
end

app/helpers/schedule_tag.rb:

module ScheduleTag
  include ScheduleHelper

  def schedule(options)
    build_schedule
  end
end

app/helpers/schedule_helper.rb:

module ScheduleHelper
  def build_schedule
    content_tag :div do
      content_tag :span,
                  "Why won't my helper just work by magic like the rest of " \
                  "rails seems to?"
    end
  end
end

When executed, rails claims the following:

TypeError in Pages#show

Showing /blah/blah/blah/app/views/pages/show.html.erb where line #1 raised:

can't convert Symbol into Integer
Extracted source (around line #1):

1: <%= raw redcloth @page.content %>
Rails.root: /blah/blah/blah

Application Trace | Framework Trace | Full Trace
app/helpers/schedule_helper.rb:3:in `build_schedule'
app/helpers/schedule_tag.rb:42:in `schedule'
app/helpers/pages_helper.rb:22:in `redcloth'
app/views/pages/show.html.erb:1:in  _app_views_pages_show_html_erb___756530284_81829440__535015588'
app/controllers/pages_controller.rb:23:in `show'

I can make a little bit of progress by changing from using a helper to a class, but that class is still having issues using the standard helpers:

/app/helpers/schedule_builder.rb:

class ScheduleBuilder
  extend ActionView::Helpers::TagHelper

  def self.build_schedule
    content_tag :div do
      content_tag :span,
                  "Why won't my helper just work by magic like the rest of " \
                  "rails seems to?"
    end
  end
end

And rails says:

NoMethodError in Pages#show

Showing /blah/blah/blah/app/views/pages/show.html.erb where line #1 raised:

undefined method `output_buffer=' for ScheduleBuilder:Class
Extracted source (around line #1):

1: <%= raw redcloth @page.content %>
Rails.root: /blah/blah/blah

Application Trace | Framework Trace | Full Trace
app/helpers/schedule_builder.rb:5:in `build_schedule'
app/helpers/schedule_tag.rb:42:in `schedule'
app/helpers/pages_helper.rb:22:in `redcloth'
app/views/pages/show.html.erb:1:in  `_app_views_pages_show_html_erb___756530284_81708830__535015588'
app/controllers/pages_controller.rb:23:in `show'

So what am I to do? Am I missing something completely obvious and ought to bury my head in shame, or is it simply not possible to call helpers from anywhere one fancies? Do I need to just suck it up and get the custom tag to duplicate all of the code in my ScheduleHelper module?

Any help would be greatly appreciated.


Solution

  • Okay, so I've come to an acceptable solution myself. So I'll be a good little boy and share.

    Instead of a helper, I'm going to use a class that renders a partial, so the question should really become: "Calling partials from custom RedCloth tags".

    (I've not commented the code, it should be pretty self documenting)

    app/helpers/schedule_tag.rb:

    module ScheduleTag
      def schedule(options)
        Schedule.new.render Date.today.monday
      end
    end
    

    app/helpers/schedule.rb:

    class Schedule < ActionView::Base
      include ActionView::Helpers::TagHelper
      include Rails.application.routes.url_helpers
    
      def render week
        self.view_paths = "app/views"
        super :partial => "schedule/schedule",
              :locals => { :week => week,
                           :schedule => ScheduledClass.get_schedule(week) }
      end
    end
    

    app/view/schedule/_schedule.html.erb:

    <table id="schedule">
      <tbody>
        <tr>
          <td>Hoop-dee-doo, it works!</td>
          <%= content_tag :td do %>
            <%= link_to "so do tag & route helpers", happy_monkey_path(:happy => "very") %>
          <% end %>
        </tr>
      </tbody>
    </table>
    

    As a slight aside, if you were to try this you may stumble upon the problem of RedCloth escaping your options string. Just enclose the options within =='s to get it to pass the options literally through. eg schedule. =={"weeks":["this","next"]}==. This may be a little on the dangerous side, so be sure to check for nastyness.