select_or_label with custom form builder
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)
else
select(method, choices, options)
end
end
end
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)
end
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")
end
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")
end
end
end
end