Saturday 29 March 2014

The spring of HTML::FormFu

HTML::FormFu arrived on his first major release (1.00) this christmas. It's quite an event considering that previews release was on 05 Oct 2012.

I'm very happy for that because now form rendering is better and cleaner. Problem is that this new rendering had some incompatibility with my Strehler forms (javascript and CSS) so, when I upgraded, thing went a little messed up.

The most interesting thing (the only worth to be written here) is about my little CategoryUnique validator.

Strehler backend manage a category tree that you can use to organize contents. For example:

foo
foo/bar
foo/ber
yetanothercategory
newcategory
newcategory/newson

When you create a new category you have to give its name and its parent. The CategoryUnique validator check if the category already exists under that parent.

In the example up there you can create bar under newcategory, you can't under foo, because there's already one category named that way.

So the validator can't just check about the value of the category field, but it has also  to consider the second parameter of the form, to check if the category has the same name under the same parent.

With HTML::FormFu 0.09010 it was easy:

my $self = shift;
my $parent = $self->form->param_value('partent');

Easy, yes, but apparently forbidden by documentation.
If the value is invalid or the form was not submitted, it returns undef.
In a validator a value cannot be already valid :-)

But it worked because there was a bug, a bug fixed in the December 2012.

We could exploit it just because no new release of HTML::FormFu come after that fix... until December 2013.

What can we do now, with the shiny fixed 1.00 version of the software? The only way I find to do the same thing is to use the undocumented HTML::FormFu::FakeQuery. The query method of the form, infact, doesn't return an hash as documentation still say, but an instance of this object.

Luckly, using it is easy enough:

my $self = shift;

my $query = $self->form->query();
my $parent = $query->param('parent');


without passing through the param method of the form, that HTLM::FormFu documentation itself doesn't trust.



Friday 7 March 2014

The clock king

I wrote about timezones telling how avoid problem using them and, in particular, using timestamp fields. As I said in the end of the article the solution has a big flow: it depends on MySQL configuration.

This is not good because best practice is to make your code as system-indepentent as possible. It's good if you can install your sotware without checking MySQL timezone. Because MySQL timezone could be configured by someone who is not you, because you could never know the real MySQL timezone and because YOU'LL NOT CHECK MySQL timezone at installation time. You'll forget it. Don't try to fool me, we both know it will go this way.

So? Is there a better solution? Obviously yes.

Problem is that timestamp field is a field that carry in it timezone information calculating it using its envionment. This property bind it to MySQL timezone and gives us headaches. So, solution is throw timestamp field away and use datetime instead.

Datetime field has no timezone information (basically, it's not converted to UNIX Timestamp when saved on the DB table) so it's just a magic string with a day and an hour. It's less powerful so you can have better control on it. The only thing you have to keep in mind is to ensure that, every time your app write a datetime on the database the timezone it uses is always the same. UTC is usually a good choose.

So, when you insert a new line and you want to trace the moment of insert you no more relay on the MySQL auto-update, but you assign:

timestamp => DateTime->now()

DateTime->now() is already with UTC timezone.

Now you can display your timestamp using any timezone you like. The value you'll receive from database will be UTC so conversion is easy.
Just:

my $ts = $dbix_row->timestamp;
$ts->set_time_zone('UTC') #Remember? Value arrive from  database with floating timezone
$ts->set_time_zone('TIMEZONE_YOU_LIKE') #Starting from UTC the datetime will be converted