Consider the following test case:
class Foobar < ActiveRecord::Base
validates_inclusion_of :foo_id, :in => 1..100 # this is a DB column
attr_accessor :bar_id # this is not a DB column
attr_accessor :foobar # this is not a DB column
end
class FoobarController < ApplicationController
# standard CRUD stuff, e.g. using resource_controller
end
<% form_for(@object) do |f| %> <%= f.select :foo_id,
(1..102) %> <%= f.select :bar_id, (1..102) %> <%=
f.text_field :foobar %> <% end %>
The problem
- Select any value in the first form.
- Select 102 in the second form so validation fails.
- The controller should re-render the form using the previously
entered values.
- This works for foo_id and for :foobar, but not for bar_id if
bar_id is not a string.
The cause
In ActionView::Helpers::FormOptionsHelper#option_value_selected?
is decided whether a value is selected or not. This works for
strings and arrays but NOT for numeric values contained in
attr_accessor variables, because they are not type casted by
ActiveRecord::Base.
If I put a line
RAILS_DEFAULT_LOGGER("selected=#{selected.inspect}, value=#{value.inspect}")
in lib/action_view/helpers/form_options_helper:441 (in
option_value_selected?), for each option in the select field, it
will echo something like
selected="1", value=1
selected="1", value=2
selected="1", value=3
and of course, these values are never equal because you cannot
compare String and Integer.
Possible Solutions
I think it is safe to assume that since all values passed by a
HTTP request are strings at first, a string comparison can be made
by default. However adding a "value.to_s" to each comparison adds
quite a hefty performance penalty (Rails profiler says about 30%)
to every option line in a select field, so this is no
solution.
The other way round would be more practicable - casting the
"selected" property to a number if its #to_i method returns a
positive number.
Please share your thoughts. How is typecasting done for methods
representing AR::Base database columns? Is it possible to manually
specify a data type for attr_accessor variables? Currently I do
this manually in an after_initialize block in each model, which
works, but is a little non-DRY.
Thank you,
Jens