On Fri, May 9, 2008 at 2:49 PM, James Masters <ja
...@mastersgames.com> wrote:
> Therefore, I am wondering if it might be best if RHTMLO by default
> does not update a field where the form field is '' but the existing db
> record has it set to NULL.
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;