1 minute read

In our web app, we have a common UI pattern: replacing select lists (eg. drop downs) with a label if there is only one item in the list. For example, when creating a subscription, the user must choose a plan. Normally, there is a list of plans to choose from. However, if there is only one plan that the user can choose, we show a label specifying the one plan they’re getting instead of showing them a list of one.

We implemented this with a custom form builder and a method called select_or_label. It takes the same arguments as select, but only creates the select list if the list of choices has more than one element.

The view looks like:

<% form_for :subscription, :builder => SelectFormBuilder, :url => { :controller => :subscriptions, :action => :create } do |form| -%>
  <%= form.label :plan %>
  <%= form.select_or_label :plan_id, Plan.all.collect {|p| [p.name, p.id]} %>
<% end -%>

The form builder creates a span and a hidden_field if the size of the choices is 1:

class SelectFormBuilder < ActionView::Helpers::FormBuilder
  include ActionView::Helpers::TagHelper

  def select_or_label(method, choices, options = {})
    if choices.size == 1
      content_tag(:span, choices.first.first) +
        hidden_field(method, :value => choices.first.last)
      select(method, choices, options)

And the spec:

require File.dirname(__FILE__) + '/../spec_helper'

describe SelectFormBuilder, :type => :helper do
  describe "select_or_label" do
    before do
      helper = Class.new { include ActionView::Helpers }.new
      @builder = SelectFormBuilder.new(:subscription, Subscription.new, helper, {}, nil)

    it "returns a span and hidden field if the size of the choices array is only one" do
      html = @builder.select_or_label(:plan_id, [["name", "id"]])
      html.should_not have_tag("select")

      html.should have_tag("span", "name")
      html.should have_tag("input[type=hidden][name=?][value=?]", "subscription[plan_id]", "id")

    it "returns a select if the size of the choices array is greater than one" do
      html = @builder.select_or_label(:plan_id, [["name", "id"], ["other name", "other_id"]])
      html.should_not have_tag("input")

      html.should have_tag("select[name=?]", "subscription[plan_id]") do
        with_tag("option[value=?]", "id", "name")
        with_tag("option[value=?]", "other_id", "other name")