/ domm

I hack Perl for fun and profit.

Follow me on twitter!
Atom Icom ... on Atom!
<<<<<<<<<<
24.11.2015: Vienna.pm TechMeet
04.09.2015: Things I learned at YAPC::Europe 2015 in Granada
01.08.2015: Announcing Measure::Everything
26.07.2015: Announcing InfluxDB::LineProtocol
02.07.2015: How to notify 1/14th of your users every fortnight
14.06.2015: Make a playlist quickly
31.05.2015: Porting a very simple script to Perl6
10.05.2015: Things I learned at German Perl Workshop 2015
14.01.2015: Picture-of-the-day helper script
06.01.2015: Metaprogramming HTML::FormHandler

For a project I'm currently working on I need to allow users to define a set of statistical data they want to collect (and later analyze). The project already gathered data for the last ~15 years, so I also have to migrate that old data from the rather crappy and hardcoded form it is currently in into something more flexible and manageable. While gathering the data (or in fact also while migrating it), each data point can be validated in one way or another. Most data points are just integers, but some are more complex, and some even depend on one another. For example, the sum of all participants shall be the same as the sum of all male and female participants. Some fields also have some arbitrary constraints. In the old code, all of those constraints where hardcoded and could not be changed by the users.

HTML::FormHandler for fun and profit

For the rewrite (and in fact for most of my projects), I use HTML::FormHandler for handling various forms. As HFH already has a lot of validation code and also provides nice ways to add custom validations to your forms, I decided to use it for all my validation needs. This makes even more sense, as I will need to provide a HTML-based form for each of the data sets anyway.

In my database I have a table named field which contains the definition of each field, i.e. type, internal_name, title, is_required etc. There is also a JSON-column named formhandler. As I'm using PostgreSQL 9.3, the DB makes sure that this field only contains valid JSON (and provides nice helper functions).

field.type directly maps to HTML::FormHandler::Field class names, while field.formhandler contains a JSON representation of the data structure needed to set up a field, eg:

{
   "label" : "Anzahl MitgliedsverbÃĪnde auf Landesebene",
   "type" : "Integer",
   "size" : 7,
   "name" : "k71",
}
{
   "label" : "PLZ",
   "name" : "k31",
   "type" : "Text"
   "maxlength" : 10,
}
{
   "label" : "Kursbetrieb",
   "options" : [
      {
         "label" : "ganztag",
         "value" : "ganztag"
      },
      {
         "label" : "vormittag",
         "value" : "vormittag"
      }
   ],
   "multiple" : 1,
   "name" : "kursbetrieb",
   "type" : "Select",
   "widget" : "CheckboxGroup"
}

As you can see, a lot of validation is already handled by FormHandler (Integer, valid options of a CheckboxGroup, ..)

But some validation is more complex, eg the one for PLZ (zip code). In the old code, the fields where defined in a (very long!) "config file" (actually Perl hash), and the extra validation routines sort of embedded into the data structure (it's a very big Perl hash containing a lot of coderefs...). I moved them into the database in a table called field_validator, linked to the field and containing columns like name, type and args. args again is a JSON field, while type maps to class name implementing the actual validation.

PLZ has one such validation named "4 Zahlen" ("4 digits"), implemented as a regex. For now I decided to keep all of the old validations as they were, so I added a field_validator with type MustMatch and args of {"regex":"\\d{4}"}.

But how could I add those validations to my form? In "classic" HTML::FormHandler, you write a form class and define your fields and validations inside the class, in proper, materialized code. As my forms are user-defined, I cannot handcraft a form class. And while you can create an impromptu form using

my $form = HTML::FormHandler->new( fields => \@list_of_field_definitions )

I did not find a straight forward way to add custom validation code to such forms.

Going Meta

So I reached for Class::MOP::Class to generate a proper dynamic class and add some methods to it.

my @validators;  # list of per-field custom validator data structures
my $class_name;  # the name of the data set
my $metaclass = Class::MOP::Class->create(
    'KB::Stats::UniverseHandler::'.$class_name => (
        superclasses => [ 'HTML::FormHandler' ],
        methods => {
            validate => sub {
                my $form = shift;
                _do_validation($form, \@validators);
            },
        }
    )
);

my $hfh = $metaclass->new_object(field_list=>\@fields);

Using Class::MOP::Class I create a new dynamic class (it could have been anonymous, but giving the class a proper name helps tracking down errors). Using superclasses I declare my new class a subclass of HTML::FormHandler. And using methods I can install methods into the new class, for now only the validate method. As I don't want to define the whole body of the method in the call to create, I just forward to the local function _do_validation (which behaves like a method). The list of validators is available via a closure and passed on to _do_validation.

sub _do_validation {
    my ($self, $validators ) = @_;

    foreach my $v (@$validators) {
        my $field = $self->field($v->{field});
        my $val = $field->value;
        next unless $val;

        # TODO implement a flexible system using validation classes

        if ($v->{type} eq 'MustMatch') {
            my $regex = $v->{args}{regex};
            if ($val !~ /$regex/) {
                $field->add_error($v->{name});
            }
        }
    }
}

_do_validation is a rather simple method that goes through all the validators, fetched the field associated with the validator and then does the actual validation. In the end I want to put the validator into proper classes, but for now I only have two different validators and it a) was faster to just do a quick if-elsif-elsif chain which is b) easy to refactor into proper classes later (as soon as I know that this design actually works).

Well, it does seem to work, so after now sharing this bits of code, my next task will be to refactor the validators into proper classes and implement more of them.

If there are easier ways to get dynamic and custom validations implemented in HTML::FormHandler, I'd love to hear about them!

Comments (via disqus)

>>>>>>>>>>