In UI for belongs_to relationships, I discussed a way to make a belongs_to UI. Unfortunately, it was very complex, and I found a better way since then.

I have to thank Michael C. Toren for the spark that inspired this entry.

The way I now do my belongs_to UIs today is much simpler. Let us assume the following models for discussion:

app/models/product.rb

1 class Product
2 belongs_to :color
3
4 validates_presence_of :color
5 end

app/models/color.rb

1 class Color
2 def self.all
3 self.find(:all, :order => name)
4 end
5 end

When creating a product, we would like to select the color of said product. The UI should be a simple SELECT box.

Rails makes it easy to have SELECT boxes coming from some table:

app/views/products/_form.rhtml

1 <p><label>Description:
2 <%= text_field ‘product’, ‘description’ ></p>
3 <p><label>Color:
4 <= collection_select ‘product’, ‘color_id’,
5 Color.all, :id, :name %>
</p>

See how I call #collection_select with color_id instead of color ? That’s the trick to use.

One nice side-effect of this is that validation will still run. The validates_presence_of declaration will be respected if no color_id is assigned.

The only problem left to resolve is the field highlighting that Rails field helpers automagically add when a field validation rule is not respected.

There are two ways to resolve that:

  1. Change the validation to require both color and color_id (adding two messages to the message area), or;
  2. Add the error DIV manually when an error is found on the counterpart field, such as this:




    app/views/products/_form.rhtml

    1 <p><label>Description:
    2 <%= text_field ‘product’, ‘description’ ></p>
    3 <p><label>Color:
    4 <div class="<= ‘fieldWithErrors’ if error_message_on ‘product’, ‘color’ >">
    5 <= collection_select ‘product’, ‘color_id’,
    6 Color.all, :id, :name %>
    </div></p>

UPDATE (2006-01-04): John Indra noted that there didn’t exist an error_messages_on, only error_message_on. Code above corrected.

Most of the Rails application I build these days have no use whatsoever for the autocomplete feature modern browsers.

FireFox and Internet Explorer have extended the input element to provide an autocomplete attribute.

This attribute may be set to on or off, indicating whether autocomplete should be enabled, on a field-per-field basis.

In Rails, that translates to:


1 <%= text_field :payment, :credit_card,
2 :autocomplete => ‘off’ %>


Obviously, if we have ten fields on the form, that would not be very DRY

Instead, you can do the following:

app/helpers/application_helper.rb

1 module ApplicationHelper
2 def text_field(args)
3 unless args.last.kind_of?(Hash) then
4 args << {}
5 end
6
7 args.last[:autocomplete] = off \
8 unless args.last[:autocomplete]
9 super(
args)
10 end
11 end

This is great when most of your fields need to have autocomplete disabled. But, while researching this topic, I stumbled on How to Turn Off Form Autocompletion
for Mozilla based browsers.

You do it this way:


1 <%= start_form_tag({}, {:autocomplete => ’off’}) >
2
3 <= end_form_tag %>


The same trick as above for overriding text_field_tag can be used to override form_tag.

Anyone has a good idea of how to do that ? I already know about AssociationHelper, but I want a real drop down.

The solution I have come up with currently is to manually create a select using collection_select, like so:

html

1 <p><label for="event_provider">Provider</label><br/>
2 <%= collection_select ‘event’, ‘provider’,
3 Provider.find_all(nil, ‘name’), ‘id’, ‘name’ %>
4

The problem is when the post is coming back. My update method is horrible:


1 # Event belongs_to Provider
2 def update
3 event</span> = <span class="co">Event</span>.find(params[<span class="s"><span class="dl">'</span><span class="k">id</span><span class="dl">'</span></span>]) <span class="no"> 4</span> <span class="no"> <strong>5</strong></span> provider_id = params[<span class="s"><span class="dl">'</span><span class="k">event</span><span class="dl">'</span></span>][<span class="s"><span class="dl">'</span><span class="k">provider</span><span class="dl">'</span></span>] <span class="no"> 6</span> params[<span class="s"><span class="dl">'</span><span class="k">event</span><span class="dl">'</span></span>].delete(<span class="s"><span class="dl">'</span><span class="k">provider</span><span class="dl">'</span></span>) <span class="no"> 7</span> provider = <span class="co">Provider</span>.find(provider_id) <span class="no"> 8</span> event.provider = provider <span class="no"> 9</span> <span class="no"><strong>10</strong></span> <span class="r">if</span> <span class="iv">event.update_attributes(params</span>[<span class="s"><span class="dl">'</span><span class="k">event</span><span class="dl">'</span></span>]) <span class="r">then</span> <span class="no">11</span> flash[<span class="sy">:notice</span>] = <span class="s"><span class="dl">'</span><span class="k">Event updated</span><span class="dl">'</span></span> <span class="no">12</span> redirect_to <span class="sy">:action</span> =&gt; <span class="s"><span class="dl">'</span><span class="k">show</span><span class="dl">'</span></span>, <span class="sy">:id</span> =&gt; <span class="iv">event.id
13 else
14 render edit
15 end
16 end

You see how I have to find the Provider, and then delete that item from the params hash ? This is the part I would like to make easier. I know about attr_accessible, and it’s converse attr_protected, and I am wondering if that is what I should use.

Search

Your Host

A picture of me

I am François Beausoleil, a Ruby on Rails and Scala developer. During the day, I work on Seevibes, a platform to measure social interactions related to TV shows. At night, I am interested many things. Read my biography.

Top Tags

Books I read and recommend

Links

Projects I work on

Projects I worked on