Form Factory: A Better Way (I Think)

| | Comments (0) | TrackBacks (0)

Recently, I’ve started backing away from Jifty as my web framework of favor. There are a few reasons for this, but mostly it’s related to performance. Jifty is includes everything, Ajax, dispatch, ORM, Comet/PubSub, REST, forms, HTML, templating, and a pony (probably a kitchen sink too). Basically, with Jifty you don’t need to spend very much time working on building up your own utilities or framework upon framework to fit your style or needs. Jifty hands you a style as well, one that I mostly like.

However, the cost to performance on my Qublog project was too much. It was easier for me to port it to Catalyst sans-Ajax than it was to try and correct the performance issues. In addition, I no longer use Jifty for work, wanted to use features of DBIx::Class, Moose, etc. I’m not trying to bag on Jifty here, just explain why I’m moving away from it general. I very well may find a use for it for something in the future.

There is, however, a major feature of Jifty I miss after having moved to Catalyst. Jifty has a collection of classes for encapsulating form rendering and processing. At the center of this is the action class, which is really a glorified functor, but with all the accoutrement required to render an HTML form and to clean up, verify, and process submission. I like this. I like this a lot. I decided to use the flexibility afforded by Moose with an implementation of something like these action classes to create a new form handling system. Over my Thanksgiving vacation, I was able to build up what has become Form::Factory.

An Example

Okay, I thought about showing you the process I worked through to get here, but you probably want to see it in action first. I’ll work backwards from there.

In order to use Form::Factory, you must first define an action class. Here’s a simple login action you might use to present a login form to your users in a web application:

package MyApp::Action::Login;
use Form::Factory::Processor;

has_control login_name => (
    control => 'text',
    features => {
        required => 1,
        trim => 1,
        length => {
            minimum => 3,
            maximum => 20,
        },
        match_regex => {
            regex => qr{.@.}, # really naive check for email address-ish thing
            message => qw{your %s should be an email address},
        },
    },
);

has_control password => (
    control => 'password',
    features => {
        required => 1,
        trim => 1,
    },
);

has c => (
    is => 'ro',
    isa => 'MyApp',
    required => 1,
);

sub run {
    my $self = shift;

    if ($self->c->authenticate({ name => $self->login_name, password => $self->password })) {
        $self->result->success("welcome back,  @{[$self->login_name]}!");
    }
    else {
        $self->result->failure('the login name and password you entered is wrong');
    }
}

The has_control calls establish the inputs expected for our login action and tell the action what features those controls have. For example, the required features will result in a form that reports an error for that control when no value is given during a submission.

The run method here actually performs the action. (For this example, I’m using something Catalyst-ish, but this could just as easily be used with CGI, CGI::Application, Plack, Jifty, or whatever you prefer.)

To use the login action in our web application, we’ll now need to send the form to the user. Here’s a Template::Declare template that should work:

template 'user/login' => sub {
    my ($self, $c) = @_;

    page_wrapper {
        form { { action is 'user/check_login', method is 'POST' }
            my $interface = Form::Factory->new_interface(HTML => {
                renderer => sub { outs_raw(@_) },
            });
            my $action = $interface->new_action('MyApp::Action::Login', { c => $c });
            $action->unstash('login');
            $action->render;
            $action->clear_messages;
            $action->stash('login');
            $action->render_control(button => { name => 'login' });
        };
    };
};

That would render an HTML form that the user can then fill in and submit. The new_interface call creates an instance of an interface class. These are responsible for connecting an object to the user interface. In this case, it’s an HTML form.

The new_action call then creates an instance of the action class associated with our interface class. Note that we can pass a hashref of arguments that will be passed to the constructor.

The unstash and stash calls are handy for remembering certain things about the form. This includes any error or success messages associated with processing the form. For example, if the user entered their login name, but the incorrect password. The processing bit (which we’ll see in a moment) would stash the error from trying to authenticate and would also stash the login name itself (but not the password since passwords controls don’t ask to stash their value). Then, when the form draws again, teh error message will be shown and the login name box will come prefilled with the previous entry.

The clear_messages call clears the messages from the action so that we can stash a clean slate for the action (we don’t want to see a message we already displayed again).

The render method renders all the controls associated with the action. (By passing the controls option you can output only a subset if you wish.) The render_control is used to output a button that is not directly associated with the action (in this case we just use it to get the user to send the form back to us).

Once the user clicks on our “Login” button, we’ll then want to process the input. That looks something like this:

sub check_user_login :Path('user/check_login') :Args(0) {
    my ($self, $c) = @_;

    my $interface = Form::Factory->new_interface('HTML');
    my $action = $interface->new_action('MyApp::Action::Login', { c => $c });
    $action->unstash('login');

    $action->consume_and_clean_and_check_and_process(
        request => $c->request->params,
    );

    if ($action->is_valid and $action->is_success) {
        $c->response->redirect('/index');
    }
    else {
        $action->stash('login');
        $c->response->redirect('/user/login');
    }
}

We get the interface and action objects same as before. We unstash the saved information again. Then, we do that long method, which is a shortcut for saying:

$action->consume( request => $c->request->params );
$action->clean;
$action->check;
$action->process;

The consume method takes the input and applies that to each control object.

The clean method then cleans up the values given to each control. In this case, for example, the login_name will have whitespace trimmed from the beginning and end since it uses the trim feature.

The check method then validates the inputs to determine whether or not we can reasonably expect the user’s input to have a chance at working. Here’s where most of the features added above will do their work. If the user does not enter anything for the username, the required feature will cause the control to be set as invalid.

Then, the process method will cause the action to process. Actually, the process method itself first checks to see if the input is valid and does nothing if it is not. If it is valid, it will copy the values from the control objects into the action attributes and run the run method defined on the action.

Finally, we can check the success or failure of the action and react accordingly. That’s how Form::Factory works in a nutshell.

Goals

Now that you’ve seen an example, here’s what I want to achieve in a nutshell:

  1. Actions should be well-structured, but very simple to build.
  2. Actions are tied to the user interface by controls, which are easy to extend.
  3. Common features of actions should be simple to define and associate with actions.
  4. User interfaces should be easy to build for hosting actions.

Actions are Well-Structured, Simple

Actions are just functors, function objects. At the center of each is a run method responsible for taking some action. To get there, however, you need input and after you get there you need output. Form::Factory uses Moose attributes to specify what input the action takes. Each has_control builds an attribute onto the action, but also associates more information about how it interacts with the user.

The output is a little less formally specified at this point, but comes out through a result object. The result object is a list of messages associated with the action along with some status information. It can also return a hash of other information for more general output. However, I’m assuming that most of the output of the action is the side-effects the action performs.

When an action is run, it goes through four basic phases to process:

  1. Consume. The user input is delivered to each control associated with each attribute on the action.
  2. Clean. The input in each control is filtered and corrected, allowing whitespace to be removed, numbers to be reformatted, etc.
  3. Check. The input of each control is validated and any errors discovered are reported. Processing stops here if there are errors.
  4. Process. The input in each control is copied into the action attributes directly and the action is run.

In addition to the features attached to the controls, you can specify general subroutines which will run during these phases. For example, if you have a form for changing a password, you might want a check that looks like this:

has_checker check_that_passwords_match => sub {
    my $self = shift;
    my $new_password = $self->controls->{new_password}->current_value;
    my $confirm_password = $self->controls->{confirm_password}->current_value;

    if ($new_password ne $confirm_password) {
        $self->result->is_valid(0);
        $self->result->error('the New Password and Confirm Passwords do not match');
    }
};

This allows you action’s run method to focus on the task it performs and nicely segments off all the checks. You can also logically group your checks and filters so that they are separate from each other. This makes it easier to subclass your actions or compose them from roles.

Controls are the Bridge

An attribute added to your action with has_control has a control object associated with it. This control does most of the work until right before processing. The control receives the initial input, the cleaned input, and is where the value sits when checked. It is not until it is checked that the value enters the attribute instance slot.

Without the control in place, i.e., if we assigned to the attribute directly, Moose would object to the input before we have a chance to do anything. I want to avoid exceptions in this case as well because input validation failures are not exceptional, they’re expected. We need a way to easily send back notes the user about corrections needed and would like to avoid spewing call stacks unnecessarily.

Controls also suggest how the user interface object should present them to the user. An interface is not bound to use these suggestion in exactly the same way as its peers. For example, a text control in HTML might just be a text input box while in a CLI interface might be presented as a command-line option that takes an argument. A check box, on the other hand, will be a check box input in HTML, but would be an option that takes no arguments in a CLI (it’s presence suggesting “on” and it’s absence suggesting “off”).

Features Modify Actions

A common feature of form handling is filtering and input verification. In Jifty these were called canonicalization and validation. Horrible. I’ve called them cleaning and checking. In general, though, the cleaning and checking you want to do is extremely repetitive. On almost everything I want to trim the whitespace and verify that something that is required is present. I often want to check input length for something too long or too short. Sometimes I want to check that email addresses or phone numbers are sane, etc. In Form::Factory, all of these are called “features.”

A feature is a generic object that modifies an action. These can be anything from a feature that is just used to flag the role for some reason to features that actually modify the structure and processing of the form. Features can also be attached to particular controls to clean or check an individual control’s input.

Interfaces Talk to the User

And interfaces are probably what make Form::Factory most unique. An interface looks at the controls for an object and presents them somehow to the user. Currently, I’ve written an HTML interface and a CLI interface. The HTML interface presents them using the matching HTML form controls. The CLI interface presents them as a usage snippet describing the command-line options the action accepts. You can define a single action class and use that action to define a web form or a command-line interface.

I plan to add a REST interface as well, which will be a variation of the HTML interface. Other interfaces I’ve considered (but have no plans for at this time) could include things like a Wx GUI, a PDF interface that builds a PDF with form fields and then consumes FDF documents created from that, an XForms interface, a XUL interface, or a curses interface.

Okay, so that’s my current contribution to the forms parsing arena. I’m still working the kinks out, so be careful if you give Form::Factory a try. Things will change. Otherwise, I’d love some feedback and help ironing out some of the wrinkles.

Cheers.

0 TrackBacks

Listed below are links to blogs that reference this entry: Form Factory: A Better Way (I Think).

TrackBack URL for this entry: http://contentment.org/mt/mt-tb.cgi/668

Leave a comment

About this Entry

This page contains a single entry by Andrew Sterling Hanenkamp published on December 9, 2009 11:53 AM.

Bluga WebThumbs 1.0 for Drupal 6 was the previous entry in this blog.

Books are Better is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.