posts by tag

helpers (5)
Testing block helpers with Rspec

One of the many great things about Rspec is that it allows you to test helper methods. Helpers can be complex at times. And when it matters, they definitely need some testing love.

An example of a typical helper may be something like:

def h1(text)
  "<h1 class='heading'>#{text}</h1>"
end

Then you can do <%=h1 'Text Here' -%> in your views. That’s trivial to test, so I won’t go into that. The tricky tests (I think) deal with block helpers. Block helpers are extremely useful and can improve the readability of what’s going on in your view(s). Probably the most useful case is when you have <% if current_user.admin? %> scattered all over the place. Wouldn’t it be better (and DRYer) to wrap that “admin condition” up in one place?

def content_for_admin(&block)
  yield if current_user.admin?
end

Pretty simple. But, how would you test that? Initially, I had somewhat of a hard time writing tests to see if the block was actually yielded or not. Here’s what I do now, but I’m open to (and seeking more) alternatives. Using the content_for_admin helper as the guinea pig:

describe ApplicationHelper do
  attr_accessor :_erbout
  fixtures :users

  before(:each) do
    self._erbout = ''
    @block = "This is the block content"
    @current_user = users(:admin)
  end

  # current_user.admin? # => true
  it "should yield block for an admin" do
    should_receive(:current_user).and_return(@current_user)
    @current_user.should_receive(:admin?).and_return(true)

    html = content_for_admin do
      self._erbout << "<div>#{@block}</div>"
    end
    html.should have_tag('div', @block)
  end

  # current_user.admin? # => false
  it "should not yield block for a non-admin" do
    should_receive(:current_user).and_return(@current_user)
    @current_user.should_receive(:admin?).and_return(false)

    html = content_for_admin do
      self._erbout << "<div>#{@block}</div>"
    end
    html.should_not have_tag('div', @block)
  end
end

Another, similar, approach is to use eval_erb, which let’s you actually write out the ERB as a string (or heredoc) and evaluate the output. I think that’s a little messy, but then again, I’ve never actually tried it.

But other than that, this is the only way I know to test the yielded output of a block helper with Rspec. Do you know of a better way? Or at least, a different way?

Rails plugin: navigation helper

This plugin is now available at GitHub. Follow all plugin modifications there (the SVN repo will soon be obsolete).

It seems like I’ve been going on and on about navigation in Rails lately. That’s probably because I have been. But it’s one of those things that presents itself in every app I work on. And since I usually go about it close to the same way, it makes a good case for a plugin, don’t you think? I shall call it, navigation helper. Clever, I know.

It’s pretty basic, but I’ll go through a few examples, anyway. Before I continue, two notes about these examples:

  1. whenever you see navigation(...), think of <%= navigation(...) -%> as that method is a helper used in a view
  2. assume the current page will always be ‘Home’

Now that we have that settled, let’s see a few examples.

Basic Usage

This is how most people would probably use this plugin:

navigation [:home, :about, :contact_me]

# HTML output:
# <ul class="nav_bar">
#   <li class="current"><a href="/home">Home</a></li>
#   <li><a href="/about">About</a></li>
#   <li><a href="/contact_me">Contact Me</a></li>
# </ul>

You must use symbols as the links/sections, and we’ll see why in just a bit. Also, notice how :contact_me turns into ‘Contact Me’ as link text. You can use CSS to transform it to all lowercase or all uppercase, but the default is to capitalize each word (which you can’t do with CSS).

Oh, and the above example would be looking for the following named routes: home_path, about_path, and contact_me_path. So make sure you have a named route for each section you pass to the helper.

Authorized Sections

I don’t know about you, but a lot of the time I have one or two tabs that only appear based on some sort of authorization. For example, this very site has ‘Portfolio’, ‘Words’, ‘Archives’, and ‘About’ at the top. But if I’m logged in it also has ‘Admin’. Again, that’s a common need for me, so I added in support for that. Use it like so:

# Example 1: single section
navigation [:home, :about, :admin], :authorize => [:admin]

# Example 2: multiple sections
navigation [:home, :about, :users, :reports], :authorize => [:users, :reports]

The plugin will only add those sections to the list if they are authorized. And how does it “authorize”? Well, by default the plugin will check against a logged_in? method. If that method either doesn’t exist or fails (returns false), those tabs will not be added. “But Ryan, don’t you think it’s kind of wrong of you to assume that we’re using a logged_in? method?” Well, maybe, maybe not. Either way, you can override that by telling the helper which method to use, like so:

navigation [:home, :about, :admin], :authorize => [:admin], :with => :auth_method

Now auth_method will be checked instead of logged_in?.

One more thing. In the case that the entire navigation is to be authorized, there is a way to avoid re-typing all of those sections in :authorize option.

navigation [:home, :about, :admin], :authorize => [:all]

Adding Subtitles

This was a recent need of mine (in the Portfolio). What I mean by subtitles is text that supports each link, but is not part of the link itself. That may have not helped you at all. Maybe this example will:

navigation [:home, 'Start Here', :about, 'Learn More']

# HTML output:
# <ul class="nav_bar">
#   <li class="current">
#     <a href="/home">Home</a>
#     <span>Start Here</span>
#   </li>
#   <li>
#     <a href="/about">About</a>
#     <span>Learn More</span>
#   </li>
# </ul>

Then you can do some fancy CSS :hover styling or whatever. Sometimes that adds just enough spice to a boring navigation bar.

But wait, there’s a second option. If you want the subtitles to appear as hover text instead, just let the helper know:

navigation [:home, 'Start Here', :about, 'Learn More'], :hover_text => true

# HTML output:
# <ul class="nav_bar">
#   <li class="current"><a href="/home" title="Start Here">Home</a></li>
#   <li><a href="/about" title="Learn More">About</a></li>
# </ul>

And as I briefly mentioned above, that is why you have to pass symbols as the link/section and strings as the subtitles. The plugin understands symbols and strings differently (the positions matter, too: even for sections, odd for subtitles).

Setting Current Tab

OK, the last thing. By default the plugin uses the controller’s name to determine the “current” tab (or link or section) you’re on. But since that’s not always feasible, you can specify the current tab for any controller by doing:

class PublicController < ApplicationController
  current_tab :home
end

Now something like this will work as intended:

navigation [:home, :about, :contact_me]

The PublicController will be seen as :home to the navigation helper, and will choose the current tab accordingly.

Documentation

All (or most) of the above is in the README. It’s on the agile web development site, too (although it doesn’t provide much info).

Installation

If you think this plugin could help you out, by all means install it. Here’s how:

ruby script/plugin install http://svn.rpheath.com/code/plugins/navigation_helper

...or…

piston import http://svn.rpheath.com/code/plugins/navigation_helper vendor/plugins/navigation_helper 

Conclusion

I have a few more things I want to do with this plugin (such as sub-navigation), but I didn’t want to get unnecessarily complex just yet. Feel free to modify the code to your needs, and let me know if there’s something you absolutely think should be added/changed/fixed. Who knows, I just might do it! Enjoy.

Tabbed navigation in Rails (refactored)

This is in relation to an older post about tabbed navigation. No matter the implementation, I always seem to match controller names with tab/link/section names. But I’m realizing that’s simply too coupled.

These days, I name my controllers in the context of a resource. And now that I’m adopting namespaces for privileged access, it’s likely that I’ll have controllers having the same controller_name but different intentions, and I wouldn’t necessarily want a section highlighted in both cases. Besides, adding a controller should not force me to consider a tab, or navigation at all for that matter. It’s more important that it makes sense within the code base.

So I’ve updated my implementation.

# views/layouts/application.html.erb
navigation :home, :projects, :about, :contact, :admin

# helpers/application_helper.rb
def navigation(*links)
  items = ''
  links.each do |link| 
    css = (link == @current_tab ? 'current' : '')
    link != :admin ? items << tab(link, css) : (items << tab(link, css) if logged_in?)
  end
  content_tag :ul, items
end

The tab(link, css) helper just constructs the link. And to set the current tab:

# controllers/application.rb
class ApplicationController < ActionController::Base
  before_filter :set_current_tab
  protected
    def set_current_tab
      # will default to controller_name if @current_tab
      # has not been set by another controller
      @current_tab ||= controller.controller_name.to_sym
    end
end

# override the 'set_current_tab' method in any controller
# if a name other than the controller_name is desired
#
# For example:
# controllers/admin/authorized_controller.rb
class Admin::AuthorizedController < ApplicationController
  before_filter :authenticate
  protected
    def set_current_tab
      @current_tab = :admin
    end
end

By implementing the tabs this way I’ve banned the directory authentication and reverted back to an AuthorizedController, as that allows me to easily authenticate and set the admin tab for all administrative controllers.

Another solution would be to leave it as it were and override controller_name, but that didn’t feel right since it would’ve completely distorted the point of the controller_name in the first place. And so I didn’t do it.

It’s not like this is rocket science, I just thought I’d give an update.

Section content with Control.Tabs

Sometimes views can get unavoidably large (think eBay registration form). And sometimes it’s nice to group related content within a view. Well, in either case, here’s an option to do just that.

I found a prototype extension, Control.Tabs which basically gives you a menu to show certain pieces of markup. It suited my needs and here’s how I tied it into Rails.

I didn’t want to fiddle with javascript each time I needed this in a view, so I created a block helper to do the work for me1:

def tabbed_content(*links, &block)
  dom_id = links.last.is_a?(Hash) ? links.pop.values.last : 'tabbed-menu'

  list_items = links.inject([]) { |items, link| items << content_tag(:li, link_to(link.to_s.capitalize, "##{link.to_s.downcase}")) }
  tabs = content_tag(:ul, list_items, :id => dom_id)

  concat tabs, block.binding
  yield
  concat javascript_tag("new Control.Tabs('#{dom_id}');"), block.binding
end

Now when a view gets inherently too large (or needs grouped), I can make it scroll-friendly and simplify the code. As an example, here’s how I’d use it to section off someone’s online resume.

<% tabbed_content :education, :work, :references do -%>

  <div id='education'>
    <h2>Education Background</h2>
    <p>whatever you want</p>
  </div>

  <div id='work'>
    <h2>Work Experience</h2>
    <p>whatever you want</p>
  </div>

  <div id='references'>
    <h2>References</h2>
    <p>whatever you want</p>
  </div>

<% end -%>

This will place a set of links (capitalized versions of what was passed in) at the top of the content, defaulting to the first section. As expected, clicking a link will show only that section. And the javascript writes an “active” class to the appropriate link, which is nice for the CSS.

As a side note, there is a default DOM id being written to the <ul>, but it can be overridden if desired (maybe there’s more than one on a page?).

<% tabbed_content :education, :work, :references, :dom_id => 'tabbed-menu-2' do -%>

I know this is incredibly simple and basic, but someone might find it useful. And of course, if you do happen to use this, make sure you have the Control.Tabs javascript library included in your layout as needed (Chris has a tip for that).

1 Note: in Rails 2.0 extract_options! could be used instead of manually checking if the last item is a Hash, which is something I currently do a lot.

Refactored block helper

Sometimes when I post about something, I think more about what I’m doing, which in turn helps me to realize a potentially better solution. For instance, before I had i<2 ? true : false which is the same as i < 2—what was I thinking??? Anyway, the problem now is I don’t know if I’m taking this simple thing too far. The block helper I was using is:

def handicaps_and_pars(&block)
  titles = ["Men's Handicap", "Men's Par", "Ladies' Handicap", "Ladies' Par"]
  4.times do |i|
    yield titles[i], i % 2 == 0 ? 'handicap' : 'hole', i < 2 ? true : false
  end
end

I pulled out the parts that made my brain work a little harder into a separate method that returns the yielded data as an array:

def data_for_index(index)
  titles = ["Men's Handicap", "Men's Par", "Ladies' Handicap", "Ladies' Par"]
  [titles[index], index % 2 == 0 ? 'handicap' : 'hole', index < 2]
end

And from that I was between two options…

# option 1 - separate data into meaningful variables
def handicaps_and_pars(&block)
  4.times do |i|
    title, partial, is_mens = data_for_index(i)
    yield title, partial, is_mens
  end
end

# option 2 - yield the method itself
def handicaps_and_pars(&block)
  4.times { |i| yield data_for_index(i) }
end

I decided to go with option 2, as I think it’s the more concise of the two. But maybe I took this already short method too far? Sometimes I don’t know when to stop with this stuff, but it’s still fun to explore the options.

2008 by Ryan Heath | Get In Touch

flickr

DesolateInfinityLooking upDazedBlurred