[TIP] Making an intelligent form fields


#1

After searching arround I found a quite smart solution to me for binding
form fields to model in an active way. I use aggregations with single
value objects such as

class Percentage
attr_accessor :value

def initialize(value)
@value = (value.is_a? String) ? value.tr(’ %’, ‘’).to_f : value
end

def to_s
return unless value
sprintf(’%.2f %%’, value)
end
end

This is an object that takes either float or string (with possible %
character in it) and viewed as string looks like “1.23 %”. So I can
easily create an intelligent “shadow” attribute for interaction:

 composed_of :i_discount_rate, :class_name => 'Percentage',
             :mapping => %w(discount_rate value)

Using i_discount_rate in form it makes value of discount_rate shown in
text field pretty with % sign. But we cannot pass it to the model
because in mass-assignment everything is just string from request.

So I used reflection on aggregation and as attributes are about to be
processed, they are “objectized”, so attributes[:i_discount_rate] will
gain a real Percentage object:

def attributes=(attributes)
  attributes.each do |param, value|
    aggregation = self.class.reflect_on_aggregation(param.to_sym)
    if aggregation
      attributes[param] = aggregation.klass.new(value)
    end
  end

  super(attributes)
end

Now an user can enter “2%”, “2.0 %”, "2.1 % ", etc. and everything is
acceptable. We can now create value object for money so field “54,20
EUR” is perfectly acceptable. And there can be objects for entering date
like “+3 days” and so on.

The mechanizm for “i_attributes” can be enhanced similiary as Date is
expanded into form fields. So for example we can have aggregation

composed_of :i_amount, :class_name => 'Amount',
            :map => [%w(a_count count), %w(a_units units)]

And then helper used like

amount_field 'order_item', 'i_amount'

which renders to one text field and one select box such as:

<input type="text" name="order_item[i_amount(count)]"/>
<select name="order_item[i_amount(units)]">
  <option value="l">liters</option>
  <option value="t">tons</option>
  <option value="pc">pieces</option>
</select>

That’s it.


Kamil


#2

Great! Just what I was looking for.

Thanks,
Karel