Tag: perl



2017年 6月 30

Mojolicious::Validator::Validation – Perform validations – metacpan.org

Mojolicious純正のフォームバリデーションを使う上で気を付ける点を書き留めておく。Mojolicious::Plugin::TagHelpersとも相性良いし(当たり前)、活用すべき。

my $validation = $c->validation;
$validation->required('name')->like(qr/hoge/);
$validation->optional('name2', 'trim')->like(qr/hoge2/);

バリデーション

required()又はoptional()から始める

$validation->required('name')又は$validation->optional('name')から始める。メソッドチェーンでもいいし、都度バリデーションメソッドを呼んでも良い。

  • requidedかoptionalを呼んだ時点でバリデーション対象のfieldが切り替わる。
  • 現在バリデーション対象になっているfieldは$validation->topicで取得できる。

filterはrequired()又はoptional()の第2引数以降で指定する

※ Mojolicious v6.41以降

$validation->required( 'name', 'filter1', 'filter2', ... );
$validation->optional( 'name', 'filter1', 'filter2', ... );

DB等へ保存する値は$validation->outputの値を使う

$c->req->paramsの値はリクエスト時の値なのでDB等に値を保存する場合には使用しない。

  • 特にfilterを使う場合は注意が必要。
  • filter適用後の値は$validation->outputに収納されている。
my $hash_ref = $validation->output;
my $value = $hash_ref->{name};

my $scalar = $validation->param('name');
my $array_ref = $validation->every_param('name');

フィルター

フィルインフォーム

タグヘルパーを使うとfill-in formしてくれるけど、値にフィルターを適用する場合は注意が必要。

  • バリデーション実行時に$c->param()でリクエストパラメータの値の書き換えが必要。
    • タグヘルパーの値補完にはreq->paramの値が使われる
バリデーションも同時に行っている場合

バリデーションエラーがあるとvalidation->param()がundefを返すのでバリデーションエラーがない時だけ書き換える…としなくてはならない。

my $validated_email_value = $c->validation->param('email');
$c->param( email => $validated_email_value ) if $validated_email_value;

独自のバリデーションを追加する

$validation->validator->add_check( name => sub {} )

独自のフィルターを追加する

$validation->validator->add_filter( name => sub {} )

Filed under: Mojolicious,perl

Trackback Uri






2017年 3月 6

今更ながらDBIx::Class(DBIC)に入門。使い始めてDBICの作法に翻弄されてる。

timestamp的なカラム、例えばupdated_atカラムを自動更新させるのにハマったのでメモ。

package Jobeet::Schema::ResultBase;
use strict;
use warnings;
use parent 'DBIx::Class::Core';

__PACKAGE__->load_components(qw/InflateColumn::DateTime/);

sub insert {
    my $self = shift;

    my $now = Jobeet::Schema->now;
    $self->created_at( $now ) if $self->can('created_at');
    $self->updated_at( $now ) if $self->can('updated_at');

    $self->next::method(@_);
}

sub update {
    my $self = shift;

    if ($self->can('updated_at')) {
        $self->updated_at( Jobeet::Schema->now );
    }

    $self->next::method(@_);
}

1;

上記のようなResultクラスのベースを継承してResultクラスを作っていくのだけど、ちょっとした問題に当たる。

先程のエントリーの例で Jobeet::Schema::Result::Job のデータを更新するには下記のようになる。

# その1
$schema->resultset('Job')->find($id)->update({ is_public => 1 });

# その2
$schema->resultset('Job')->search({ id => $id })->update({ is_public => 1 });

いずれのコードもis_publicカラムを1に更新する。そして、updated_atカラムが現在の時刻に自動更新される…と思うのだけどそうならない場合がある。

更新されるのはfind()を使っているその1の場合だけ。その2の場合は更新されない。

この理由と対処方法は下記エントリーに書かれている。

find()search()は帰ってくるオブジェクトが違う = 使われるupdateメソッドは別のものって事。

DBICなら用意されていそうだと思って探したら DBIx::Class::TimeStamp っていうのを見つけたけど、こちらもfind()のようにrowオブジェクト取得してからupdateしないと効かないようだ…。

search()->update()を使う理由

単純に発行されるSQLが少ないから。

今回やりたいことは更新対象の行のprimary keyが分かってて、かつ、その行のデータの取得は必要がない場面を想定している。

発行されるSQLを見ながらいろいろ試してみて、$schema->search({...})->update({...}) の場合はSELECT文が発行されず、UPDATE文のみが発行されるようだったので。

通常はいきなりUPDATEを行うことは少ないかもしれない。

解決策

解決策としてはfind()で更新するって方法とResultクラスではなくResultsetクラスのベースになるものにしてあげるって方法があると思う。

find()で解決した場合の問題点

find()を使う場合は必ずSELECTが発行される。先程の例で言うと発行されるSQLのイメージは下記のような感じ。

SELECT * FROM job WHERE id = xxx;
UPDATE job SET is_public = 1, updated_at = xxx WHERE id = xxx;

必要がないSELECT文が実行されるのは避けたい。

ResultSetクラスでの解決

Resultクラスの共有部分を作るように、ResultSetクラスの共有で解決できそうだと思って試したら出来た。

03日目: データモデル の例でのサンプルコード

schemaクラス

package Jobeet::Schema;
use strict;
use warnings;
use parent 'DBIx::Class::Schema';
use DateTime;

__PACKAGE__->load_namespaces(
    default_resultset_class => '+Jobeet::Schema::ResultSetBase'
    # or
    # default_resultset_class => 'ResultSetBase'
);

my $TZ = DateTime::TimeZone->new(name => 'Asia/Tokyo');
sub TZ    {$TZ}
sub now   {DateTime->now(time_zone => shift->TZ)}
sub today {shift->now->truncate(to => 'day')}

1;

load_namespaces の引数 default_resultset_class でデフォルトの ResulutSet クラスを指定。

ResultSetクラス

package Jobeet::Schema::ResultSetBase;
use strict;
use warnings;
use parent 'DBIx::Class::ResultSet';

# ResultBaseクラスのinsertで対応可能なので必要ない
# sub create {
#     my $self = shift;
# 
#     my $table = $self->result_source;
#     my $now   = Jobeet::Schema->now;
#     $_[0]->{created_at} = $now if $table->has_column('created_at');
#     $_[0]->{updated_at} = $now if $table->has_column('updated_at');
# 
#     $self->next::method(@_);
# }

sub update {
    my $self = shift;

    if ( $self->result_source->has_column('updated_at') ) {
        $_[0]->{updated_at} = Jobeet::Schema->now;
    }

    $self->next::method(@_);
}

1;

Resultクラス

package Jobeet::Schema::ResultBase;
use strict;
use warnings;
use parent 'DBIx::Class::Core';

__PACKAGE__->load_components(qw/InflateColumn::DateTime/);

sub insert {
    my $self = shift;

    my $now = Jobeet::Schema->now;
    $self->created_at( $now ) if $self->can('created_at');
    $self->updated_at( $now ) if $self->can('updated_at');

    $self->next::method(@_);
}

sub update {
    my $self = shift;

    if ($self->can('updated_at')) {
        $self->updated_at( Jobeet::Schema->now );
    }

    $self->next::method(@_);
}

1;

このResultクラスを継承して各テーブルのResultクラスを作る。エントリーの例で言うと下記コードのような感じ。

package Jobeet::Schema::Result::Job;
use strict;
use warnings;
use parent 'Jobeet::Schema::ResultBase';

# ここにテーブル定義

1;

もうちょっとスマートな方法があったら後で加筆する。


Filed under: perl

Trackback Uri






2016年 12月 7

PerlのWebApplicationFrameworkであるMojoliciousでは様々なフックが用意されているのだけれど、actionの前後だけに実行したい場合は独自にフックポイントを追加しないといけない。

実装方法その1

一番単純な実装方法はaround_actionフックを使うのが良さそう。actionは$lastフラグが真の時に実行されるので前後に独自のフックをエミットする。

sub startup {
    my $self = shift;
    ...

    $self->hook(
        around_action => sub {
            my ( $next, $c, $action, $last ) = @_;
            return $next->() unless $last;

            # before_actionフックを実行
            $c->app->plugins->emit_hook( before_action => $c );

            # Controller::Actionを実行
            $c->$action();

            # after_actionフックを実行
            $c->app->plugins->emit_hook( after_action => $c );
        }
    );
}

このフックを使えば動的ページのみでフックを実行できる。session関連の操作とかに便利。

実装方法その2

上記のコードの場合は全てのactionに適用されてしまうので、フックを実行したくないcontrollerを作りたい場合はMojolicious::Controllerを継承したclassを作る事で実現できる。

# App
sub startup {
    my $self = shift;
    ...

    $self->hook(
        around_action => sub {
            my ( $next, $c, $action, $last ) = @_;
            return $next->() unless $last;

            $c->process($action);
        }
    );
}
# フックを使用するbase class
package MyApp::Controller;
use Mojo::Base 'Mojolicious::Controller';

sub process {
    my ( $c, $action ) = @_;
    $c->before();
    $c->action();
    $c->after();
}

sub before {
    my $c = shift;
    $c->app->plugins->emit_hook( before_action => $c );
}

sub after {
    my $c = shift;
    $c->app->plugins->emit_hook( after_action => $c );
}

1;
# フックを使用しない base class
package MyApp::NoHookController;
use Mojo::Base 'Mojolicious::Controller';

sub process {
    my ( $c, $action ) = @_;
    $c->action();
}

1;
# フックを使うcontroller
package MyApp::Controller::Fizz;
use Mojo::Base 'MyApp::Controller';

...


# フックを使わないcontroller
package MyApp::Controller::Buzz;
use Mojo::Base 'MyApp::NoHookController';

...

before/after_actionフックを使う場合はMyApp::Controllerを継承したcontrollerを作り、使わない場合はMyApp::NoHookControllerを継承したcontrollerにする。


Filed under: Mojolicious,perl

Trackback Uri






2016年 8月 25

Mojolicious::Plugin::LocaleTextDomainOO – I18N(GNU getext) for Mojolicious. – metacpan.org

きっかけ

Mojolicousで国際化するプラグインにはMojolicous::Plugin::I18Nというものがある。これは良く出来ていて、URLやドメイン名での振り分けが出来たりする。

国際化の部分はLocale::Maketextを使う。Locale::Maketext::Lexiconも使えばpoファイルなどもつかえる。

普通に使う分なら問題ないのだけど、GNU gettextのmsgctxttext domainのようなことは出来ない。

CPANをいろいろ漁ってみて、gettextコンバーチブルなモジュールでLocale::TextDomain::OOってのが自分の要件に合いそうなのでこちらをMojoliciousで使えるようにしてみた。

手順

Perl Moduleの作成にはMinillaを使った。MojoliciousプラグインをMinillaで開発する手順は別エントリーに書いた。

Minillaを使ってMojoliciousプラグインを開発する

PAUSEアカウントを取得

注意点は各エントリー等に書いてある通り。一番悩むのはA short description of why you would like a PAUSE ID:というPAUSEの申請理由でした。

長くかかる場合もあるみたいだけど、自分の場合はその日のうちに返信来ていました。

Minilla release

MinillaでCPANにアップロードする方法はCPAN に姉を公開した話とかを参考に。

モジュール開発で一番大変なのはドキュメントの作成。恐らく通じないであろう英語で書かなくてはならないのツラい。ま、コード載せておけば半分くらいつたわる、はず。

あと、cpanfile.snapshotの扱いをどうすればよいかが分からない。他のモジュール見て回ると一緒にパッケージングされていないので.gitignoreに書いておいたけど、この方法で良いか不明。

失敗談

Mojolicous::Plugin::I18N の機能はそのまま使いたかったのでパッケージに含めてアップロードしたらネームスペースの権限に引っかかってしまった。おれおれモジュールの場合には問題ない事(?)だったので注意したい。

おそらくはそれさえも平凡な日々: CPANで意図しない名前空間の取得を防ぐために

懸念点

Locale::TextDomain::OOは、Mooベースだし、結構依存が多い。Cartonでパッケージ管理しているなら特に問題ないのかな。

雑感

  • とにかくモジュール開発にはMinilla(やDist::Milla)が大変便利。
  • CPANに公開するとなるとそれなりにテストやドキュメントを整備しなくてはいけないので大変な反面、とても勉強になる。
  • いろいろ試行錯誤したけど、それも勉強になる。

と、どれをとっても勉強になるので是非挑戦して頂きたい。


Filed under: perl,プログラミング

Trackback Uri






2016年 8月 16

1. Mojoliciousプラグインの雛形作成

$ mojo generate plugin MyPlugin

カレントディレクトリに Mojolicious-Plugin-MyPlugin というディレクトリ名で雛形作成される。

2. ドキュメントにAuthor情報を追記する。

$ cd ./Mojolicious-Plugin-MyPlugin
$ vi lib/MyPlugin.pm

minillaではPODに書かれているAuthorが使われる。作成された雛形(MyPlugin.pm)のPODにはAuthorの項目がないので追記する。

vi lib/Mojolicious/Plugin/MyPlugin.pm

...

=head1 AUTHOR

Lastname Firstname <name@domain.com>

=head1 SEE ALSO

....

3. Minilizeする

$ minil migrate

minillaで必要なファイルが作成される。

3.5 Makefileを削除

Makefileは必要ないファイルなので削除する。

$ rm -f ./Makefile

※このファイルがあるとCPANにアップロードした時に

no_generated_files
Remove the offending files/directories!

とおこられるみたい。

4. commitする

$ git add -A
$ git commit -m'initial commit'

まずは全てをコミットしておく。開発準備完了。

5. バリバリ開発する

あとはもりもり開発をすすめる


Filed under: Mojolicious,perl

Trackback Uri