Start with some model that has a boolean field that can be nil/null.
Create a view with a form that uses a select box to set the field's
value, and set :include_blank => true. The obvious assumption is that
if you select blank (as opposed to Yes/1/true or No/0/false), the
field value will be saved as nil/null. Not so - it is saved as 0/
false. This has the weird behavior of the select box showing the
false choice when you select blank and get bounced back for a second
try at the form.
create_table :widgets do |t|
t.string :name
t.boolean :fancy
end
w = Widget.create(:name => "weather", :fancy => true)
w.fancy # => true
w.update_attributes(:fancy => nil)
w.fancy # => nil
w.update_attributes(:fancy => "")
w.fancy # => false
It's not too much trouble to patch ActiveRecord to change it so that
setting a boolean field to a blank string gets saved as NULL in the
database instead of 0. The question is: what is the desired
behavior? More specifically, when should the value be coerced, with
special consideration to the case when the field doesn't allow a nil/
NULL value?
I think the answer is that blank values should always be coerced to
nil in memory, even when the field is created with :null => false.
This is consistent with how integer fields are handled. You can have
a nil value in memory, and the db will barf when you try and save it
as NULL. AR expects you to use a validation to catch that and give
the user another shot at the form. However... there is what looks to
me like a bug in AR where assigning a blank-but-not-empty string to an
integer field has a different result from assigning an empty string or
nil. An empty string is coerced to nil, but a string of one or more
whitespace characters is coerced to 0. It's also easy to fix it so
that all blank strings are coerced to 0, and I think that would be the
best thing to do. But I wonder if anyone out there might be relying
on this behavior. There are only 2 tests that break when this change
is made (in validations_test.rb).
By the way, strings representing integers are coerced to their correct
values, but non-numeric objects like arrays and hashes are coerced to
1. WTF? Why is that useful?
I was going to create a ticket with a patch for this stuff to refer
to, but the code is sitting on a computer at work so that won't happen
until the morning (sorry, Pratik).
To summarize:
I propose that all blank strings should be coerced to nil, for both
boolean and integer fields. Any issues with that? Anyone know if
they are relying on that behavior?
--
Josh Susser
http://blog.hasmanythrough.com
I agree. This follows the guideline of "validate early", which is
generally a good one.
Also, it makes sense to handle it early if you were to use another
persistence layer. This is a UI-layer problem, solve it close to the
UI.
-- Chad
Ian
--
Argument from Design--We build web applications
Western House 239 Western Road Sheffield S10 1LE
Mob: +44 (0)797 4678409 | Office: +44 (0)114 2667712
<http://www.ardes.com/> | <http://blog.ardes.com/ian>
I think the problem with that approach stems from the fact that form
data is submitted as untyped strings. There's no way to look at the
string "1,100" and guess if that means the string itself, the integer
1100, or the list [1, 100]. Currently ActiveRecord does the best it
can to convert string data from forms into appropriate values for
fields, and sometimes it falls over (bug or flawed design?).
I see three paths we can take to improve things:
1) incrementally improve ActiveRecord to more sensibly process string
inputs and convert to the correct data type for fields, i.e. blank
string handling
2) significantly alter ActiveRecord for more flexible and targeted
processing of string inputs
3) create some kind of middle-man object to assist in converting form
input strings to correct data types
Path 1 seems like a good approach in the short term, and there seems
to be little reason not to fix obvious errors in how ActiveRecord
operates. Even if we do something else, it doesn't seem like a good
idea to remove this functionality from AR, since that would break
virtually *every* Rails app in existence.
Path 2 could be interesting as a generic approach. I've done exactly
this in specific situations often before - e.g. I fake up a tags_list
accessor on the model to allow user input of a list of tags like
"rails, ruby, sighting". You can of course do this without any
special support, but perhaps a bit of syntactic sugar could improve
things.
Path 3 sounds great in theory. It's like a presenter that runs in
reverse too. But I wonder if separating the processing of form input
data into a separate object is going to be worth the effort. I'd be
interested in seeing someone's proposal for what that might look like
(unfortunately I have a few other science experiments higher in my own
priority queue right now).
In the mean time, I propose Path 1 as the simplest thing that could
possibly work to fix the use case where submitting forms with blank
values gives a non-nil value in the field.
> -1 for getting ActiveRecord to bail out ActionController with coercion
> of empty strings and blanks.
>
> As for the coercion of non-numerics objects, I agree that "1" seems
> totally outrageous. I would hope for the coercion to use to_i/to_f
> conventions and raise an exception when they fail.
That's just what it does (schema_definitions.rb:65):
when :integer then value.to_i rescue value ? 1 : 0
I'm still puzzling over that one, especially since `value` will never
be nil (thanks to a test a few lines above).
Quick thought: I don't think calling #to_i is guessing. I see it as "I
expect an integer, please provide me with whatever you can to satisfy
that". I think most Ruby APIs are like that. So I always call #to_s or
#to_i in methods that expect such types.
Just like AR will call #to_i on #find when passed a string. It's ok,
and it lets you do some more magic and makes your code look a lot
better.
Just my $0.2
Agreed :-)
I'd definitely love to see some work done on tidying up the conversion
code and making it more consistent and testable. In the meantime the
patch is a nice pragmatic solution until someone has the time and the
inclination to spend time doing that rework.
If you're that person, drop us a line :)
--
Cheers
Koz