Dave Howorth
2005-02-02 15:43:38 UTC
I thought I'd let anybody who's interested know how far I've got.
Basically, it runs!
I used Maypole code from Jesse Sheidlower, and from Cees Hek and Perrin
Harkins for the error handling. Thanks to them all.
I've patched most of it into Maypole::Model::CDBI for now. I have this
at the top:
use Data::FormValidator;
Maypole::Config->mk_accessors qw(dfv_profiles);
Then here's my do_edit action:
sub do_edit : Exported {
my ( $self, $r ) = @_;
my $profile = $r->config->dfv_profiles->{$r->model_class}
|| $self->dfv_profile;
my $params = $r->params;
my $results = Data::FormValidator->check($params, $profile);
# could warn about unknown fields here if debugging
if ($results->has_invalid or $results->has_missing)
{
my $dfv_msgs = $results->msgs({
format => '%s',
invalid_seperator => "\cZ",
# Yes, you have to misspell separator!!
});
my $errors; # Template variable for error hash
foreach my $missing_field ($results->missing) {
$errors->{$missing_field}->{missing} = 'Missing';
}
my $invalid = $results->invalid;
foreach my $invalid_field (keys %$invalid) {
my @msg_text = split "\cZ", $dfv_msgs->{$invalid_field};
foreach my $failed_constraint
(@{$invalid->{$invalid_field}}) {
$errors->{$invalid_field}->{$failed_constraint} =
shift @msg_text ;
}
}
# Set up to redisplay the completed form with the errors
$r->template_args->{cgi_params} = $params;
$r->template_args->{errors} = $errors;
$r->template('edit');
return;
}
# If we got this far, the results are OK
#
my $valid_params = $results->valid;
my ($obj) = @{ $r->objects || [] }; # get first object
if ($obj) { # editing an existing object
$obj->set(%$valid_params);
$obj->update;
}
else { # creating a new object
$obj = $self->create($valid_params);
}
$r->template('view');
$r->objects( $obj ? [$obj] : []);
}
The point of all the messing with errors is to allow all the error text
to be kept in the templates and not in the code, whilst still allowing
plausible defaults. Here's an extract from addnew.tt to give a flavour.
This is not a finished template, it just shows both possibilities.
coldata.singular is my replacement for colnames.$col.
[%- IF errors.$col %]
<span class="error">
[%- FOREACH err = errors.$col ;
IF err.key == 'missing' -%]
Please provide a value for [% coldata.singular %]<br />
[%- ELSE ;
err.key ; ' ' ; err.value ; '<br />' ;
END ;
END -%]
</span>
[%- END ;
I've played with two methods of setting profiles. I'm still undecided
about their merits so the code supports both. Either the profiles can be
stored in the config like this:
# Data::FormValidator configuration
use Regexp::Common qw/RE_num_int RE_URI_HTTP/;
my $printable = qr/^[\040-\377\r\n\t]+$/;
# surely there must be a d-fv 'printable' constraint!!?
my $profiles = BeerDB->config->dfv_profiles({
'BeerDB::Pub' => {
required => [qw/name/],
optional => [qw/notes url/],
constraints => {
name => $printable,
notes => $printable,
url => [
{ name => 'format', constraint => 'RE_URI_HTTP' },
]
},
},
'BeerDB::Style' => {
required => [qw/name/],
optional => [qw/notes/],
constraints => {
name => $printable,
notes => $printable,
},
},
... etc ...
});
My other method of loading profiles is via a method in each model
sublass. This has the merit that everything is in the model, which some
people see as a 'good thing'. For example:
package QD1::Coord; # for coords table
sub dfv_profile {
return {
required => [qw/name bio_database_id/],
missing_optional_valid => 1,
constraints => {
id => 'RE_num_int',
name => qr/^[\040-\377\r\n\t]+$/,
bio_database_id => 'RE_num_int',
}
}
}
I've only tested D-FV a very little but it seems to work so far. Its
power is definitely in the profiles. But so is the complexity and I'm
still at the bottom of the learning curve. I haven't worked out how to
make them most convenient and I haven't yet found any kind of dictionary
or cookbook of practical constraints and programming idioms to achieve
particular results. But it's early days.
Cheers, Dave
Basically, it runs!
I used Maypole code from Jesse Sheidlower, and from Cees Hek and Perrin
Harkins for the error handling. Thanks to them all.
I've patched most of it into Maypole::Model::CDBI for now. I have this
at the top:
use Data::FormValidator;
Maypole::Config->mk_accessors qw(dfv_profiles);
Then here's my do_edit action:
sub do_edit : Exported {
my ( $self, $r ) = @_;
my $profile = $r->config->dfv_profiles->{$r->model_class}
|| $self->dfv_profile;
my $params = $r->params;
my $results = Data::FormValidator->check($params, $profile);
# could warn about unknown fields here if debugging
if ($results->has_invalid or $results->has_missing)
{
my $dfv_msgs = $results->msgs({
format => '%s',
invalid_seperator => "\cZ",
# Yes, you have to misspell separator!!
});
my $errors; # Template variable for error hash
foreach my $missing_field ($results->missing) {
$errors->{$missing_field}->{missing} = 'Missing';
}
my $invalid = $results->invalid;
foreach my $invalid_field (keys %$invalid) {
my @msg_text = split "\cZ", $dfv_msgs->{$invalid_field};
foreach my $failed_constraint
(@{$invalid->{$invalid_field}}) {
$errors->{$invalid_field}->{$failed_constraint} =
shift @msg_text ;
}
}
# Set up to redisplay the completed form with the errors
$r->template_args->{cgi_params} = $params;
$r->template_args->{errors} = $errors;
$r->template('edit');
return;
}
# If we got this far, the results are OK
#
my $valid_params = $results->valid;
my ($obj) = @{ $r->objects || [] }; # get first object
if ($obj) { # editing an existing object
$obj->set(%$valid_params);
$obj->update;
}
else { # creating a new object
$obj = $self->create($valid_params);
}
$r->template('view');
$r->objects( $obj ? [$obj] : []);
}
The point of all the messing with errors is to allow all the error text
to be kept in the templates and not in the code, whilst still allowing
plausible defaults. Here's an extract from addnew.tt to give a flavour.
This is not a finished template, it just shows both possibilities.
coldata.singular is my replacement for colnames.$col.
[%- IF errors.$col %]
<span class="error">
[%- FOREACH err = errors.$col ;
IF err.key == 'missing' -%]
Please provide a value for [% coldata.singular %]<br />
[%- ELSE ;
err.key ; ' ' ; err.value ; '<br />' ;
END ;
END -%]
</span>
[%- END ;
I've played with two methods of setting profiles. I'm still undecided
about their merits so the code supports both. Either the profiles can be
stored in the config like this:
# Data::FormValidator configuration
use Regexp::Common qw/RE_num_int RE_URI_HTTP/;
my $printable = qr/^[\040-\377\r\n\t]+$/;
# surely there must be a d-fv 'printable' constraint!!?
my $profiles = BeerDB->config->dfv_profiles({
'BeerDB::Pub' => {
required => [qw/name/],
optional => [qw/notes url/],
constraints => {
name => $printable,
notes => $printable,
url => [
{ name => 'format', constraint => 'RE_URI_HTTP' },
]
},
},
'BeerDB::Style' => {
required => [qw/name/],
optional => [qw/notes/],
constraints => {
name => $printable,
notes => $printable,
},
},
... etc ...
});
My other method of loading profiles is via a method in each model
sublass. This has the merit that everything is in the model, which some
people see as a 'good thing'. For example:
package QD1::Coord; # for coords table
sub dfv_profile {
return {
required => [qw/name bio_database_id/],
missing_optional_valid => 1,
constraints => {
id => 'RE_num_int',
name => qr/^[\040-\377\r\n\t]+$/,
bio_database_id => 'RE_num_int',
}
}
}
I've only tested D-FV a very little but it seems to work so far. Its
power is definitely in the profiles. But so is the complexity and I'm
still at the bottom of the learning curve. I haven't worked out how to
make them most convenient and I haven't yet found any kind of dictionary
or cookbook of practical constraints and programming idioms to achieve
particular results. But it's early days.
Cheers, Dave
--
Dave Howorth
MRC Centre for Protein Engineering
Hills Road, Cambridge, CB2 2QH
01223 252960
Dave Howorth
MRC Centre for Protein Engineering
Hills Road, Cambridge, CB2 2QH
01223 252960