June 2012 Archives

Better, Faster, Funner - Part 1

I've recently started working on a new web app project using all sorts of latest-and-greatest techniques and libraries, needless to say I choose Perl to build it out. The architecture of this application is different from what I'm used to but I like the direction I'm headed, everything seems to be in order, so I'd like to share my thoughts, struggles and accomplishments.

The WAF (web app framework) I choose to develop around this project is Dancer, although I've been away from Dancer and its community since before version 1 was officially released I decided to use it because, quite-frankly, it has nice and robust documentation, sensible defaults, and does exactly what I need it to with minimal code. You may have noticed that I neglected to mention the framework's plugin ecosystem, which IMHO is not preferred for large/growing projects.

The Hub

I've designed the application main module (root class) to be "the source of truth" for the entire application. This means that the root class is responsible for providing access to configuration options in an object-oriented fashion, allows switching between configuration profiles, writing to disk and providing absolute path information using Self::Dir. Some hackery and caching is needed to convince Dancer to switch configuration profiles during runtime.

The 2-Step (Not the Dance)

Like many other wishful thinkers I wanted the app to be completely self-contained and deploy-able with minimal fuss. After days of tinkering and rethinking botched deployments I came up with a simple 2-step system for deploying the app based on my typical work-flow.

I created two Perl scripts in the app root, setup-server and setup-application, both of which should be self-explanatory but allow me to elaborate. The requirements for the scripts are as follows:

The setup-server script should be executed first in the install process, it only requires that Perl 5.8+ be installed. I stringifies lists (arrays) of dependencies (system dependencies (build-essential, cpanm, etc), system cpan dependencies, local dependencies, system commands, etc) to be installed/executed in a particular order. This ensures that the server has the requirements need to run the next script, setup-application.

The setup-application script creates config files for various servers (web servers, databases, etc) and installs them in the assigned locations then starts/restarts the respective daemons. The magic CPAN library that makes creating, starting, stopping, and restarting services fun and stress-free is Ubic (especially when ran as root). Ubic is just pure awesome, it allows me easily add services to my application during a development iteration and have it just work when I re-deploy the app. I install each service to be executed via Ubic::Service::SimpleDaemon which has the added benefit of being automatically restarted on failure.

Stuck Between a Rock and a Hard Place

I've recently started evaluating libraries to provide form rendering, validation, general OO, and data storage.

Because I like using Validation::Class (which I authored) so much, for me the obvious choice was Validation::Class::Plugin::FormFields (which I also authored) though it has a sort of rigidness and inflexible to it, the type rigidness that can be found in all form rendering tools that rely on templating system like Template::Toolkit.

So I decided to use Validation::Class with ::Plugin::FormFields anyway and refactor it to make it better. I came to the conclusion some time ago that rendering forms in their entirety is too complex and specific to get right for all or even most use-cases, and an admirable compromise would be to provide an easy and intuitive interface to process and render form elements/fields individually.

So what does a better HTML form rendering engine look like? The following are code samples of the refactored V::C plugin I'm working on:

# $model is your V::C class instance

my $form = Validation::Class::FastForm->new(
    model => $model
);

# renders the login field as an HTML5 form input
# with data-minlength/maxlength, placeholder, etc
# and output the result

$form->render('login', 'text');
$form->render('login', 'email');
$form->render('login', 'date');
$form->render('login', 'number');

# alter you form field using css/xpath selectors
# and output the result

$form->process('login', 'text')
->change('input@type'  => 'password')
->change('input@title' => 'Please enter something here')
->data;

$form->dataset('user_type', [
    { text => 'Buyer',  value => 'buyer' },
    { text => 'Seller', value => 'seller'},
]);

$form->render('user_type', 'radio');
$form->render('user_type', 'checkbox');
$form->render('user_type', 'select');

$form->process('user_type', 'radio')
->change('select@multiple' => 'Yes')
->data;

These are working examples (currently) and hopefully give you a visual of the direction I'm heading in. If you feel the API for this form rendering engine is simple and easy-to-use, thanks, this simplicity was born out of frustration and providing this simplicity is bringing even more frustration.

I knew from the start I wanted to inline the templates and allow them to be extended and overridden per instance. I started out using HTML::Zoom which is nice but has some limitation that I wasn't willing to patch to move forward. Some of those limitations were failure to parse simple xpaths/css-selectors, failure to handle null and undef in a DWIM fashion, and constantly blowing-up with difficult to trace error messages.

I then refactored the code to use Template::Semantic which requires XML::LibXML which is not always the easiest to install via CPAN. Template::Semantic is great at handling null and undef in a DWIM fashion but lacks the chainable goodness once utilized in HTML::Zoom. Template::Semantic also doesn't die with roaming error messages. One thing that is extremely annoying is that it doesn't automatically append pushable attributes (like class, etc), and it doesn't create missing attribute nodes on-the-fly either. ARGGGHHH.

So I wrote Validation::Class::FastForm as a subclass of Template::Semantic which uses a role that provides a chainable API and formats selectors and vars so that they append pushable attributes and create non-existent attribute nodes. YAYYY.

The only problem now is every transformer is a coderef (like HTML::Zoom) which makes processing slower.

-Al

About Al Newkirk

user-pic ... proud Perl hacker, ask me anything!