ActiveModelLike::ErrorsというPerlモジュールを書いた
通常Model内のエラーをControllerに渡す方法はWAFに組み込まれている。が、薄いWAFだとControllerとViewだけあって、Modelは好きに実装して下さいということが多い。得にPerlは軽量なWAFが多く、Mojoliciousのように薄くはないけどModel層がないものもある。ちょっとしたものならメソッドの戻り値で返してもいいと思うが、それが増えていくとController側でメソッド毎の戻り値のパターンを意識しないといけなくなるので、すぐに辛くなる。で、どうするかというと、大体Model内で$self->{errors}->{}のようなハッシュやエラー用のクラスを作って、そこへエラーを格納してControllerに戻すというケースが多いと思う。その際、エラーの有無を確認するメソッドや、メッセージを表示するメソッドが必要になるが、それがアプリケーション毎に違うとそれもそれで辛い。
ということを考えていたときに、ふと「Railsと同じI/Fだったら、(個人的に使う分には)わかりやすくていいかも」とActiveModel::Errorsのソースを読んでみると、基本的な機能は簡単に移植できそうだったので興味本位でPerlに移植してみた。
https://github.com/nkwhr/ActiveModelLike-Errors
ActiveModel::Errorsの4.2.xをベースにしている。[]や[]=は同等のことを実現する方法がなさそうだったので(あったら教えてください)省いたのと、疑問符付きのメソッド名は一部変更している。また、本家のAM::ErrorsはAM::ValidationsやAM::Translationsと連携することで、SymbolからHuman Readableなメッセージに変換したりI18n対応を行っているが、その辺まで移植しようとするとキリがないので、割り切ってエラーを格納して返すだけのものにした。なのでActiveModelLike。(言い訳)
対応表
| AcitveModel::Errors | ActiveModelLike::Errors |
|---|---|
| [ ], [ ]= | N/A |
| add | add |
| add_on_blank | N/A |
| add_on_empty | N/A |
| added? | is_added |
| as_json | N/A |
| blank? | is_blank |
| clear | clear |
| count | count |
| delete | delete |
| each | each |
| empty? | is_empty |
| full_message | full_message |
| full_messages | full_messages |
| full_messages_for | full_messages_for |
| generate_message | N/A |
| get | get |
| has_key? | has_key |
| include? | include |
| key? | N/A |
| keys | keys |
| new | new |
| set | set |
| size | size |
| to_a | to_array |
| to_hash | to_hash |
| to_xml | N/A |
| values | values |
使い方
各メソッドはREADMEかActiveModel::Errorsのドキュメントを見てもらった方が早いので省略。
Mojoliciousの場合、以下のようにModelに組み込めばRailsっぽく使える。
package MyApp::Model::User; use Mojo::Base -base; use ActiveModelLike::Errors; has 'errors' => sub { ActiveModelLike::Errors->new; }; has 'name'; has 'age'; sub is_valid { my $self = shift; $self->errors->add(name => 'is invalid') if $self->name ne 'nkwhr'; $self->errors->add(age => 'is invalid') if $self->age != 32; return $self->errors->is_empty; } 1;
package MyApp::Controller::Example; use Mojo::Base 'Mojolicious::Controller'; use MyApp::Model::User; sub foo { my $self = shift; my $user = MyApp::Model::User->new({ name => $self->param('user'), age => $self->param('age'), }); unless ($user->is_valid) { my $error_messages = join(', ', @{$user->errors->full_messages}); $self->flash(error => $error_messages); return $self->redirect_to('index'); } $self->render('foo'); } 1;