yujiro's blog

エンジニアリング全般の事書きます

Mojolicious でアプリケーションをつくってみた ②

bamboo-yujiro.hatenablog.com

の続きです。

モデル

アソシエーション

lib/MojoSample/Schema/Result/ 以下にファイルを作成し、そこに記述します。

たとえば、memos の場合、アソシエーションは

belongs_to => user 
has_many => memo_tags
many_to_many => tags 

という感じです。その場合は以下のように記述します。

__PACKAGE__->belongs_to(
    user => 'MojoSample::Schema::Result::User',
    'user_id'
);

__PACKAGE__->has_many(
    memo_tags => 'MojoSample::Schema::Result::MemoTag',
    'memo_id'
);

__PACKAGE__->many_to_many(
    tags => 'memo_tags' => 'tag'
);

キーになってる user, memo_tags, tags はその名前でモデルからアクセスできるようになります。

例えば、

my $memo = $c->db->resultset('Memo')->search({id => $c->param('id')})->first();

print $memo->user->username;

という感じ。

値はアソシエーションするテーブルのResultクラス。

第二引数は外部キー。

many_to_many の場合のみ違うので注意が必要です。

ロジック

このResultクラスにメソッドを定義して、そのオブジェクトから呼び出すことが可能です。 例えば、以下はmemo に紐付いた複数のタグをカンマ区切りに出力する例ですが、

sub tag_str {
  my $self = shift;
  my $tag_str = '';
  foreach $tag ($self->tags) {
    $tag_str .= $tag->name . ',';
  }
  chop($tag_str);
  return $tag_str;
}

と定義して、

my $memo = $c->db->resultset('Memo')->search({id => $c->param('id')})->first();

print $memo->tag_str;

という感じで呼びだすことができます。

バリデーション

ここは一番苦労しました。 最初は Result クラスに

__PACKAGE__->validation(
  module => 'FormValidator::Simple',
  profile => [
    title => ['NOT_BLANK'],
    content => ['NOT_BLANK'],
  ],
  filter => 0,
  auto => 1,
);

みたいに書いていたんだけど、 入力された値がユニークかどうかチェックするやつがデフォルトになかったので、

FormValidator::Simple::Plugin::DBIC::Unique

というプラグインをつかうことにしました。

http://search.cpan.org/~lyokato/FormValidator-Simple-Plugin-DBIC-Unique-0.05/lib/FormValidator/Simple/Plugin/DBIC/Unique.pm

上記にある例にならって、

['DBIC_UNIQUE', 'MojoSample::Schema::Result::User', 'username'] 

としたんだけど全く動かない。 以下のようにresult set のオブジェクトを渡したら動きました。

['DBIC_UNIQUE', $schema->resultset('User'), 'username'] 

ただ、result set のオブジェクトを渡す場合、Resultクラスには書けないぽい。

$schema が作られるのが Resultクラスの読み込みより後っぽいので。

なので lib/Mojosample.pm に書くことにしました。

バリデーションエラーのメッセージもlib/Mojosample.pmに書いています。

以下の感じです。

# Validation
FormValidator::Simple->set_message_decode_from('utf-8');
FormValidator::Simple->set_messages({
  users => {
    username => {
      NOT_BLANK => 'ユーザー名は必須です。',
      LENGTH => 'ユーザー名は4文字以上16文字以内で入力してください。',
      DBIC_UNIQUE => '指定されたユーザー名は既に使用されています。'
    },
    password => {
      NOT_BLANK => 'パスワードは必須です。',
      LENGTH => 'パスワードは8文字以上16文字以内で入力してください。'
    }
  },
  memos => {
    title => {
      NOT_BLANK => 'タイトルは必須です。',
    },
    content => {
      NOT_BLANK => '内容は必須です。',
    }
  },
});

コントローラ

lib/MojoSample/Controller/ 以下に定義します。

sub index {
  my $c = shift;
  my $memos = $c->db->resultset('Memo')->list($c->session->{login_user_id})->page(($c->param('page') || 1));
  my $pager = $memos->pager();
  $c->render(memos => $memos, pager => $pager);
}

View への値受け渡しは

 $c->render(memos => $memos, pager => $pager);

または

 $c->stash(memos => $memos, pager => $pager);

とします。

セッションを用いたフラッシュメッセージは

$c->flash(message => 'メモを追加しました'); 

というようにします。

この辺の、mojolicious 単体については

https://github.com/yuki-kimoto/mojolicious-guides-japanese/wiki

に詳しくかいてあります。僕が紹介するまでもないって感じです。^^;

ルーティング

まず、コード載せます。

# Router
my $r = $self->routes;

# Normal route to controller
my $filter = $r->under->to('Base#before_filter');
$filter->get('/')->to('Top#index');
$filter->get('/users/login')->to('Users#login');
$filter->post('/users/login')->to('Users#login');
$filter->get('/users/new')->to('Users#new_');  # new は使えなかった
$filter->post('/users/create')->to('Users#create');

# ログインが必要になるページ
my $login_required = $filter->under->to('Users#logined_check');
$login_required->get('/memos')->to('Memos#index');
$login_required->get('/memos/new')->to('Memos#new_'); # new は使えなかった
$login_required->get('/memos/:id/show')->to('Memos#show');
$login_required->post('/memos/create')->to('Memos#create');
$login_required->post('/memos/:id/destroy')->to('Memos#destroy');
$login_required->get('/memos/:id/edit')->to('Memos#edit');
$login_required->post('/memos/:id/update')->to('Memos#update');
$login_required->get('/users/logout')->to('Users#logout');

ログインが必要になる箇所とそうでない箇所で分けて書いてあります。

何かしらのアクションに行く前に Base コントローラの before_filter を通っています。

そこでログインしているユーザーのオブジェクトをセットしています。

Memos コントローラは全てログインが必要なので、

my $login_required = $filter->under->to('Users#logined_check'); として、

Users コントローラの logined_check アクションを噛ませます。

そこでログイン状態をみてログインしてなかったらログインページにリダイレクトさせれば良さそうです。