• このエントリーをはてなブックマークに追加

はじめに

パラメータのバリデーションに Data::Validator を利用している今日この頃.

Amon2 アプリケーションにおいて,アクションのロジック内で必要になるたびに, use Data::Validator とか Data::Validator->new(...) とかするのがとてもメンドイので,Amon2プラグインとして呼び出せるようにしています.

以下,「こんな感じ」の紹介と,Data::Validator を拡張してみるお話もあったりします.

Amon2 プラグイン「DataValidator」

Amon2::Plugin::DataValidator としてCPANモジュール化し,extlib 下に入れて使っています.

プラグインをロードすると...:

package MyApp;
use parent qw/Amon2/;
...
__PACKAGE__->load_plugin('DataValidator');
...

コンテキストオブジェクトで new_validator というメソッドを呼び出せるようになります.パラメータも Data::Validator コンストラクタと同様です.戻ってくるのは Data::Validator オブジェクトなので,あとはいっしょです:

# in controller
package MyApp::C::FooBar;
sub index {
    my ($class, $c) = @_;
    ...
    my $v = $c->new_validator(
        foo => { isa => 'Int', default => 1 },
        bar => { isa => 'Str', optional => 1 },
        ...
    );
    my $q = $v->validate( $c->req->parameters->mixed );
    ...
}

プラグインのソースコードはこんな感じ:

package Amon2::Plugin::DataValidator;
use strict;
use warnings;
use Data::Validator;
 
our $VERSION = '0.01';
 
sub init {
    my ($class, $context_class, $config) = @_;
    no strict 'refs';
    *{"$context_class\::new_validator"} = \&_new_validator;
}
 
sub _new_validator {
    my ($self, %params) = @_;
    return Data::Validator->new(%params);
}
 
1;

Amon2::Plugin::DBI あたりをまるまる参考にさせていただいてます><

バリデーションルールを拡張したい

全角文字を含むパラメータを半角に揃えてからバリデーションに通したい,バリデーションルールにそんな処理をするための「フィルタ」を書けたらいいな,という衝動に駆られ,そのような処理部分を追加した Data::Validator のサブクラスを返すようプラグインを修正してみました.

# in controller
package MyApp::C::FooBar;
sub index {
    my ($class, $c) = @_;
    ...
    my $v = $c->new_validator(
        foo => {
            isa => 'Int',
            filter => sub {
                (my $v = shift) =~ tr/0-9/0-9/;
                return $v;
            },
        },
        ...
    );
    my $q = $v->validate( $c->req->parameters->mixed );
    ...
}

このサンプルでは,foo というパラメータに全角数字が含まれている場合,それを半角に直した上で,バリデーションを行います.

プラグインのソースコードは次のようになりました.Data::Validator::Filterable という Data::Validator のサブクラスを定義し,これを返すような作りになっています:

use strict;
use warnings;
 
package Data::Validator::Filterable;
use Mouse;
extends 'Data::Validator';
 
has filter_map => (
    is  => 'ro',
    isa => 'HashRef',
);
 
no Mouse;
 
sub BUILDARGS {
    my ($class, @mapping) = @_;
    my $args = {};
    my %filter_map;
    my @mapping_4_super;
    while ( my ($name, $rule) = splice @mapping, 0, 2 ) {
        if ( ref($rule) eq 'HASH'  &&  ref($rule->{filter}) eq 'CODE' ) {
            $filter_map{$name} = $rule->{filter};
            delete $rule->{filter};
        }
        push @mapping_4_super, $name, $rule;
    }
    $args = $class->SUPER::BUILDARGS(@mapping_4_super);
    $args->{filter_map} = \%filter_map;
    return $args;
}
 
### override
sub validate {
    my $self = shift;
    my $args = $self->initialize(@_);  # isa Hashref
    my $fm = $self->filter_map;
    for my $k ( keys %$args ) {
        my $f = $fm->{$k};
        next  unless $f && ref($f) eq 'CODE';
        $args->{$k} = $f->($args->{$k});
    }
    return $self->SUPER::validate($args);
}
 
1;
 
package Amon2::Plugin::DataValidator;
 
our $VERSION = '0.02';
 
sub init {
    my ($class, $context_class, $config) = @_;
    no strict 'refs';
    *{"$context_class\::new_validator"} = \&_new_validator;
}
 
sub _new_validator {
    my ($self, %params) = @_;
    return Data::Validator::Filterable->new(%params);
}
 
1;

Moose とか Mouse とか不勉強で,どうやってクラスを継承するか,とかそのあたりから調べるハメになりましたが,なんとかこぎつけられた感じですw

とりあえず GitHub

テストとかまだアレですが.

おわりに

以上,Amon2 アプリケーション内でバリデータオブジェクトを少しでもラクに呼び出すために,Data::Validator な Amon2 プラグインをこさえてみたりちょこっと拡張してみたりしたお話でした.