January 17, 2015

Laravelのクエリスコープをテストするメモ

以下の"Query Scopes"をいかに簡単にテストするか考えてみました。
http://laravel.com/docs/4.2/eloquent#query-scopes

Laravel5を使っていますが、Laravel4でも考え方は同じなはずです。

Illuminate\Database\Eloquent\Model を継承した Fooクラス が、以下のクエリスコープを持っているとします。
public function scopeBar(Builder $q, $baz)
{
    return $q->whereHas('qux', function ($q) use ($baz) {
        $q->where('quxx', $baz);
    });
}
とりあえず、このクエリスコープが組み立てるSQLを、バインドされるパラメータ含めて確認できれば良かったので、以下の方法でやってみました。

1.
storage/database.sqlite を作成。コネクションエラーを回避したいだけなので、空ファイルでOKです。

2.
config/database.php を編集。
'default' => env('DB_DRIVER', 'mysql'),
3.
phpunit.xml に追記。
<env name="DB_DRIVER" value="sqlite"/>
4.
テスト。
public function testScopeBar()
{
    $q = (new Foo())->bar(100);
    $sql = $q->toSql();
    $bindings = $q->getBindings();

    dd($sql, $bindings); // これをテストする
}
より良い方法があれば教えて下さい。

December 23, 2014

OpenShiftのJenkinsでGitLabのソースを取得する

BitbucketとGitLabが異なるだけで、方法は全く以下の通りなのですが、自分メモとして書き残しておきます。(大変参考にさせて頂きました。)
http://www.techscore.com/blog/2013/10/15/openshift-online-jenkins-bitbucket/

要点は、OpenShiftの~/.ssh/はパーミッション上、書き込み不可なので、代わりにGIT_SSHを使う。となります。

1.
~/app-root/data/.ssh/jenkins_id_rsa.pub をGitLabに登録する
* もちろん、新しく作成してもOKです。

2.
~/app-root/data/.ssh/jenkins_id_rsa のパーミッションを600にする

3.
~/app-root/data/.ssh/gitssh.sh を作成する
#!/bin/sh
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $OPENSHIFT_DATA_DIR/.ssh/jenkins_id_rsa "$@"
4)
~/app-root/data/.ssh/gitssh.sh に実行権限を与える

5)
Manage Jenkins > Configure System > Environment variables に以下の設定をする
name: GIT_SSH
value: 作成したgitssh.shのパス

6)
Jenkinsでビルド実行してソースが取得できればOKです。取得できなければどこかが間違っています。

OpenShiftのJenkins ServerにCapistrano 3をインストールする

OpenShiftのJenkins Serverはrubyのバージョンが1.8.7で、Capistrano 3をインストールしようとしたらエラーになりました。

簡単な方法は無いかなーと考えてみたんですが、とりあえず以下の方法でインストール出来ました。

適当な場所にbundleをコピーして、実行するrubyを変更します。
> mkdir ~/app-root/data/bin
> cp /usr/bin/bundle ~/app-root/data/bin/
vim等で ~/app-root/data/bin/bundle の先頭の行を以下に変更します。
#!/usr/bin/ruby193-ruby
適当な場所にGemfileを作ります。
source 'https://rubygems.org'

gem 'capistrano', '~> 3.3.0'
インストールと初期化がうまく行けばOKだと思います。
> ~/app-root/data/bin/bundle install
> ~/app-root/data/bin/bundle exec cap install

OpenShiftでJenkins Serverのビルドが"Waiting for next available executor"で進まない

メモです。

タイトルのとおりなんですが

Manage Jenkins > Manage Nodes > master(の設定) > "# of executors" を 1 に(0だった)

したら、進んだ。。。

December 20, 2014

LaravelのBladeテンプレートで、ビューファイルと、依存するJSやCSSファイルを整理整頓する

WEB開発をしていると、画面単位で使用するJSファイルやCSSファイルをどう管理するか、悩むことが良くあるのではないでしょうか。

そのせいでコントローラが複雑になってしまったり、コンフィグ化して管理場所が深い位置になってしまったり、スッキリと解決するのがなかなか難しい問題だと思います。共通化に失敗すれば、苦痛にしかなりません。

最近、LaravelのBladeテンプレートを用いて、その辺りがかなり解決できた(気がしている)ので、その例のメモです。Bladeテンプレートでなくても、継承式のテンプレートエンジンであれば、似たような事はできるんだろうと思います。

以下、Laravel 4.2を用いていますが、バージョンはたぶんあまり関係ありません。

1) 全体のレイアウトを管理するビューファイルを作成する
views/layouts/default.blade.php
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
@yield('append_css')
@yield('append_js')
</head>
<body>

<section id="content">
<h2>content</h2>
@yield('content')
</section>

<section id="content2">
<h2>content2</h2>
@yield('content2')
</section>

<section id="content3">
<h2>content3</h2>
@yield('content3')
</section>

</body>
</html>
このレイアウトファイルは、各画面用のビューファイルで @extends('layouts.default') されます。ポイントは @yield() している箇所です。それぞれの使い方は以下です。

@yield('append_css') ... 画面単位で使用するCSSを追加する領域
@yield('append_js') ... 画面単位で使用するJSを追加する領域
@yield('content') ... コンテンツ領域
@yield('content2') ... コンテンツ領域2
@yield('content3') ... コンテンツ領域3

この例では、3つのコンテンツ領域を縦に並べているだけですが、実際にはヘッダ領域になったり、サイドナビ領域になったり、メイン領域になったりするイメージです。このビューファイルは、レイアウトの大枠しか知りません。

2) 画面用のビューファイルを作成する
views/contents/index/index.blade.php
@extends('layouts.default')

@section('content')
@include('contents/top/_foo')
@stop

@section('content2')
@include('contents/top/_bar')
@stop

@section('content3')
@include('contents/top/_baz')
@stop
それぞれのコンテンツ領域に、後述する細分化されたビューファイルを @include() して展開しています。このビューファイルは、使用するレイアウトと、各コンテンツ領域でどのコンテンツを使用するかしか知りません。

3) 各コンテンツのビューファイルを作成する
views/contents/index/_foo.blade.php
<h3>foo</h3>

<?php
append_css('assets/css/contents/foo.css');
append_js('assets/js/contents/foo.js');
?>
views/contents/index/_bar.blade.php
<h3>bar</h3>

<?php
append_css('assets/css/contents/bar.css');
append_js('assets/js/contents/bar.js');
?>
views/contents/index/_baz.blade.php
<h3>baz</h3>

<?php
append_css('assets/css/contents/baz.css');
append_js('assets/js/contents/baz.js');
?>
これらのビューファイルは、自身のコンテンツ内容しか知りません。それぞれ内容は適当ですが、ポイントは append_css() と append_js() です。これは自作の関数で、以下になります。
<?php

if (! function_exists('append_css')) {
    /**
     * @param  mixed $styles
     */
    function append_css($styles)
    {
        static $appended = [];

        if (is_scalar($styles)) {
            $styles = [$styles];
        }

        foreach ($styles as $style) {
            if (in_array($style, $appended)) {
                continue;
            }

            $appended[] = $style;

            View::startSection('append_css');
            echo HTML::style($style);
            View::appendSection();
        }
    }
}

if (! function_exists('append_js')) {
    /**
     * @param  mixed $scripts
     */
    function append_js($scripts)
    {
        static $appended = [];

        if (is_scalar($scripts)) {
            $scripts = [$scripts];
        }

        foreach ($scripts as $script) {
            if (in_array($script, $appended)) {
                continue;
            }

            $appended[] = $script;

            View::startSection('append_js');
            echo HTML::script($script);
            View::appendSection();
        }
    }
}
@section('append_js')
{{ HTML::script('assets/js/contents/bar.js'); }}
@append
の方法で append すると、他ビューで同名のファイルが append された時、重複して読み込まれてしまいます。その防止策として、上記のように関数化しています。

これで、細分化されたコンテンツ単位で、依存するJSやCSSファイルが明確になりました。そのコンテンツが不要になれば、セットで削除するだけです。尚、他のビュー用のJSと連携する場合は、独自イベントを介するのが好ましいと思います。(Backbone.js的な考え方かも。)チーム作業時にコンフリクトしにくくなるメリットもあります。

デメリットは、コンテンツファイルが増えるとJSやCSSもファイル単位で増えるので、その結果、読み込みファイル数が多くなることです。JSは要らない場合もありますが、CSSはどんどん増えるはずなので、似たような考え方でSass等で管理した方が良いかもしれません。(実際には別の事情でそうしています。)

以下、参考までに、Sassを用いた簡単な管理例です。

A) 各コンテンツ用のSCSSファイル (上記の3と対) を作成する
B) 画面単位のSCSSファイル (上記の2と対) を作成して A を @import する
C) 画面単位のビューファイル (上記の2) で B のCSSファイルを append_css() する
D) 当然、各コンテンツのビューファイルでは append_css() しない

全体的に大切なことは、とりあえず適切な単位でファイル分割しておいて、各所で好きに組み合わせて使えること。でしょうか。

この方法はコントローラ側に一切手を付けなくて済むので、わりとおすすめなのですが、もっと良い方法があればぜひ教えて下さい。