まったり Rails (その5)

さて、scaffoldによるプロトタイプの管理アプリケーションが紆余曲折ありながらも動いたので、次はモデルに項目を追加してみよう。

既存のモデルに項目を追加するには、データベースとの連携を開始した時にしたのと同じ方法を使う。具体的にはスクリプトからmigrationを実行してMigrationの派生クラスを生成し、必要なコードを追加後にrakeタスクを実行する。

  • 項目'price'を追加するadd_priceを生成する
>ruby script/generate migration add_price
      exists  db/migrate
      create  db/migrate/002_add_price.rb

作られたスクリプト'002_add_price.rb'の'002'はマイグレーションの版数の表している。最初に実行した時(テーブルを生成した時)は'001'だったので、今回は'002'という訳だ。

  • 002_add_price.rb (修正前)
class AddPrice < ActiveRecord::Migration
  def self.up
  end

  def self.down
  end
end

実際にmigrationスクリプトを実行するrake dbタスクでは、この版数を使って今までの版数を遡って以前の設定に戻したり、そもそもデータベーステーブルを消去することが可能になる。
self.upメソッドとself.downメソッドは重要だ。それぞれ、migrationがアップグレードモードで実行された場合と、ダウングレードモードで実行された場合に外部から呼びだされるため、データベース/テーブルを変更する動作と、それと対になる変更を取り消す動作を正しく書く必要がある。

さて、ではスクリプトを書き換えて項目を追加してみよう。

  • 002_add_price.rb (修正後)
class AddPrice < ActiveRecord::Migration
  def self.up
    # テーブル名, カラム名, 型, 有効桁数, 小数点以下桁数, 既定値
    add_column :products, :price, :decimal, :precision => 8, :scale => 2, :default => 0
  end

  def self.down
    # テーブル名, カラム名
    remove_column :products, :price
  end
end

今回は項目を追加するのでself.upメソッドにはadd_columnメソッドを使い、self.downメソッドではremove_columnメソッドを使う。※

正しく書けたら(私は一度間違ったが)、rakeのdbタスクで変更をデータベースとモデルに反映する。
なお、反映されたモデルとscaffoldは動的に変更が反映されるため、migrate後にサーバを再起動する必要は無い。(この仕組みを見たことの無い人が見たら、大抵はびっくりする)

  • 実行したコマンド
>rake db:migrate
(in E:/www/sample)
== 2 AddPrice: migrating ======================================================
-- add_column(:products, :price, :decimal, {:scale=>2, :default=>0, :precision=>8})
   -> 0.2350s
== 2 AddPrice: migrated (0.2400s) =============================================

ここまで上手くいっていれば、scaffoldによるadminには見事 ':price'が追加されていることだろう。

Railsがそれまでのフレームワークと違う所は、それまでプログラミングフェーズだけで完結していたフレームワークに、コマンド、それも開発作業の中で実際に必要だったコマンドを昇格させ、生成したコードと結びつけることでによって開発作業の重複と無駄を省き効率をアップさせたことだ。

Microsoftならば、RailsのコマンドをGUIベースのウィザードにしただろうし、Visual Studioのアドインにしただろうが、Railsはそれをしなかった。

私にとってはコマンドの良さを再確認させてくれたのがRuby on Railsというフレームワークである。

※migrateで生成されたself.up、self.downメソッドだが、なぜ 'self.'とインスタンスを明示しているのだろう。単にActiveRecordモジュール内のメソッドだから?

コメントでご指摘頂いたが、メソッド定義での'self.〜'はインスタンスメソッドではなく、クラスメソッドを定義する場合の書き方だ。
Rubyのクラスメソッドは他の言語のように、クラス名.メソッド名でも書けるが定義部でself.メソッド名、と書くのが一般的のようだ。