nanisore oishisou

Webエンジニアあるまさんのゆるふわ奮闘記。

いろんなEager Loading(2)私の季節編

さて、引き続きEager Loadingについて書いていきますYO。
お気づきだと思いますが
前回、Eager Loadingについての記事なのに、Eager Loadingについて何の説明もせずに終わらせたベル子です。てへぺろ

突然ですが、私の一番好きな季節は秋です。
ご飯も美味しいし、紅葉も綺麗だし、気候もちょうどいい。
日本の一番に気に入ってるところは、秋があるというところと言っても過言ではありません。

落ち葉が舞う上野公園を少し散歩してから、国立西洋美術館で絵画を眺める。
少し肌寒いなあと思いながら、チャイを片手にベンチでミステリー小説を読む。
青梅街道のイチョウ並木沿いを自転車を走らせ、新宿に映画を観にいく。

孤独で気ままな生活が懐かしいです。
1日でいいから、そんなことをして過ごしたいなぁと切望する毎日です。
I'm eager to spend time doing things like that.

Eagerの英語での使い方を紹介してみました。

さて、おさらいです。
記事の一覧を表示し、記事に紐づくユーザー名を表示する場合で考えてみます。
↓のようにControllerに書いて、view側にarticlesのオブジェクトを渡すとします。

$articles = Article::all();

return View::make('articles')->with('articles', $articles);

そしてview側でこのように書くと、usersテーブルのrowを取得するクエリがN+1回走ってしまいます。

@foreach ($articles as $article)
     <h2>{{{$article->title}}}</h2>
     <p>posted by {{{$article->user->name}}}</p>
@endforeach

このような場合に、先行して1回のクエリで、関連したusersのデータを全て取ってきてくれるのがEager Loadingというしくみです。

では実際の書き方の紹介をします。

$articles = Article::with('user')->get();

簡単ですね。
先行して欲しいデータのリレーションメソッド名をwithメソッドの引数に入れるだけです。

このようにシンプルな関係を表現したいだけなら、とても簡単なのですが、実際のシステムを作る場合には、こんなにシンプルにはいきません。
テーブルはもっと複雑に絡み合っていて、表示させなければならないデータもたくさんあります。

たとえば、記事には筆者と編集者と提供した会社が紐付いているとしたらどうでしょうか。
まずはモデルでリレーションを定義します。

class Article extends Eloquent {

    public function writer()
    {
        return $this->belongsTo('Writer');
    }

    public function editor()
    {
        return $this->belongsTo('Editor');
    }
    public function company()
    {
        return $this->belongsTo('Company');
    }

}

@foreach ($articles as $article)
     <h2>{{{$article->title}}}</h2>
     <p>筆者: {{{$article->writer->name}}}</p>
     <p>編集者: {{{$article->editor->name}}}</p>
     <p>提供社: {{{$article->company->name}}}</p>
@endforeach

上記の場合、記事が10本あると、なんと31回もクエリが走ってしまいます。
(やってみてないけど多分そうです。)

ここでようやく本当の本題であるEager Loadingのいろいろな書き方の紹介をしたいと思います!


★1、複数のリレーションデータをいっぺんに取得したい。

上記のようなパターンで、すべてのリレーションテーブルのデータをEager Loadingするには下記のように書きます。


$articles = Article::with('writer', 'editor', 'company')->get();

このように書くことで今まで31回クエリが走っていたところ、4回のクエリで済むのです!
(やってないから、やってみて確かめてみてください。)


★2、さらに先のリレーションデータまでいっぺんに取得したい。

例えば、編集者は提供社に所属しているので、編集者のクラスに提供社とのリレーションが定義されているようなネスト構造になっているデータを取得したい場合です。

@foreach ($articles as $article)
     <h2>{{{$article->title}}}</h2>
     <p>編集者: {{{$article->editor->name}}}</p>
     <p>提供社: {{{$article->editor->company->name}}}</p>
@endforeach

このように取得しようとすると、10本記事があると21回のクエリが走ってしまいます。
しかし、ネストしているリレーションも、↓このように取得することができるんです!
すごいですね、Laravel。

$articles = Article::with('editor.company')->get();

このように書いておくと、21回走っていたクエリが3回で済むようになります。
(何度も言いますが、やってみてないです。)

★3、リレーションテーブルのデータ取得に条件を足したい。

例えば、編集者が退職していた場合、記事一覧には編集者名を表示したくないのでdeleted=1のものは表示したくない、のような場合です。

リレーションメソッド内に条件をチェーンでつなげることはもちろんできるのですが、リレーションメソッドはいろんな場所から参照していることが多いと思うので、イレギュラーな表示パターンが出てきてしまった際に、『今回だけ条件を足したい』のようなことがあります。

そんなときは以下のように、withメソッドの引数を配列にしてクロージャーを渡してやることで、リレーションテーブルの取得に、新たなクエリを追加することができます。

$articles = Article::with(['editor' => function ($query) {
    $query->where('deleted', 0);
}])->get();


★4、何かの条件のときだけ、Eager Loadingしたい。

例えば、ログインユーザーが筆者の時だけ編集者と提供社を一覧に表示するので、そのときだけEager Loadingしたい、のような場合です。

$articles = Article::all();

if ($user->role == 'writer') {
    $articles->load('editor', 'company');
}

このことを、Laravelサイトでは、"Lazy Eager Loading"と表現しています。

おいおいLazyなのかEagerなのか訳わからんじゃないか。
ネーミングセンスw
先にモデルを取得しちゃったけど、あとから追加でEager Loadしたくなったときに使ってね!ってことですかね。

このようにEager Loadingの仕方にもいろいろあるんですね。
非常に便利です!
こんなふうに書かなくても全部モデル内で別メソッドにしてやるぜ!という声が聞こえてきそうですが、いろんな書き方があるということを知ってると、あとで何か困ったことがあったときに使えるかもしれないです。

あと、不確かなのですが、Eager Loadしたデータにクエリを加えて再度get()だのfirst()だのしてしまうと、Eager Loadingしてくれなくなるような気がしています。
どうしてもEagerできなかったら、諦めるほかないということでしょうね。

まだまだピヨピヨなので、分からないことだらけなのですが、Eager LoadingやORMと上手に付き合って、スッキリきれいで読みやすいコードが書けるように精進していきたいです!!

ホントはコードを書いて確認してからアップしようと思ってたのですが、最近、デスノートをhuluで見始めたら面白くて止まらなくて(*´ω`*)

そのうち実際にコード書いて確かめてみますー。