CodeIgniter: hookを使ってbefore_filterやってみる。

CodeIgniterでコントローラーでbefore_filterやafter_filterするにはどうすればいいんだろう?と思い、調べてみたところ、CodeIgniterではフックが使えるらしいです。

フックとは、決められたタイミングで行われるコールバックのことです。
今回は認証機能をフックで呼んでみるような感じのサンプルを作ってみます。

  1. application/config/config.phpの$config[‘enable_hooks’]をTRUEに設定します。
  2. application/config/hooks.phpに、フックで使用するクラスを指定します。ここでは、BeforeFilterクラスを指定してみます。
    1
    2
    3
    4
    5
    6
    7
    8
    <?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
    // コントローラーのコンストラクタが終了したタイミングで呼ばれるフック名
    $hook['post_controller_constructor'] = array(
      'class'     => 'BeforeFilter', // 呼ぶクラス名
      'function'  => 'is_login', // 呼ぶメソッド
      'filename'  => 'before_filter.php', // ファイル名
      'filepath'  => 'hooks' // applicationフォルダからの相対パス
    );

    フックのタイミングは色々あります。一覧を記述します。

    pre_system
    システム実行の最初に呼ばれる。ベンチマーククラスとフッククラスだけロードされている。ルーティングはまだ。
    pre_controller
    コントローラーが呼ばれる直前に呼ばれる。すべての基本クラスのロード、ルーティング、セキュリティチェックなどが終わっている
    post_controller_constructor
    コントローラーがインスタンスされた直後。Railsのbefore_filterのタイミング。
    post_controller
    コントローラーが完全に実行された直後。Railsのafter_filterのタイミング。
    display_override
    ブラウザに送信するdisplayメソッドをオーバーライドした動きになる。処理済みのデータは$this->output->get_output()で取得可能。
    cache_override
    display_cacheメソッドの代わりに独自のメソッドを呼び出すことが可能なタイミング。独自キャッシュメカニズムとかに使える。
    post_system
    ブラウザに送信し終わったときのタイミング。ベンチマークをとるようなところ。

    今回はbefore_filterのようなフックを作りたかったので、post_controller_constructorを指定しています。また、同じタイミングで複数のフックを仕掛けたい場合は、二次元配列にすることで指定できます。次のようにするとよいでしょう。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    <?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
    // コントローラーのコンストラクタが終了したタイミングで呼ばれるフック名
    $hook['post_controller_constructor'][] = array(
      'class'     => 'Hoge',
      'function'  => 'foo',
      'filename'  => 'hoge.php',
      'filepath'  => 'hooks'
    );
    $hook['post_controller_constructor'][] = array(
      'class'     => 'Fuga',
      'function'  => 'bar',
      'filename'  => 'fuga.php',
      'filepath'  => 'hooks'
    );
  3. さて、あとはフックで呼ばれる対象のクラスを作成するだけです。application/hooks/before_filter.phpを作成します。
  4. before_filter.phpを以下のように記述します。
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    <?php
    class BeforeFilter {
      private $CI;
     
      public function __construct() {
        // CIオブジェクトを取得
        $this->CI =& get_instance();
      }
     
      function is_login() {
        if(!$this->CI->session->userdata('logged_in') &&
           $this->CI->uri->uri_string != '/auth/login') {
          // ログインしていない
          redirect('auth/login');
        }
      }
    }

    CIオブジェクトをget_instance()で取得してしまえば、あとはどうとでもなると思います。ここでは、セッションにログイン情報が入っているかを見てます。ただし、ログイン画面でもチェックしてしまうと無限ループになるので、ログイン画面以外でのチェックにしています。

Rails風なものと違って、別ファイルでフィルタ(フック)を定義するのですが、まぁ使い方はほとんど同じだなーという感じです。とりあえずこれでログインチェックをいちいち全てで行わなくて済むようになったので一安心です。


CodeIgniter: migrationをしてみる

現在、仕事ではなく個人的な用途でCodeIgniterを使っています。既存のコンテンツのCMS脱却を図ろうとしているためです(CMSでないほうが楽な場合のほうが多い。ブログは除く)。
CodeIgniterはシンプルなMVCモデルのフレームワークなのですが、Modelを使わない場合はわかりやすいテンプレート型のCMSとして活用できるな!と思ったので、これを採用しました。

しかし、データベースを使ったアプリ部分も新たに追加しようかなと思ったので、Modelも使ってみようかなと思い、調べていました。仕事でRailsをやるようになったので、CodeIgniterにもMigrationのような機能はないかなー?と思って調べてみたところ、ありました。でもまだ日本語のマニュアルはなさげです。

英語マニュアルにリンクをはっときます。
CodeIgniter User Guide Version 2.1.2 : Migration Class

  1. まず、application/config/migration.phpの$config[‘migration_enabled’]をTRUEに設定します。
  2. そして、applicationフォルダの下にmigrationsフォルダを作成します。
  3. そして、001_create_users.phpを作成し、以下のように定義します。
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <?php
    defined('BASEPATH') OR exit('No direct script access allowed');
     
    class Migration_Create_users CI_Migration {
     
      public function up()
      {
        $this->dbforge->add_field('id');
        $this->dbforge->add_field(array(
          'name' => array(
            'type' => 'VARCHAR',
            'constraint' => '100',
          ),
          'password' => array(
            'type' => 'VARCHAR',
            'constraint' => '100'
          )
        ));
     
        $this->dbforge->create_table('users');
      }
     
      public function down()
      {
        $this->dbforge->drop_table('users');
      }
    }
  4. 次に、application/config/migration.phpの$config[‘migration_version’]を1にします。
  5. migrateするには、Migrateライブラリを使う必要があるため、それを呼びます。呼ぶには、コントローラーからアクセスして呼ぶっぽいです。コマンドラインでもできるかもしれないけど、そのやり方は書いてなかったですね。例えば、Migrateコントローラーを作って呼んでみます。
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    <?php
    class Migrate extends CI_Controller{
     
      public function index() {
        $this->load->library('migration');
        if (!$this->migration->current()) {
          show_error($this->migration->error_string());
        }
      }
    }
  6. http://localhost/index.php?migrate/indexにアクセスしてみましょう(URLルーティングいじってたら別になりますので注意。まぁ普通はいじってると思いますが)
  7. エラーになったら、原因を直してリロードしましょう。何も表示されなかったらマイグレーション成功です。phpMyAdminなどで見てみましょう。

一カ所、ハマった点がありまして、それはprimary keyを設定するところでした。
$this->dbforge->add_field(‘id’);
とすると、自動的にそれがプライマリキーになります。
CodeIgniterのサイトのサンプルコードが’blog_id’となっていたので、それを’id’に修正してからmigrateしたら、エラーで怒られてしまいました。先にadd_fieldしておくとよいでしょう。
SQLを書かなくてもデータベースのテーブルを作ることができて、バージョンも管理できるという点が、やっぱり好きですねぇ。


Java: 数値をenumに変換する方法

enumはswitch文にも使えるし、ぱっと見でも何を表しているかわかるし、switch文のためにわざわざ定数をたくさん定義しなくてもよくなるので積極的に使っていきたいと思っていたのだが、データの永続化を行おうとすると、数値で保存する必要があるので、ordinal()を使って数値化していた。

しかし、数値からenumに戻す方法がわからなかったので、ググってみたら、よさげな方法があったので、それを載せておく。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public enum MultiMode {
 
    NONE,
    ENABLE,
    DISABLE;
    public static MultiMode valueOf(final int value) {
        for(MultiMode m : EnumSet.allOf(MultiMode.class)) {
            if(m.ordinal() == value) {
                return m;
            }
        }
        return null;
    }
}

valueOfメソッドをオーバーロードして、intからenumを返すように定義してみた。
enumに定義されている値と総当たりでチェックしていって、該当した時点でreturnしている。

もっといい方法があるのかもしれないけれど、自分的にはこれでいいかなという印象。


calabash-androidを試す。

最近、The RSpec Bookの布教活動に専念しているパトラッシュです。
おはようございます。

最近はテストの勉強をよくしています。Cucumberを使ってBDDをするというのがいいということがわかってきて、もしこういうのがAndroidでも使えたら面白いんじゃないかなー?と思ってぐぐってみたら、ありました。Calabashというやつです。

calabash

calabashは、AndroidとiOSのネイティブアプリのテストをCucumberで書くためのライブラリっぽいです。導入の仕方はgithubを読めということだったので、githubに行きます。

https://github.com/calabash/calabash-android

そこにインストールの仕方とかは書かれてるのですが、プロジェクトへの導入方法がざっくりとしか書いてなくて意味がわからなかったのでとりあえずメモとして残します。
(私の環境はMacOSX Lionです。)

インストール方法は簡単です(但し、Rubyが入ってる事前提)。
Rubyわかんねーよ。という人はrvmでぐぐって頑張れ。

1
gem install calabash-android
  1. 最初に、Androidプロジェクトを作っておきます。Hogeプロジェクトとしましょうか。
  2. Hogeプロジェクトを作ったら、忘れずにテストプロジェクトも作ります。
  3. ターミナルでHogeプロジェクトのディレクトリに移動します。
  4. calabash-android setup と打って実行すると、色々と質問されます。順番にいうと、
    1. パッケージ名を教えなさいよ。AndroidManifestに書いてあるでしょ?それよ!
      com.okolabo.android.hoge
    2. で?メインアクティビティはなんなの?
      com.okolabo.android.hoge.HogeActivity
    3. アプリへのパスを書きなさいよ!
      /User/******/Documents/workspace/hoge/bin/hoge.apk
    4. 一体いくつのバージョンが対象なのよ?
      8
    5. テスト用のkeystoreは?どうせdebug.keystoreなんでしょ?自分で指定したいんならyをdebug.keystoreだったらnを押せばいいんじゃない?私的にはnがお勧めよ!
      n
    6. さて、これでセットアップは終わったわ。.calabash_settingを作ったから、いつでも直せるわよ。べ、別にもう一度セットアップスクリプトを実行してもいいんだからね!?

    という感じです。

  5. 次に、Cucumber用のスケルトンファイルを作ります。
    calabash-android gen と入力すると、ドゥイーンとfeaturesディレクトリが作られます。
  6. エミュレータを作成しておきます。対象アプリをインストールしておきます。(Eclipseなら、Hogeプロジェクトの上で右クリックして、Run As -> Android Application)
  7. あとはcalabash-android run を実行します。自分でbuildするんなら云々みたいなのを聞かれた気がします。calabash-android buildをしておいたほうがいいのかもしれません。
  8. いきなり、マニフェストにandroid.permission.INTERNETがないよ!と怒られてテストが失敗します。INTERNETのパーミッションがないと、アプリとCucumberを行うサーバ側で通信ができないからテストできんぞ!ということらしいです。hogeプロジェクトのマニフェストに入れましょう。(アプリをリリースするときには外せばいいんかな?そこまで調べてない)
  9. もう一度、対象アプリをインストールし直し。(Run As -> Android Appication)
  10. 今度はマニフェストじゃなくて、テストコードによってテストが落ちます。最初に作られるCucumberのサンプルコードが、ログインボタンを押して云々みたいなのだから、当然です。うちのアプリはまだHello World, Hogeしか表示してませんから。
    まずはそれに対応させます。featuresディレクトリのmy_first.featureを開きます。
  11. 超適当に直しました。
    Cucumberを使った事ある人はわかるでしょう。
    Featureにユーザーストーリーを書きます。
    AのためにBがCしたい、なぜなら…みたいなのですが、ここでは省略。アジャイルサムライを読め!
    あとはシナリオを書きます。一部コメントアウトしました。ここでの目的は、テストを緑することだったので。

    1
    2
    3
    4
    5
    Feature: First feature
     
      Scenario: As a sample
        # When I press "Login"
        Then I see "Hello World, Hoge!"

    保存します。

  12. calabash-android run を実行します。成功するはずです。しなかったら、エミュレータを疑うか、シナリオをタイポってないかを疑えばいいかなと。

以上です。

AndroidやiOSでもCucumberによるBDDができるとなると、ユーザーに理解しやすいテストができてよさそうですねー!!


Ruby: The RSpec Bookを写経中

テストに関する本はないのかなー?と思っていたら、あったので、買ってみた。その名もThe RSpec Book。しかし、前書きからとんでもない始まり方をする。

「まんまと引っかかりましたね。(中略)本書はRSpecの本ではありません。」

えぇー!!と思ったのだが、前書きを読めば読むほど面白そうな内容であった。パラパラとつまみ読みをする本というよりも、ガッツリと写経するべき本だなと直感した。そして現在、写経中。78ページまで進んだ。

100ページにも到達していないのだが、多くの気付きがあった。
この本はBDDを体験するため本である。ユーザーストーリーを考え、それをCucumberに落とし込む。そしてエラーが起きた部分のロジックをRSpecで検証しながら進むという感じ。CucumberとRSpecが、同時並行的に補い合いつつプロダクトを完成させていく体験ができる。と感じている。

現在の仕事でも、RSpecとCucumberは使っているのだが、使い方というかアプローチが違って、「アプローチ的にはおそらくこれが普通なんだろう」と思っていたので正直目からウロコが落ちるような感じである。もちろん、テストをちゃんとしているという点で、今のプロジェクトはすごいと思うが。まぁ同じツールを使っているので、やっていることはほぼ同じだが、アプローチが違うだけでこうも印象が違うもんかと。

平たく言うと、今のプロジェクトはBDDではないから(後でテストを書く)、どうしてもプログラマーが書く感じのテストになってしまう(仕様がわかっているからこそ書けるテスト、とでもいうか)。もちろんあるだけ素晴らしいんだけども。

また、途中からプロジェクトに参加したため、RSpecとCucumberに対する認識が浅かったので、一からCucumberのstep_definitionsを書いていくのとかがすごい新鮮であるというのがある(テスト結果がコピペ用のコードを書いてくれてるのとか知らなかった)。

そういう細かな点や、テストがこうなるのでこうしましょうという説明がかなり丁寧に書かれているので、躓くことがあんまりない。あるとしたら、コード内の英語を読むのくらいじゃないだろうか?

写経でアジャイルを経験できる本でもあるので、そういう意味でもいい本だなと思う。アジャイルサムライを読んだ後に取り組むとよさげ。また、写経すればいいだけなので、Ruby以外のプログラマにとってもすごくいい本だと思う。まぁRailsに特化している後半はどうかわからんけれども。ついでにRuby好きになってくれればw