This is an interesting problem - but I think that the correct solution
should let the user to set the field to "" if he chooses to.
Conflating "" to NULL is as wrong as conflating NULL to "". At the
server side we can have a convention that undef means NULL - so the
question is what the browsers send.
--
Zbigniew Lukasiak
http://brudnopis.blogspot.com/
This all depends on what you pass to params(). Example:
% perl -MRose::HTML::Form -de 0
main::(-e:1): 0
DB<1> $f = Rose::HTML::Form->new;
DB<2> $f->add_fields(t => { type => 'text' });
DB<3> $f->params({ t => undef });
DB<4> $f->init_fields;
DB<5> x $f->field_value('t');
0 undef
DB<6> $f->params({ t => '' });
DB<7> $f->init_fields;
DB<8> x $f->field_value('t');
0 ''
DB<9> $f->params({});
DB<10> $f->init_fields;
DB<11> x $f->field_value('t');
0 undef
So if you really want '' to be coerced to NULL, then change all ''
param values to undef before passing the params hash to the form's
params() method.
-John
The method in Rose::HTML::Form is called object_from_form(), and as
such it is generic to the point of being unrelated to databases
entirely. So it's certainly not going to gain any features based on
what to do in the case when a db record has a NULL :)
What I think you want instead is a db_object_form_form() and
init_with_db_object() pair of methods that understands and handles
Rose::DB::Object-derived objects.
I'm planning to add such methods to RHTMLO eventually. I've been
testing something similar in my own code, which I've posted before
(when the list was on SF.net) and I'll post it again here (see below).
It does some funky stuff with a.b.c nested forms and fields
corresponding to $a->b->c foreign keys and relationships in RDBO
objects, which may or may not be to your liking. But it does have the
'' to undef coercion, plus some custom boolean handling. Feel free to
modify it to suite your needs.
-John
---
package My::HTML::Form;
use strict;
use base 'Rose::HTML::Form';
use Carp;
use Scalar::Util();
use Rose::DB::Object::Util;
use Rose::HTML::Form::Constants qw(FF_SEPARATOR);
use EVA::HTML::Errors qw(FORM_SET_FIELD_ERROR);
# Variables for use in regexes
our $FF_SEPARATOR_RE = quotemeta FF_SEPARATOR;
our $FF_SEPARATOR = FF_SEPARATOR;
sub db_object_from_form
{
my($self) = shift;
my($class, $object);
if(@_ == 1)
{
$class = shift;
if(ref $class)
{
$object = $class;
$class = ref $object;
}
}
elsif(@_)
{
my %args = @_;
$class = $args{'class'};
$object = $args{'object'};
}
else
{
croak "Missing required object class argument";
}
$object ||= $class->new();
my $parent_object = $object;
unless($object->isa('Rose::DB::Object'))
{
croak "$object is not a Rose::DB::Object-derived object";
}
my $meta = $object->meta;
foreach my $field ($self->fields)
{
my $name = $field->name;
next unless($self->param_exists_for_field($name));
$object = $parent_object;
my $partial_name = '';
if($name =~ /$FF_SEPARATOR_RE/o)
{
my $nibble = $name;
my $obj = $object;
while($nibble =~ s/^([^$FF_SEPARATOR]+)(?:$FF_SEPARATOR_RE)//o)
{
my $related = $1;
last unless($obj->can($related));
if(Rose::DB::Object::Util::has_loaded_related($obj, $related))
{
$obj = $obj->$related()
}
else
{
my $new_obj;
eval
{
$new_obj = $obj->$related();
unless($new_obj)
{
if(my $fk = $obj->meta->foreign_key($related))
{
$new_obj = $fk->class->new;
}
elsif(my $rel = $obj->meta->relationship($related))
{
my $class = $rel->can('foreign_class') ?
$rel->foreign_class : $rel->class;
$new_obj = $class->new;
}
$obj->$related($new_obj);
}
};
if($@ || !$new_obj)
{
# Restore failed segment
$nibble = "$related$FF_SEPARATOR$nibble";
last;
}
$obj = $new_obj;
}
}
if($nibble =~ /$FF_SEPARATOR_RE/o)
{
$name = $field->local_name;
}
else
{
$name = $nibble;
$object = $obj;
}
}
else
{
$name = $field->local_name;
}
if($object->can($name))
{
# Checkboxes setting boolean columns
if($field->isa('Rose::HTML::Form::Field::Checkbox') &&
$meta->column($name) && $meta->column($name)->type eq 'boolean')
{
#$Debug && warn "$object->$name(", $field->is_on, ")\n";
$object->$name($field->is_on);
}
else # everything else
{
my $value = $field->internal_value;
$value = undef unless(length $value);
#$Debug && warn "$object->$name($value)\n";
eval { $object->$name($value) };
if($@)
{
# this looks like an error that should not be user-visible
# $self->error("Could not set $object->$name($value) - $@");
$self->error_id(FORM_SET_FIELD_ERROR);
warn "Could not set $object->$name($value) - $@";
return undef;
}
}
}
}
return $parent_object;
}
sub init_with_db_object
{
my($self, $object) = @_;
croak "Missing required object argument" unless($object);
$self->clear();
my $selected_object;
foreach my $field (sort { $a->name cmp $b->name } $self->fields)
{
my $name = $field->name;
$selected_object = $object;
if($name =~ /$FF_SEPARATOR_RE/o)
{
my $nibble = $name;
my $tmp_obj = $selected_object;
while($nibble =~ s/^([^$FF_SEPARATOR]+)$FF_SEPARATOR_RE//o)
{
my $related = $1;
last unless($tmp_obj->can($related));
if(Rose::DB::Object::Util::has_loaded_related($tmp_obj, $related))
{
$tmp_obj = $tmp_obj->$related()
}
else
{
my $new_obj;
eval { $new_obj = $tmp_obj->$related() };
if($@ || !$new_obj)
{
# Restore failed segment
$nibble = "$related$FF_SEPARATOR$nibble";
last;
}
$tmp_obj = $new_obj;
}
}
if($nibble =~ /$FF_SEPARATOR_RE/o)
{
$name = $field->local_name;
}
else
{
$name = $nibble;
$selected_object = $tmp_obj;
}
}
else
{
$name = $field->local_name;
}
if($selected_object->can($name))
{
#$Debug && warn field($name) = $selected_object->$name = ",
$selected_object->$name(), "\n";
$field->input_value(scalar $selected_object->$name());
}
}
}
1;