DBICでUser->find_by_id(1)とかやってみた

追記

改良版書きました
DBICでUser->find_by('age', 1)とかUser->find_all_by('user_id', 1)とかやってみた


catalystアプリの中で、DBICを使ってDBに問い合わせする部分をActiveRecordっぽくしてみた。

before

my $user = $c->model('DBIC::Users')->search({'me.id' => 1, 'me.deleted' => 0},{prefetch => [qw/book/]})->next;

after

my $user = User->find_by_id(1);

動機

  • $c->model〜〜って長すぎる。打つのがめどい
  • テーブルは複数形だけど、モデル名は単数形がいい
  • 何より最近railsにハマってる身としては、railsライクにいきたい

モデル名を単数形にするのは神がやってたのをパクらせてもらった。
なのでUserで$c->model('DBIC::User')を呼ぶところだけ。

やってみよう

最初にやってみたのはこんなの。

  • MyApp::Controller::Root.pm
package MyApp::Controller::Root;

use strict;
use warnings;

use base 'Catalyst::Controller';
__PACKAGE__->config->{namespace} = '';

sub p {warn Dumper shift}

sub auto :Private {
    my ( $self, $c ) = @_;

    *User = sub { $c->model('DBIC::User') };
    p User->find(1)->id; # 1が返る
    return 1;
}

駄目な点

  • 2回目以降のアクセスではSubroutine MyApp::Controller::Root::User redefinedが発生
    • no warnings 'redefine'するのは嫌
  • テーブルの数だけこれ書くんかい?
  • しかも全コントローラに?
改善する上で問題となる点
  • なんとか外部に定義したい。しかし、定義にはどうしても$cが必要。

やってみよう・2回目

定義を一箇所に書いてみよう

  • MyApp.pm
use Catalyst qw/
    〜〜〜
    +MyApp::Utils
/;
  • MyApp::Utils.pm
package MyApp::Utils;

use strict;
use warnings;
use DirHandle;

use base qw/Exporter/;

my @schemas;

use subs @schemas;
our @EXPORT = @schemas;

BEGIN {
    my $path = $ENV{APP_HOME} || ''; # apache使用時のため
    $path .= './lib/MyApp/Schema';   # 本当は$c->path_toを使いたい

    my $d = DirHandle->new($path);
    while (defined (my $f = $d->read)) {
        next if $f =~ /^\./;
        $f =~ s/\.pm//;
        push @schemas, $f;
    }
}

sub setup {
    my $c = shift;

    $c->NEXT::setup(@_);

    foreach my $f (@schemas) {
        no strict 'refs';
        *{$f} = sub { $c->model("DBIC::$f") };
        *{'MyApp::ResultSet::' . $f . '::find_by_id'} = sub {
            my ($self, $id) = @_;
            $f = lc $f;
            $self->search({"$f.id" => $id})->next;
        }
    }
}

1;
  • MyApp::Controller::Root.pm
package MyApp::Controller::Root;

use strict;
use warnings;

use MyApp::Utils;
略

改善した点

  • use subsでredefined対策
  • コントローラで書いてたのをMyApp::Utilsに定義するようにした、コントローラではこれをuseするだけでよい
  • テーブル名はMyapp/Schema以下から取得するようにした
  • find_by_idを自動で生やした

駄目な点

  • Utilsで$cを使うためだけにMyAppでuseして、なおかつコントローラでもuseするってどうよ
  • Utilsって名前の割にUtilじゃなくね?センス狂ってね?

Myapp/Schema以下からテーブル名取ってこれるのはこんな構成にしてるため
最近のcatalyst構成 - だるろぐ跡地


でもいい感じに出来てきた。

やってみよう・3回目

  • MyApp::Utils.pm

MyApp::ActiveRecord.pmにリネーム。

  • MyApp.pm
use Catalyst qw/
    〜〜〜
-    +MyApp::Utils
+    +MyApp::ActiveRecord
/;
  • MyApp::Controller::Root.pm
- use MyApp::Utils
+ use MyApp::ActiveRecord
  • MyApp::Schema::User.pm
__PACKAGE__->resultset_attributes({
    alias => 'user',
    from  => [{user => 'users'}],
    where => {'user.deleted' => 0},
    cache => 1,
    prefetch => [qw/book/],
});

終わり。
作成にあたり、神の残したコードにお世話になり、元・神様にヘルプしまくりました。感謝。

もっとやりたいこと