復習を兼ねてRoR2.0

昨年(2007)12月に、Rails のバージョンが2に上がりました。現在の最新は 2.0.2 となっています。1系とは若干(?)仕様が異なるようで、そのままでは動かない既存アプリも出てきそうです。
そこで、今回はInstantRailsをバッサリと入れ替えた上で、復習を兼ねて簡単なアプリを構築してみましょう。

Windows XP にインストールしてあるInstantRailsを最新版に入れ替えます。これでRuby on Rails(RoR)のバージョンも 2.0.2 に上がりました。

プログラミングはいつものAptanaIDEで書きましょう。

ナレッジデータベースを作ろう

プロジェクトを作る

今回のテストアプリは、ナレッジデータベースです。一行程度の短い質問と、その答えのデータベスです。カテゴリ分けできるようにしておきましょう。
早速、[ファイル]-[新規]-[Rails Project]でプロジェクトを作ります。名前は、knowledge としておきます。

まずデータベースの設定をしておきましょう。
knowledgeプロジェクトの、config\database.ymlを開きます。
# SQLite version 3.x
#  gem install sqlite3-ruby (not necessary on OS X Leopard)
development:
 adapter: sqlite3
 database: db/development.sqlite3
 timeout: 5000

# Warning: The database defined as 'test' will be erased and
# re-generated from your development database when you run 'rake'.
# Do not set this db to the same as development or production.
test:
 adapter: sqlite3
 database: db/test.sqlite3
 timeout: 5000

production:
 adapter: sqlite3
 database: db/production.sqlite3
 timeout: 5000
あれれ、デフォルトのデータベースが SQLite3 になってます。そうです、RoRバージョン2では、SQLite3がデフォルトになりました。でも、そんなの関係ねぇ、ここはいつものMySQLに変えておきます。おっぱっぴー
development:
 adapter: mysql
 database: knowledge
 username: root
 password:
 host: localhost
 encoding: utf8

test:
 adapter: mysql
 database: knowledge_test
 username: root
 password:
 host: localhost
 encoding: utf8

production:
 adapter: mysql
 database: knowledge
 username: root
 password:
 host: localhost
 encoding: utf8
rakeを走らせるとtestデータベースはクリアされますので、productionデータベースとは違う名前にしておきます。

ここで、MySQLにknowledgeデータベースを作っておきます。私の場合、こちらは[MySQL Administrator]というGUIツールを使っています。[カタログ]を開き、[Create New Schema]で、knowledge を作ります。

今までならここでmigrationファイルを作り、テーブルを作成してしまうのですが、RoR2.0の作法は違うようです。
先に足場を組みます。
AptanaIDE の[Generators]タブを選び、
scaffold category sort_order:integer name:string
で最初の足場を作ります。
これはカテゴリマスタになります。RoR1.0と異なり、ここでカラムの指定をします。
sort_orderカラムは並び順の指定、nameカラムはカテゴリ名称になります。
続いて、
scaffold item category_id:integer question:string answer:string
Q&A本体の足場も作ります。
これで何が出来たのか、ちょっとだけ確認してみると――app\viewsのファイルは拡張子がxxx.html.erb みたいになっています。list.rhtmlはindex.html.erbだし、_form.rhtmlは無くなっています。
app\controllersのファイルも中身は変更になっているようです。
デフォルトではpagenateがありません。
respond_to do |format|
 format.html # index.html.erb
 format.xml { render :xml => @items }
end
見慣れないものがあります。これは、クライアントからの要求により、htmlかxmlを返す仕組みのようで、http://ほげほげ/xxx.html を要求すると通常のWEBページが、http://ほげほげ/xxx.xml を要求すると、XML形式でデータが取得できます。

さて、話を戻して、db\migrateの中を見ておきましょう。
001_create_categories.rb
002_create_items.rb
が出来ています。中を開けてみます。
class CreateCategories < ActiveRecord::Migration
 def self.up
  create_table :categories do |t|
   t.integer :sort_order
   t.string :name

   t.timestamps
  end
 end

 def self.down
  drop_table :categories
 end
end
少し変わっていますが、まあ判るでしょう。
def self.up の直ぐ下に、
  options = {
   :options => "ENGINE=MyISAM DEFAULT CHARSET=utf8"
  }
を足しておきましょう。
他に初期値の設定なども必要に応じて書けますが、今回はこのままです。

では、001_create_categories.rb を右クリックして[Run Migration]しましょう。
うまく行ったようなら 002_create_items.rb も実行します。

これで足場とテーブルが揃いました。Servers タブで、KnowledgeServer を起動します。

ブラウザで、http://localhost:3000/categories に繋いでみます。


表示されましたか? あの懐かしくも素っ気ない画面ですね。
試しに何件か追加してみましょう。

ここで、 http://localhost:3000/categories.xml とすると、登録したデータがXML形式で送られてきます。今のところ使い途を思いつきませんが、データ再利用の折にはきっと役に立つでしょう。

カテゴリの並び順を何とかする

カテゴリを新規追加するたびに自分で sort_order を手打ちするのはあんまりです。何とかしましょう。
新規追加のときに、sort_order が自動的に一番最後の番号になるようにしてみます。
app\models\category.rb に、以下のように追加します。
class Category < ActiveRecord::Base
 def before_create
   begin
   self[:sort_order] = Category.maximum(:sort_order) + 1
  rescue
   self[:sort_order] = 1
  end
 end

end
app\views\categories\new.html.erb から、sort_order 登録部分を削除しておきます。
カテゴリの順序を入れ替える機能を付けましょう。
一覧画面で、上下に移動可能にします。
\app\views\categories\index.html.erb に、以下を追加します。
<% for category in @categories %>
 <tr>
  <td><%=h category.sort_order %></td>
  <td><%=h category.name %></td>
  <td><%= link_to 'Up', :action => 'move_up', :id => category %> <%= link_to 'Down', :action => 'move_dn', :id => category %></td>
  <td><%= link_to 'Show', category %></td>
  <td><%= link_to 'Edit', edit_category_path(category) %></td>
  <td><%= link_to 'Destroy', category, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr
<% end %>
move_up、move_dn を呼び出しますので、呼ばれる側を作りましょう。
\app\controllers\categories_controller.rb に、以下を追加します。
 def move_up
  renum_up
  redirect_to :action => 'index'
 end

 def move_dn
  renum_dn
  redirect_to :action => 'index'
 end

private
 def renum_up
  category = Category.find(params[:id])
  if (category.sort_order > 1)
   current_id = category.id
   current_sort_order = category.sort_order
   previous_id = Category.find_by_sort_order(current_sort_order - 1)
   Category.update(previous_id, :sort_order => current_sort_order)
   Category.update(current_id , :sort_order => current_sort_order - 1)
  end
 end

 def renum_dn
  category = Category.find(params[:id])
  if (category.sort_order < Category.maximum(:sort_order))
   current_id = category.id
   current_sort_order = category.sort_order
   next_id = Category.find_by_sort_order(current_sort_order + 1)
   Category.update(next_id , :sort_order => current_sort_order)
   Category.update(current_id, :sort_order => current_sort_order + 1)
  end
 end
一覧表示で[Up][Down]をクリックすると―― Sort Order の順序が入れ替わりますね。希望していたのとはちょっと違う挙動です。
一覧の並び順を変えましょう。
\app\controllers\categories_controller.rb に、以下を追加します。
 def index
  @categories = Category.find(:all, :order => "sort_order")

  respond_to do |format|
   format.html # index.html.erb
   format.xml { render :xml => @categories }
  end
 end
今度はどうでしょう? いいカンジですか?

Q&A(items)の登録画面に手を入れる

http://localhost:3000/items を表示させ、new_item を押すと、こんな画面が表示されるはずです。

カテゴリはIDを直打ちしなけりゃならないし、Q&Aの入力欄はちっちゃいし、気に入りませんよね。
では、app\views\items\new.html.erb を開きましょう。
まずはカテゴリの選択部分から直しましょう。
 <p>
  <b>Category</b><br />
  <%= f.text_field :category_id %>
 </p>
こんな感じですね。
これを
 <p>
  <b>Category</b><br />
  <%= select("item", "category_id", Category.find(:all).collect {|e| [ e.name, e.id ] }) %></p>
 </p>
こんな風に変更します。
ドロップダウンリストになったかな?
Category.find(:all).collect の部分、以前は Category.find_all.collect みたいに書いていたんですが、これはエラーになります。RoR2では find_all や find_first は無くなり、代わりに find(:all)、find(:first) を使うようになっています。

Question欄とAnswer欄も変更します。
 <p>
  <b>Question</b><br />
  <%= f.text_field :question, :size => 60 %>
 </p>

 <p>
  <b>Answer</b><br />
  <%= f.text_area :answer, :cols => 60, :rows => 8 %>
 </p>
size、cols、rowsの値は好きに決めましょう。

入力画面が決まったら、変更画面(edit.html.erb)も同様に書き換えましょう。

Q&A(items)の一覧画面を見やすくする

初期状態では、こんな感じです。

見にくいです。
まずはカテゴリ表示を名称に変えます。
  <td><%=h item.category_id %></td>
  <td><%=h Category.find(item.category_id).name %></td>
に書き換えます。
また、一覧表示に Answer は要らないので、削除してください。
後は好みに応じて変更を加えれば良いでしょう。
――私の場合は、

色を付けたり、同じカテゴリが続くときには先頭のみ表示させたり。ああ、そうそう、Category.sort_order 順に並べるのがまだでしたね。

\app\controllers\items_controller.rb を開きます。
 def index
  @items = Item.find(:all)
次のようにJOINして順序を指定します。
 def index
  @items = Item.find(:all,
    :joins => "INNER JOIN categories ON items.category_id = categories.id",
    :order => "categories.sort_order"
)
show をクリックしてQ&Aを表示させてみましょう。

まず、カテゴリ表示を名称に変えます。
<p>
<b>Category:</b>
<%=h Category.find(@item.category_id).name %>
</p>
このテクニックにもずいぶん慣れましたね。
しかし、まだ見にくいです。登録時には改行が入っていたはずのAnswerが、一つの文章に繋がって表示されています。
これは、データとしては改行が入っていても、HTMLとして表示されるときには無視されているからです。HTML表示のときに、改行を<BR>に置き換えれば良さそうです。
早速置き換え用ユーザー定義関数を作りましょう。
場所は、\app\helpers\application_helper.rb にしましょう。
module ApplicationHelper
 def text_to_html(text)
  if text
   text.gsub!(/</,"&lt;")
   text.gsub!(/>/,"&gt;")
   text.gsub!(/\&/,"&amp;")
   text.gsub!(/\"/,"&quot;")
   text.gsub!(/\'/,"&quot;")
   text.gsub!(/\r\n/,"<br >")
   return text
  end
 end
end
改行だけなら、text.gsub!(/\r\n/,"<br >") 以外は不要です。
関数を作ったので、表示ルーチンも変更します。
<p>
 <b>Answer:</b>
 <%= text_to_html(@item.answer) %>
</p>
大体良さそうですか?私は表示部をテーブル化してみました。show.html.erb の最終形は次のようになりました。
<table border="0" cellpadding="5">
<tr bgcolor="powderblue">
 <th valign="top" align="left">Category:</th>
 <td valign="top" align="left"><%=h Category.find(@item.category_id).name %></td>
</tr>

<tr bgcolor="bisque">
 <th valign="top" align="left">Question:</th>
 <td valign="top" align="left"><%=h @item.question %></td>
</tr>

<tr bgcolor="seashell">
 <th valign="top" align="left">Answer:</th>
 <td valign="top" align="left"><%= text_to_html(@item.answer) %></td>
</tr>
</table>

<%= link_to 'Edit', edit_item_path(@item) %> |
<%= link_to 'Back', items_path %>
こんな感じです。



さて、これで一通りの機能が揃いました。以前に作ったアプリとあんまり違わない内容になってしまいましたね。
逆に言えば、RoR1のときと殆ど同じ手順でアプリを作れることが確認できました。

まだ仕事ではホスト系のアプリやDelphiばっかりですが、いずれRoRで本格的な業務用アプリを構築したいですね。

会社に、IBM i5の新品がやってきました。旧モデルの置き換えですが、OSその他のバージョンが上がり、今までは諦めていた Ruby on Rails からの DB2 アクセスが出来るかも知れません。
i5 はようやく組み上がって火が入る状態になったところなので、RoRからの接続テストを実行するまでにはもう少し掛かりそうです。

そういえば、RoRでOracleのDBは使えるんかいな?






サイト名: flyman のおもちゃ箱   http://www.kestrel.jp
この記事のURL:   http://www.kestrel.jp/modules/tinyd04/index.php?id=7