CodeIgniter セッションに日本語を入れると切れる問題

フラッシュメッセージを出そうと思って、セッションに日本語をいれようとしたら、勝手にログアウトされてしまう現象に見舞われました。
データベースのセッション用のテーブルがよくないのかなーと思って、照合順序をutf8_general_ciに変更したら日本語も扱えるようになりました。セッションテーブル生成の際にまるっとコピペで作ったのがまずかったのかもしれません。

とりあえずphpmyadminで直してやりました。

気をつけましょう!


CodeIgniterでForm Validationを使う。

CodeIgniter 2.1.3を使っています。

Form Validationを使うのはとっても簡単です。
アクション名でライブラリを読み込みましょう。

class Hoge extends CI_controller {
  function create() {
    if ($this->input->post()) {
      $this->load->library('form_validation');
      $this->form_validation->set_rules('title', 'タイトル', 'required');
      if (!$this->form_validation->run()) {
        // 再度フォームを表示する
      } else {
        // DBに登録するなど
        // redirect('/hoge/index');
      }
    } else {
      // フォームを表示する
    }
  }
}
[/php]

set_rulesで、ルールを設定します。
第一引数がname、第二引数がラベル、第三引数がルールです。
1
$this->form_validation->set_rules(name, label, roles);

ルールは、|を使うことで複数指定できます。

CodeIgniterは日本語の言語ファイルがないので、英語のやつをとってきて修正していきます。
言語ファイルは、system/language/englishにあります。これをコピーして、application/language/japaneseにペーストします。

form_validation_lang.phpの中身を自由に書き換えてやりましょう!
また、labelに言語ファイルを指定することもできます。上記の例ではそのまま指定しましたが、多言語対応しようと思ったら、必要ですね。

// これを
$this->form_validation->set_rules('title', 'タイトル', 'required');
// こうする
$this->form_validation->set_rules('title', 'lang:attr_title', 'required');

attr_titleは適当に設定します。
application/language/japanese/attr_lang.phpを作り、以下のようにします。

$lang['attr_title'] = 'タイトル';

適宜読み込みたいので、autoload.phpに設定しましょう。
autoload.phpにも書いてありますが、ファイル名がattr_lang.phpの場合、読み込み指定はattrにします。_langは省略します。

// application/config/autoload.php
$autoload['language'] = array('attr');
[/php]

これで、エラーメッセージのラベルについては言語ファイルから参照しにいきます。


CodeIgniterのCLIでDBアクセスするときの注意点

CodeIgniterでデータベースのマイグレーションしたいじゃないですかー?
そうすると、migrationライブラリを読み込んでいろいろしたいけれど、ブラウザからコントローラーにアクセスしてやるのはナンセンスじゃないですかー?
そうなると、CLIになるじゃないですかー?

CodeIgniterのコントローラーでは、CLIからのアクセスのみに限定する方法があるので、マイグレーションはCLIからのみにします。$this->input->is_cli_request()で条件分岐すればよいです。
あと、マイグレーションファイルは既に書いているものとします。
これは、マイグレーションを実際に実行するコントローラーです。

<?php
class Migrate extends CI_Controller{

  public function __construct() {
    parent::__construct();
    $this->config->load('migration');
    $this->load->library('migration');
  }

  function migration() {
    if ($this->input->is_cli_request()) {
      $migrationVersion = $this->config->item('migration_version');
      if (!$this->migration->current()) {
        echo $this->migration->error_string();
        return;
      }
      echo "Migrate: #{$migrationVersion}\n";
    }
  }

  function rollback() {
    if ($this->input->is_cli_request()) {
      $migrationVersion = $this->config->item('migration_version');
      $rollbackVersion = $migrationVersion - 1;
      echo "Current Version: #{$migrationVersion}";
      if (!$this->migration->version($rollbackVersion)) {
        echo $this->migration->error_string();
        return;
      }
      echo "Rollback: #{$migrationVersion} -> #{$rollbackVersion}\n";
    }
  }
}

さぁこれで準備はできたかなーと思って、いざマイグレーション。

cd /path/to/codeigniter # CodeIgniterで作ったアプリのディレクトリ
php index.php migrate migration

エラーになりました。
Unable to connect to your database server using the provided settings.

ブラウザ経由だとエラーにならないだけに、謎だなーと思っていたのですが、config/database.phpのhostnameがlocalhostだと、エラーになるようです。127.0.0.1にしたら、通りました。

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
$active_group = 'default';
$active_record = TRUE;

$db['default']['hostname'] = '127.0.0.1'; // localhost => 127.0.0.1に変更した
$db['default']['username'] = 'user';
$db['default']['password'] = 'password';
$db['default']['database'] = 'database';
$db['default']['dbdriver'] = 'mysql';
$db['default']['dbprefix'] = '';
$db['default']['pconnect'] = TRUE;
$db['default']['db_debug'] = TRUE;
$db['default']['cache_on'] = FALSE;
$db['default']['cachedir'] = '';
$db['default']['char_set'] = 'utf8';
$db['default']['dbcollat'] = 'utf8_general_ci';
$db['default']['swap_pre'] = '';
$db['default']['autoinit'] = TRUE;
$db['default']['stricton'] = FALSE;
$db['default']['port'] = 8889;

/* End of file database.php */
/* Location: ./application/config/database.php */

CLIもコントローラーでなんとかするという感じが微妙な感じがするCodeIgniterですが、マイグレーションくらいはCLIでやりたいから、これでいきましょうかねぇ。


BACKBONE.JSガイドブックを読んで

仕事でjQueryでゴリゴリやることに限界というよりは疑問を感じるようになって、JavaScript MVCを調べるようになり、辿り着いたbackbone.js。Railsで使いたいなーと思ってbackbone-on-railsを使って、やってみようかと思ったけれど、まだbackbone.jsのことをよく理解してなかったので失敗。
んで、PHPでやっているプロジェクトがあるので、こっちはピュアなJavaScriptでやっているから、こっちでやってみることにした。

ちなみに4章以降はやっていません。(パラパラ読みはしてる)

お供にBACKBONE.JSガイドブックを持ってやっているのだけれど、俺のやり方が悪いのかもしれないんだけれど、なんかちょっと不親切だなーと思うところがあった。
Backbone.Routerを使ってルーターを定義し、Backbone.history.start();をしても動かなかった。
ルーターに設定されているURLにヒットすればBackbone.history.start()の戻り値はtrueになるはずだったのだが、ずっとfalseを返していた。

var Router = Backbone.Router.extend({
    routes: {
        'admin/index.html': 'admin_index'
    },
    admin_index: function() {
        var view = new AdminView();
        $('#content').html(view.render().el);
    }
});
$(function(){
    console.log(Backbone.history.start()); // false
});

ここで、オプションの{pushState: true}を追加したら、動き出した。

var Router = Backbone.Router.extend({
    routes: {
        'admin/index.html': 'admin_index'
    },
    admin_index: function() {
        var view = new AdminView();
        $('#content').html(view.render().el);
    }
});
$(function(){
    console.log(Backbone.history.start({pushState: true})); // true
});

オプションなので、デフォルトで動き出すのかと思ったら、そうでもないぽかった。start()したときにルーティングが合致すれば即座に実行されると書かれていたのだが、開始されなくてすごく参った。これは省略しても動く条件があるのか、それともpushStateじゃないと動かないのか?わからない…。(俺的にはpushStateじゃなくても動くと思ってたんだが)

また、アプリケーションの遷移をさせるのに、Backbone.history.navigate()を使う。これもまたやり方が悪かったのかもしれないが、動かなかった。p.83の「Aタグをクリックしたらnavigateする」というやつをそのまま書いたのだが…。

$(document).on('click', 'a[href][data-navigate]', function(e) {
    e.preventDefault();
    Backbone.history.navigate($(this).attr('href')); // 動かない…
});

正確には、navigateはされているんだけれど(ブラウザ上のURLは書き換えられてる)、triggerが発火しない。本では、「navigateで合致するルーティングが登録されていたら実行される。URLは遷移するが処理を実行したくない場合は第2引数に{trigger: false}を渡します。」とあった。処理をしたくない場合だけ書くのかと思っていたのだが…。サンプルもそうなっているし…。

ひょっとしてと思って、{trigger: true}にしたら、処理が実行された。

$(document).on('click', 'a[href][data-navigate]', function(e) {
    e.preventDefault();
    Backbone.history.navigate($(this).attr('href'), {trigger: true}); // 動いた!
});

これが通ったらあとは結構普通に書くことができたけれど、この2つにすげーハマった…。

また、本の中で紹介されているbackbone-formsというプラグイン。これを使ってみようと思って入れてみた。これはかなりよさそうで、bootstrapにも対応しているのでsimple-formいらなくなるんじゃね?くらいに思えたのだけれど、formのsubmitボタンが生成されない…。Backbone.Formのインスタンスでcommitメソッドを実行すると、保存されるみたいな感じだったけれど、そのタイミングがsubmitボタンじゃねーの!?と思うのだが…。生成の仕方があるのかもしれないが、ぐぐった感じだと無理矢理追加するというパターンばっかりでスマートなやつがなかった。submitボタンはフラグで置くかどうか選べるくらいあってもよいのになぁと思った。もっとBackbone.Viewに詳しくなってからのほうがいいかもしれないと思って、結局使うのをやめた。

あと、Backbone.Viewでつかうビューのテンプレートを、JavaScript内でゴリゴリとhtmlを書いているのが、なんとも…。まぁテンプレートはunderscore.jsの_.templateメソッドで作れるのであるが、これもまた面倒というか…。面倒くさがり過ぎかもしれんが。

Railsと組み合わせた場合、backbone-on-railsならばJSTのテンプレートが最初から使える状態になっていて、テンプレートはテンプレートでerbみたいに書けるので、いいと思うのだが、backbone.js単体でやろうと思うと結構手間な感じがする。

Backbone.Modelでデータを更新するときに使われるBackbone.syncの、デフォルトでアクセスするurlがRailsを想定したようなRESTful APIなので、本当にRailsを使うのであれば、すごく有用だと思う。だが、俺は今回PHPのCodeIgniterでやろうとしていた。CodeIgniterではライブラリを使えばRESTは実現できるっぽいんだけれど、Railsに比べるとものすごく手間がかかる(というかRailsが楽すぎる…)。

結論は、Backbone.jsはRailsもしくはYEOMANでないと面倒くさそう(YEOMANは使ったことないけれど)。

Backbone.jsのような大量のJavaScriptを作るような仕組みの場合は、4章で説明しているようなRequireJSとかGruntとかでごにょごにょやるようなのがないとキツい。Railsは勝手にコンパイルしてくれるので、相性が本当にいいんだろうと思う。

コードを書いていて、今回PHPで作っているツールはBackbone.jsを使わずにPHPでサクッと作ったほうがいいなと思った。

Backbone.js自体はすごくいい感じだと思う。よくわからないところもあるけど。Railsは本当にScaffoldした後はrootに指定したページからBackbone.jsで書き換えるだけでいいので、ほぼフロントエンドでの仕事になるんじゃないかと思うが、テンプレートもerbもしくはhamlみたいに書けるから、違和感もなさそうだし。

Railsプロジェクトでは積極的に使っていきたい印象。

本の感想としては、JavaScriptばっかに注力しすぎてる。前半からテンプレートをどういうふうにするかをもっと掘り下げてほしかった。
初心者向けではないのはわかるけれど、順序がなぁ…という感じ。


アニメーションをオフにしてRailsのテストを速くする方法

※この方法はtwitter bootstrapのmodalを使っている場合、機能しません。

テストを書いていると、Capybaraがアニメーションの途中でボタンを押そうとして、ボタンが見つからないというエラーがでるということはありませんか?
僕は毎回のようにあります。

たまたまググっていたら、テスト環境のjQueryのアニメーションをオフにする方法がヒットしたので、これはいいと思ってやってみたところ、問題なく動きそうだったのでとりあえず採用。また、CSS3のアニメーションをオフにする方法も紹介されていたので、それも合わせて組み込んでみました。

参考にしたURLはこちら。

まずはCSS3のアニメーションを無効にするviewを定義。
views/layouts/_no_transition.html.slim

- if Rails.env.test?
  css:
    .notransition * {
      -webkit-transition: none !important;
      -moz-transition: none !important;
      -o-transition: none !important;
      -ms-transition: none !important;
      transition: none !important;
      /*CSS transition properties*/
      -webkit-transition-property: none !important;
      -moz-transition-property: none !important;
      -o-transition-property: none !important;
      -ms-transition-property: none !important;
      transition-property: none !important;
      -webkit-transform: none !important;
      -moz-transform: none !important;
      -o-transform: none !important;
      -ms-transform: none !important;
      transform: none !important;
      -webkit-animation: none !important;
      -moz-animation: none !important;
      -o-animation: none !important;
      -ms-animation: none !important;
      animation: none !important;
    }

次に、jQueryのアニメーションを無効にするviewを定義。
views/layouts/_jquery_no_animation.html.slim

- if Rails.env.test?
  javascript:
    $.fx.off = true;
    $('body').addClass('notransition');

最後に、レイアウトファイル。
views/layouts/default.html.slim

doctype html
html lang="ja"
  head
    meta charset="utf-8"
    meta content="IE=Edge,chrome=1" http-equiv="X-UA-Compatible"
    meta content="width=device-width, initial-scale=1.0" name="viewport"
    title = content_for?(:title) ? yield(:title) : t("app_name")
    = csrf_meta_tags
    /! Le HTML5 shim, for IE6-8 support of HTML elements
    /![if lt IE 9]
      <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
    = stylesheet_link_tag "application", media: "all"
    = render partial: 'layouts/no_transition'
  body
    = render "layouts/navbar_fixed"
    .container
      .content
        .row
          .span12
            = bootstrap_flash
            = yield
      footer
        p © Company 2013
    = javascript_include_tag "application"
    = render partial:'layouts/jquery_no_animation'

これで、テストの際のみ、アニメーションがオフにされます。
が、先頭で書いているように、twitter bootstrapのmodalが表示されずにテストがエラーになったので、使えるときと使えないときがあるようです。とりあえずjQueryのアニメーションくらいはオフにしてもいいんじゃないかなー?と思います。