Rails2.3からRails3への移行

先頃、Ruby on Rails 3.0.0がリリースされたが、早速手元のアプリケーションを移行してみた。ちょっと変わったアプリなので、一般的な移行手順の話とは若干ずれるかもしれないが、とりあえず備忘録的に書いておこうと思う。

参考にした資料は、WEB+DB PRESS Vol.58の特集「詳解 Rails3」。内部構造から新規プロジェクト立ち上げ、移行のチュートリアルまで、要点をおさえてわかりやすく書かれている。

さて今回対象にしたのは Walkrr というぼくの散歩コースを記録するためのアプリ。変わっていると書いたが、具体的には、主要なテーブルがひとつだけのシンプルなスキーマ、画面遷移はなし、ほとんどの処理はJavascript, ajaxで実行されるという特徴をもつ(ライブラリーは jQuery)。苦労したのはほとんどAjaxまわりだった。

順を追っていこう。上記記事の第6章「移行の手引き」に従い、新規作成した Rails3の空のアプリケーション既存のRails2.3のアプリケーションのコードをかぶせるという方針をとった。移行のポイントは大きく次の3つ。

  1. routing ファイル config/route.rb を新しい書式で記述し直す。
  2. gemを GemFile というファイルで統合的に管理するようにする。Rails3では従来の Plugin もgemとしてインストールすることが推奨されていて、実際今回は拡張機能的なものはほぼすべて gem としてインストールした。今まで Plugin としてインストールしていた spatial_adapter はちゃんと Rails3 に対応した gem が公開されているし、jrails は不要になった。例外はある意味 jrails の代替である、jquery-ujs という jQuery で unobtrusive Javascript (後述)を実現するためのライブラリーで、この中の rails.js というファイルを手動で public/javascripts ディレクトリにコピーした。あと、will_paginate に関しては Rails3 に対応した開発版をインストールした。
  3. link_to_remote, button_to_remote 等従来 ajax リクエストを発行するための用意されていたメソッドが廃止され、通常の非ajaxのリクエストを発行する link_to, button_to, form_tag 等のメソッドに引数として “:remote => true” を指定すると ajax リクエストが発行されるようになった。これは unobtrusive Javascript という、Javascriptのコードを表面上から隠蔽して、使用する Javascript ライブラリーに依存しないマークアップを生成する手法で実現されている。

さて問題は3番だ。実はこれまで XXX_remote でできていたことが、unobtrusive JavaScript ですべてそのまま実現できるわけじゃない。今まで submit => ‘…’ で指定した任意の form の内容をsubmitしたり、before => ‘…’ でsubmitの前に前処理的なコードを実行していたのだが、どちらもサポートしていなかった。そこで、 submit に関してはタグの階層を変更して、自分の属する form の内容をsubmitするように変更し、before に関しては以下のように jQuery のイベントハンドラーを登録するようにした。

$("#form_id").bind("ajax:before", function () {
    //some procedure
});

これでOKかと思ったが、そうは問屋がおろさなくて、次にはまったのは <em>.rjs ファイルだ。ajax の結果を受けて、Javascript 形式のView ファイルが動き、画面の書き換えを行うようになっているが、</em>.rjs はそのViewの形式のひとつだ。似たようなものに <em>.js.erb というファイルがあるが、こちらが、基本的に Javascript ファイルで <%= ... %> で囲んだ ruby コードが動的に展開されるのに対し、 </em>.rjs はすべて ruby で記述する。今回、rjs の中で呼び出している、 page.replace_html というページの中の特定の部分を書き換えるコードが、中途半端なことに prototype.js に特化したままになっていて、jQuery には対応してなかったのだ。最初、 *.js.erb 形式にしてしまおうかと思ったが、そうすると、rubyコード部分のシングルクォーテーションとダブルクォーテーションの対応関係に気をつけなくてはいけないし、改行をわざわざ省かなくてはいけなくなる。そんな妙な気をつかうよりは、参照している prototype.js のメソッドをjQueryで定義した方が早い。ということで以下のコードを Javascriptファイルに挿入した。

Element.update = function (element, html) {
    $('#' + element).html(html);
}

そしてさらにもうひとつ、1つのformの中で選択したsubmitボタンに応じて action と ajax/非ajax を切り替える処理が必要になった。試行錯誤の末、 それぞれの onclick の中で form の action属性と data-remote属性を切り替えるようにした。 data-remote というのは先ほど紹介した unobtrusive Javascript 固有の属性だ。これが “true” ならば ajax で接続を行う。ただし、たぶん jqueiry-ujs のバグだと思うが、これを “false” にするだけではだめで、属性ごと取り除けば、通常の非ajaxの通信が行われるようになる。

だいたいそんな感じでどうにか移行が完了した。移行前と移行後のソースコードは以下の場所で公開している。masterブランチが Rails3 用 for_rails238ブランチが Rails2.3用だ。