Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails passing a block to a helper method

Viget Labs posted an article and gist detailing a rails helper method for adding a particular class (like .selected or .active) to a navigation link if it's url matches the current path.

You can use it in your layout like so:

= nav_link "News", articles_path, {class: "btn btn-small"}

<!-- which creates the following html -->

<a href="/articles" class="btn btn-small selected">News</a>

Nice. I'm using bootstrap, and want to have an icon in my button, so I need to generate the following html:

<a href="/articles" class="btn btn-small selected"><i class="icon-home"> </i> News</a>

I forked the gist and figured out a simple way of doing it. My fork lets the developer pass :inner_html and :inner_class to the helper like so:

= nav_link "News", articles_path, {class: "btn btn-small"}, {inner_html: 'i', inner_class: 'icon-home'}

It works fine, but I don't like my underlying implementation:

def link
  if @options[:inner_html]
    link_to(@path, html_options) do
      content_tag(@options[:inner_html], '', :class => @options[:inner_class]) + " #{@title}"
    end
  else
    link_to(@title, @path, html_options)
  end
end

As you can see, I'm passing the new options to content_tag inside the block of a link_to method. I was hoping I would be able to refactor it in a few ways.

First of all, I'd prefer to be able to do this in my view:

= nav_link "News", articles_path, {class: "btn btn-small"} do
  %i.icon-home

I want to give the inner html as a block, and not as attributes of the option hash. Can anyone give me any pointers on how to achieve this?

I thought it would a simple case of telling the nav_link method to accept a block:

def nav_link(title, path, html_options = {}, options = {}, &block)
  LinkGenerator.new(request, title, path, html_options, options, &block).to_html
end

class LinkGenerator
  include ActionView::Helpers::UrlHelper
  include ActionView::Context

  def initialize(request, title, path, html_options = {}, options = {}, &block)
    @request      = request
    @title        = title
    @path         = path
    @html_options = html_options
    @options      = options
    @block        = block
  end

  def link
    if @block.present?
      link_to @path, html_options do
        @block.call
        @title
      end
    end
  end

But this fails to output the icon, and instead inserts a number (4). I don't get it clearly. Anyone got any advice. Where can I go to read more about this sort of thing, as I really want to be able to figure stuff like this out without having to ask on stackoverflow.

like image 963
stephenmurdoch Avatar asked Aug 10 '12 13:08

stephenmurdoch


2 Answers

I tried your problem and the following worked for me perfectly, in the helper:

  def my_link(title, path, &block)
    if block_given?
      link_to path do
        block.call
        concat(title)
      end
    else
      link_to title, path
    end
  end

Usage:

my_link "No title", User.first do
  %i.icon-home 
like image 181
Matzi Avatar answered Oct 16 '22 05:10

Matzi


The solution in the end was as follows:

# capture the output of the block, early on if block_given?
def nav_link(title, path, html_options = {}, options = {}, &block)
  LinkGenerator.new(request, title, path, html_options, options, (capture(&block) if block_given?)).to_html
end

I also had to modify my link method:

def link
  if @block.present?
    link_to(@path, html_options) do
      @block.concat(@title)
    end
  else
    link_to(@title, @path, html_options)
  end
end

I've updated my gist. You could probably hack it up to accept more complex blocks.

like image 30
stephenmurdoch Avatar answered Oct 16 '22 04:10

stephenmurdoch