Data::FormValidator で複数フィールドのチェック

たとえば,

〒 [   ] - [    ]

みたいな郵便番号入力フィールドがあった場合,Data::FormValidator でチェックするには,

my $dfvr = Data::FormValidator->check($form, {
    required => [qw/ postal1 postal2 /],
    constraint_methods => {
        postal1 => sub {
            my ($dfvr, $value) = @_;
            return ($value =~ m/^\d{3}$/o);
        },
        postal2 => sub {
            my ($dfvr, $value) = @_;
            return ($value =~ m/^\d{4}$/o);
        },
    },
});

として,TT で,

〒
<input type="text" name="postal1" value="[% form.postal1 %]" />
 -
<input type="text" name="postal2" value="[% form.postal2 %]" />
<br />
[% IF dfvr.missing('postal1') || dfvr.missing('postal2') -%]
郵便番号を入力してください。
[% ELSIF dfvr.invalid('postal1') || dfvr.invalid('postal2') -%]
郵便番号を正しく入力してください。
[% END -%]

とやったりします。でもテンプレート側で条件文が複雑になるのは嫌だなぁと思ったのです(そもそもテンプレート側で条件判定してるのがだめだろって指摘はご容赦を)。

required の代わりに(本来の用途からはずれますが)require_some を使うと,

    required => [qw/ postal1 postal2 /],
    require_some => {
        postal => [ 2, qw/ postal1 postal2 / ],
    },

postal1, postal2 いずれか存在しない場合,$dfvr->missing('postal') が真になりますので,

[% IF dfvr.missing('postal1') || dfvr.missing('postal2') -%]
[% IF dfvr.missing('postal') -%]

のようにすっきりかけます。でも,constraint_methods で「postal」と指定できないんですね(制約チェックの対象にならない)。

その後あれこれつたないテクを駆使して invalid についても実現できました。「なかなかバッドノウハウ的〜」と悦に入っていたんですが,冷静に見直してみると,結局,

$form->{postal} = $form->{postal1} . $form->{postal2};

my $dfvr = Data::FormValidator->check($form, {
    required => [qw/ postal /],
    constraint_methods => {
        postal => sub {
            my ($dfv, $value) = @_;
            return ($value =~ m/^\d{3}\d{4}$/o);
        },
    },
});

のようにプリプロセスするのが一番スマートであることに気が付きました。そう,フォームの入力をそのまま利用しようとこだわっていたのが敗因だったんですね。

このアプローチの欠点は,filters メソッドが適用されないというところです。ですが (入力内容を再利用することを考えると)フィルタくらい自力でかけてもいいかなと思いました。

どうせ postal1, postal2 いずれも required なので,postal1 の constraint_method で両者を判定するという案もありますが,上記のアプローチだと,

$form->{postal} = $form->{postal1} . '-' . $form->{postal2};

my $dfvr = Data::FormValidator->check($form, {
    required => [qw/ postal /],
    constraint_methods => {
        postal => sub {
            my ($dfv, $value) = @_;
            return 0  unless $value =~ m/^-?(\d{3})-?(\d{4})-?$/o;
            $form->{postal1} = $1;
            $form->{postal2} = $2;
            return 1;
        },
    },
});

のようなことができます。何をやっているかというと,postal1 に7桁郵便番号が入力された場合,自動的に分割しているのです。