境界を越える: Rails のマイグレーション

データベース・スキーマの変更を再考する

Ruby on Rails は進歩的な Web 開発フレームワークであり、過激な概念、例えばコンフィギュレーションよりも規約優先、メタプログラミング偏重、ドメイン固有言語、オブジェクト・リレーショナル・マッピングではなくデータベース・ラッピングなどを実装しています。この記事では、Railsのスキーマ・マイグレーションの考え方、つまり各データベース・スキーマの変更を、ベースとなるオブジェクト・モデルから分離する考え方について検証します。

Bruce Tate (bruce.tate@j2life.com), President, RapidRed

Bruce TateBruce Tateは父であり、マウンテンバイク乗りであり、カヤック乗りであり、そしてテキサス州オースチンに住んでいます。Joltを受賞した『Better, Faster, Lighter Java』を含め、ベストセラーとなった3冊の著書を執筆しています。最近、『Spring: A Developer's Notebook』を発刊しました。彼はIBMに13年間在籍した後、J2Life, LLC社を設立し、Java技術とRubyに基づく軽量開発戦略とアーキテクチャーを専門としたコンサルティングを行っています。



2006年 8月 15日

私は熱心なサイクリストとして、マウンテン・バイクに乗る人達と一般道用の自転車に乗る人達という、2つの本格的なコミュニティーがあることを知っています。通常の考え方では、山道でマウンテン・バイクを乗り回す方がずっと危険と思われていますが、私はそう思いません。一般道を走る人達は、岩や木よりもはるかに危険な障害物、つまり自動車を考慮しなければなりません。同じような考え方の違いが、オブジェクト指向アプリケーション開発に対する2 つのパーシスタンス方式のいずれか一方をサポートするグループの間にも生まれています。

現在のパーシスタンス・フレームワークでは、2 つの方式のうち、いずれか一方が使われています。つまり、マッピング方式、あるいはラッピング方式のどちらかです。マッピング・ソリューションでは、独立したデータベース・スキーマとオブジェクト・モデルを作成することができ、作成した後にソフトウェア・レイヤーを使って両者の間の違いを調整することができます。マッピング・ソリューションでは、データベース・スキーマの構造に非常に似た、オブジェクト・モデルを作ろうとします。これとは対照的にラッピング・ソリューションでは、データベースのテーブルや行を囲むラッパーとしてオブジェクトを使うことによって、データベースの中のデータを操作します。通常の考え方では、いったんソリューションができてしまえば、マッピング・ソリューションの方が柔軟であると考えられています。これは、マッピング・ソフトウェアの方が、スキーマやオブジェクト・モデルの変更にうまく対応できるためです。しかし、そうした捉え方は、最も重要な部分、つまりデータを無視しています。パーシスタントなドメイン・モデルを含む、アプリケーションの変更を効果的に処理するためには、データの変更やスキーマの変更、そしてモデルの変更を調整する必要があります。ほとんどのプロジェクトでは、これを適切に行っていません。

通常、開発チームは、スキーマの変更を処理する場合は、SQL スクリプトを使って、新しいバージョンのスキーマを白紙の状態から再生成します。スクリプトは、すべてのテーブルを削除してしまい、そして再度すべてのテーブルを追加するかもしれません。そうした方式では、すべてのテスト・データは破壊されてしまうため、実稼働のシナリオでは使い物になりません。場合によると、ツールが、デルタ・スキーマを生成するスクリプトを作成したり、alter_tableのような SQL コマンドを使って前のバージョンのスキーマを変更するスキーマを作成したりする場合もあります。しかし、スキーマ変更を元に戻すスクリプトを作成する手間をかけるようなチームは、ほとんどありません。ましてや、データの変更を処理する自動スクリプトを作成するチームとなると、さらに少数です。簡単に言えば、通常のマッピング方式は、道路上の自動車を無視している、つまり不適切なスキーマ変更を元に戻し、またデータを処理するという問題を無視しているのです。

このシリーズについて

この、境界を越えるシリーズでは、著者である Bruce Tate が、「今日の Java プログラマーは、他の手法や言語を学ぶことから多くを得ることができる」という概念を押し進めます。プログラミングの世界の様相は、あらゆる開発プロジェクトにとってJava が最善の選択肢であることが明確だった頃から変わってきています。他のフレームワークもJava フレームワークと同じ構築形態をとりつつあり、また他の言語での概念を学ぶことによって、それをJava プログラミングに生かすこともできます。皆さんが書く Python (あるいはRuby や Smalltalk、その他何であれ) コードによって、皆さんの Java コーディングに対する取り組みも変わる可能性があるのです。

このシリーズでは、Java 開発とは大幅に異なりながら、同時に Java 開発にも直接応用できるプログラミング概念や手法について紹介します。場合によると、そうした技術を利用するためには、それらを統合する必要があるかも知れません。また場合によると、そうした概念は直接利用できるものかもしれません。他の言語やフレームワークが、開発者やフレームワーク、Javaコミュニティーでの基本的な手法にまで与えうる影響に比べると、個々のツールはそれほど重要ではありません。

この記事では、Ruby on Rails のマイグレーションについて、すなわち実稼働データベースへの変更を扱うRails のソリューションについて、詳しく見ていきます。マイグレーションは、実稼働のデータベースに加えられる変更に対応するための、Railsによるソリューションです。マイグレーションは強力さと単純さを兼ね備えており、スキーマの変更とデータの変更の両方を、ラッピング方式を使って処理します。(Rails の基礎となっているパーシスタンス・レイヤーである Active Record についてまだ知らない人は、この「境界を越える」シリーズで以前Rails をとりあげた記事をまず読むようにお勧めします。)

Java プログラミングでのスキーマ変更の処理

マッピング・ベースのフレームワークでは、スキーマとモデル、そしてマップが必要です。こうしたアーキテクチャーでは、繰り返しが多くなります。下記のような属性を、いったい何度繰り返し指定すればいいのでしょうか。

  • モデル中のゲッター
  • モデル中のセッター
  • モデル中のインスタンス変数
  • マッピング「先 (to)」
  • マッピングの「元 (from)」
  • 列定義

念のために言うと、Hibernate などの Java フレームワークの場合は、コード生成をふんだんに使うことによって、ほとんどの繰り返しを見えないようにしています。レガシー・スキーマはオブジェクト・リレーショナル・マッパーを使って処理でき、また新しいデータベース・スキーマに関しては、Hibernateに用意されているツールを使ってモデルから直接スキーマを生成でき、自分が選んだIDE を使ってゲッターとセッターを生成することができます。マッピングは、Javaのアノテーションを使えばドメイン・モデルの中に埋め込むことができます (しかしそうすると、そもそもマップを持つ意味が部分的に損なわれると私は思います)。このコード生成手法は、他の目的、つまりスキーマのマイグレーションにも使用できます。こうしたコード生成ツールのいくつかは、新しいドメイン・モデルと古いスキーマとの違いを検出し、そうした違いを埋めるためのSQL スクリプトを生成することができます。ただし、こうしたスクリプトはスキーマを処理するためのものであり、データを処理するわけではないことに注意してください。

例えば、データベースの中の first_name と last_name という列をマージしてname という1 つの列にするマイグレーションを考えてみてください。通常の Javaパーシスタンス・フレームワークのツールでは、問題の一部、つまりスキーマの変更しか処理できないため、データベース管理者の役に立ちません。このスキーマ変更を行う場合には、既存のデータも処理できなければなりません。この仮説アプリケーションの新しいリリースをデプロイする際には、データベース管理者は通常、下記を行うSQL スクリプトを手動で作成しなければなりません。

  • name という新しい列を作成する
  • first_namelast_name からのデータをキャプチャーして新しい列に入れる
  • first_name 列とlast_name 列を削除する

もし、このスキーマ変更の元となったコード・バージョンに何か問題があった場合には、通常は手動で変更をロールバックしなければなりません。モデルやスキーマ、そしてデータ全体に渡って変更を統合し、自動化できる仕組みを持っているチームは、ごく稀です。


Rails でのマイグレーションの基本

Rails では、すべてのスキーマ変更 (スキーマを最初に作成することを含みます)は、マイグレーションの中で起こります。データベース・スキーマの中での変更は、up動作と down 動作をカプセル化した、それぞれ独自のマイグレーション・オブジェクトを持っています。リスト1 は、空のマイグレーションを示しています。

リスト 1. 空のマイグレーション
class EmptyMigration < ActiveRecord::Migration
  def self.up
  end

  def self.down
  end
end

マイグレーションの呼び出し方はすぐ後に説明しますが、とりあえず、リスト1 に示すマイグレーションの構造を見てください。このマイグレーションの upメソッドの中に、データベースの論理的変更を 1 度行うために必要な全コードを置きます。また、すべてのスキーマ変更を取り消すための、すべての変更もキャプチャーします。Railsの開発や生産のためのツールは、up と down をカプセル化することによって、パーシスタント・オブジェクト・モデルを含むすべての変更の、デプロイ、取り消しプロセスを自動化することができます。データベースに対する、そうした変更には、次のようなものがあります。

Rails の開発生産ツールは、up と down をカプセル化することによって、パーシスタント・オブジェクト・モデルを含むすべての変更の、デプロイ、取り消しプロセスを自動化することができます。

  • 新しいテーブルの追加または削除
  • 新しい列の追加または削除
  • インデックスや他の制約の追加、削除、修正などによる、データベースへの変更
  • データベースのデータの変更

マイグレーションでは、データへの変更を許すことによって、データの変更とスキーマの変更(一般的には同時に起こります) を、容易に同期できるようになっています。例えば、各州と2 桁の ZIP コードとを関連付ける新しいルックアップ・テーブルを追加することができます。マイグレーションの中では、例えばSQL スクリプトを呼び出すことで、あるいはフィクスチャーをロードすることで、データベース・テーブルにデータを追加することができます。マイグレーションが適切なものであれば、マイグレーションを行ってもデータベースは一貫した状態に保たれ、手動で調整する必要がありません。

各マイグレーションに対するファイル名は、固有の番号で始まります。この規約のおかげで、Railsはマイグレーションを明確に順序付けることができます。またこの方式では、データベース・スキーマの、前の論理状態と後の論理状態の間を行ったり来たりすることができます。


マイグレーションを使う

マイグレーションを使用するために必要なものは、Rails プロジェクトとデータベースのみです。この記事のコードを追うためには、リレーショナル・データベース・マネージャー、Rubyと、Rails のバージョン 1.1 以上をインストールします。皆さんは既に作業を始めていることになります。以下のステップに従って、データベースを持つRails プロジェクトを作成します。

  1. rails blog とタイプして、blog という Rails プロジェクトを作成します。
  2. blog_development というデータベースを作成します。私は MySQL を使って、MySQLのコマンドラインから単純にcreate database blog_development をタイプします。
  3. 必要に応じて、config/database.yml によってデータベースをコンフィギュレーションし、データベースへのログインID とパスワードを追加します。

番号付けがどのように動作するかを見るために、マイグレーションを生成します。

  1. blog ディレクトリーから、ruby script/generate migration create_blog をタイプします。(Unix で実行している場合は ruby を省略することができます。私もこの先ではruby を省略します。)
  2. script/generate migration create_user をタイプします。(blog/db/migrate の中のファイルを見ると、連続した番号の付いた2 つのファイルがあるのが分かります。マイグレーション・ジェネレーターが番号を管理してくれるのです。)
  3. 001_create_blog.rb というマイグレーションを削除し、script/generate migration create_blog を使ってマイグレーションを再度作成します。リスト 2 に示すように、新しいマイグレーションが003_create_blog.rb として作成されています。
リスト 2. マイグレーションを生成する
> cd blog
> script/generate migration create_blog
      create  db/migrate
      create  db/migrate/001_create_blog.rb
> script/generate migration create_user
      exists  db/migrate
      create  db/migrate/002_create_user.rb
> ls db/migrate/  
001_create_blog.rb      002_create_user.rb
> rm db/migrate/001_create_blog.rb 
> script/generate migration create_blog
      exists  db/migrate
      create  db/migrate/003_create_blog.rb
> ls db/migrate/
002_create_user.rb      003_create_blog.rb

マイグレーションには、それぞれ数字の接頭辞が付いています。新しいマイグレーションには、そのディレクトリーの中で最も大きい接頭辞、プラス1 が付けられます。この方法によって、マイグレーションは順番に再生成され、そして順番に実行されるようになります。他の何かの内容に基づくマイグレーションの構築(例えば他のマイグレーションによって作成されたテーブルに列を追加するマイグレーションなど)にも一貫性が保たれることになります。この通し番号システムは単純で直感的な上に、一貫性もあります。

データベースの中でのマイグレーションの動作を見るために、db/migrations ディレクトリーの中にあるすべてのマイグレーションを削除してみます。script/generate model Article とタイプし、Article に対するモデル・オブジェクトと、リスト 1 に示した空のマイグレーションを生成します。Rails が、モデル・オブジェクトと、それぞれのarticle に対するマイグレーションを生成することに注意してください。db/migrate/001_create_articles.rbを編集し、リスト 3 のようにします。

リスト 3. CreateArticles に対するマイグレーション
class CreateArticles < ActiveRecord::Migration
  def self.up
    create_table :articles do |t|
      t.column :name, :string, :limit => 80
      t.column :author, :string, :limit => 40
      t.column :body, :text
      t.column :created_on, :datetime
    end
  end

  def self.down
    drop_table :articles
  end
end

マイグレート・アップとマイグレート・ダウン

マイグレーションが何をするのかを具体的に見るために、とにかくマイグレーションを実行し、データベースを見てみましょう。blog ディレクトリーから、rake migrate とタイプします。(Ruby でのrake は、C プラットフォームでの make や、Java プラットフォームでの ant に相当するものです。)migrate は 1 つのrake タスクです。

次に、データベースの表を表示しましょう。MySQL で単純に mysql> コマンド・プロンプトを使ってuse blog_development; をタイプし、次に show tables をタイプすると、リスト 4 に示す結果が得られます。

リスト 4. Rails のマイグレーションによって作成された schema_info テーブル
mysql> show tables;
+----------------------------+
| Tables_in_blog_development |
+----------------------------+
| articles                   |
| schema_info                |
+----------------------------+
2 rows in set (0.00 sec)

mysql> select * from schema_info;
+---------+
| version |
+---------+
|       1 |
+---------+
1 row in set (0.00 sec)

2 番目のテーブル、schema_info に注意してください。このマイグレーションではarticles テーブルを指定しましたが、rake migrate コマンドは自動的にschema_info を作成したのです。今度は select * fromschema_info を実行します。

何もパラメーターを付けないでrake migrate を実行すると、まだ適用されていないマイグレーションをすべて実行するようにRails に要求することになります。Rails は下記のすべてを行います。

  • もしschema_info テーブルが存在していない場合には、このテーブルを作成します。
  • なにも行が存在していない場合には、値 0 を持つ行をschema_info の中に挿入します。
  • 現在のマイグレーションよりも大きな番号を持つすべてのマイグレーションに対してup メソッドを実行します。rakeschema_info テーブルの version 列の値を読むことによって、現在のマイグレーションの番号を判断します。rake は最小の値から最大の値へとup マイグレーションを実行し、また逆に、最大の値から最小の値へと down マイグレーションを実行します。

マイグレート・ダウンするためには、単純に、あるバージョン番号を付けてrake migrate を実行します。マイグレート・ダウンはデータを破壊する可能性があるため、十分に注意する必要があります。テーブルや列の削除など、一部の操作もデータを破壊します。リスト5 は、マイグレート・ダウンを行ってからマイグレート・アップを行った結果を示しています。これを見ると、schema_info が現在のバージョン番号を忠実に追跡している様子がわかると思います。この手法によって、様々な開発段階を表現するスキーマの間を容易に行ったり来たりできるのです。

リスト 5. マイグレート・ダウン
> rake migrate VERSION=0
(in /Users/batate/rails/blog)
== CreateArticles: reverting ==================================================
-- drop_table(:articles)
   -> 0.1320s
== CreateArticles: reverted (0.1322s) =========================================

> mysql -u root blog_development;
mysql> show tables;
+----------------------------+
| Tables_in_blog_development |
+----------------------------+
| schema_info                |
+----------------------------+
1 row in set (0.00 sec)

mysql> select * from schema_info;
+---------+
| version |
+---------+
|       0 |
+---------+
1 row in set (0.00 sec)

mysql> exit
Bye
> rake migrate
(in /Users/batate/rails/blog)
== CreateArticles: migrating ==================================================
-- create_table(:articles)
   -> 0.0879s
== CreateArticles: migrated (0.0881s) =========================================

> mysql -u root blog_development;
mysql> select * from schema_info;
+---------+
| version |
+---------+
|       1 |
+---------+
1 row in set (0.00 sec)

さて、今度はテーブルそのものをいじってみましょう。少し前に戻り、リスト 3 と、テーブルの定義を見てください。MySQL で show create table articles;コマンドを実行します。そうすると、リスト 6 に示す結果が得られます。

リスト 6. article のためのテーブル定義
mysql> show create table articles;
+----------+...-----------------+
| Table    | Create Table |
+----------+...-----------------+
| articles | CREATE TABLE 'articles' (
  'id' int(11) NOT NULL auto_increment,
  'name' varchar(80) default NULL,
  'author' varchar(40) default NULL,
  'body' text,
  'created_on' datetime default NULL,
  PRIMARY KEY  ('id')
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+----------+...-----------------+
1 row in set (0.00 sec)

これを見ると、このテーブル定義の大部分がマイグレーションによって作られたものであることが分かると思います。Railsのマイグレーションの重要な利点の 1 つとして、テーブルを作成するためにダイレクトSQL 構文を使う必要がないことがあげられます。それぞれのスキーマ変更は Rubyの中で処理するため、できあがる SQL はデータベースから独立しています。しかし、id列に注意してください。この列は指定しませんでしたが、auto_increment と NOTNULL を使ったことによって、Rails のマイグレーションが作成してくれたのです。この特定の列定義を持つid 列は、識別子列に関する Rails の規約に従っています。もしこの列を id を付けないで作成したい場合は、マイグレーションはリスト7 に示すマイグレーションのように、単純に :id オプションを付加します。

リスト 7. id 列を持たないテーブルを作成する
  def up
    create_table :articles, :id => false do |t| 
      ...
    end
  end

ここまでは 1 つのマイグレーションを深く掘り下げましたが、まだスキーマの中に1 つも変更を作成していません。そろそろ別のテーブルを作成する時です。ここではコメント用のテーブルを作成します。script/generate model Comment とタイプして、Comment というモデルを生成します。db/migrate/002_create_comments.rb の中に作られるマイグレーションを、リスト8 のように編集します。いくつかの列を持つ新しいテーブルが必要になります。また、ヌルではない列とデフォルト値を追加する、というRails の機能を利用することになります。

リスト 8. コメント用の、2 番目のマイグレーション
class CreateComments < ActiveRecord::Migration
  def self.up
    create_table :comments do |t|
      t.column :name, :string, :limit => 40, :null => false
      t.column :body, :text
      t.column :author, :string, :limit => 40, :default => 'Anonymous coward'
      t.column :article_id, :integer
    end
  end

  def self.down
    drop_table :comments
  end
end

早速、このマイグレーションを実行します。もし万が一、マイグレーションの実行中にエラーが発生した場合には、マイグレーションがどのように動作するかを思い出してください。schema_info の行の値をチェックし、データベースの状態を調べる必要があります。コードを修正した後で、いくつかのテーブルを手動で削除したり、schema_info の行の値を変更したりする必要があるかもしれません。魔術ではないことを忘れないでください。Railsは、まだ実行されていない、すべてのマイグレーションに up メソッドを実行するのです。もし、既に存在しているテーブルまたは列にテーブルや列を追加しようとすると、その操作は失敗します。ですから、そのマイグレーションが一貫した状態にあることを確認する必要があります。ここではとりあえず、rake migrate を実行します。リスト 9 はその結果です。

リスト 9. 2 番目のマイグレーションを実行する
> rake migrate(in /Users/batate/rails/blog)
== CreateComments: migrating ==================================================
-- create_table(:comments)
   -> 0.0700s
== CreateComments: migrated (0.0702s) =========================================

> mysql -u root blog_development;
mysql> select * from schema_info;
+---------+
| version |
+---------+
|       2 |
+---------+
1 row in set (0.00 sec)

マイグレーションは、様々な種類のスキーマ変更を処理することができます。例えば、インデックスを追加、削除することができます。列を削除、リネーム、もしくは追加することでテーブルを変更できます。また、必要であればSQL に戻ることもできます。SQL で可能なことは、マイグレーションでもすべて可能なのです。Railsは、下記のような大部分の一般的な操作に対して、ラッパーを持っています。

  • テーブルの作成 (create_table))
  • テーブルの削除 (drop_table)
  • テーブルへの列の追加 (add_column)
  • テーブルからの列の削除 (remove_column)
  • 列のリネーム (rename_column)
  • 列の変更 (change_column)
  • インデックスの作成 (create_index)
  • インデックスの削除 (drop_index)

マイグレーションによっては 1 つ以上の列を変更し、データベース内の 1 つの論理的変更として、まとめる場合もあります。ここで、最上位レベルのblog を追加するマイグレーションを考えてみてください (このblog は、そのblog に属する articles を持っています)。このマイグレーションのためには、新しいテーブルを作成する必要があり、また、あるblog を指す各 article への外部キーを表す新しい列も追加する必要があります。リスト10 は、このマイグレーションの全体を示しています。このマイグレーションを実行するには、rake migrate とタイプします。

リスト 10. テーブルを作成し、列を追加するマイグレーション
class CreateBlogs < ActiveRecord::Migration
  def self.up
    create_table :blogs do |t|
      t.column :name, :string, :limit => 40;
    end
    add_column "articles", "blog_id", :integer
  end

  def self.down
    drop_table :blogs
    remove_column "articles", "blog_id"
  end
end

データも同様です

SQL で可能なことは、マイグレーションでもすべて可能です。

ここまではスキーマの変更にのみ焦点を当ててきましたが、データの変更も重要です。データベースの変更では、スキーマの変更だけではなくデータの変更も要求される場合があります。また、こうしたデータ変更には、論理的な変更が要求される場合もあります。例えば、blog の記事にコメントが付けられることを示すために、各記事に対して新しいコメントを作成したい、としましょう。このblog が開設されてからしばらく経った後に変更を行う場合には、まだコメントが付けられていない記事にのみ新しいコメントを付けたいものです。この変更は、マイグレーションを使えば容易に行うことができます。これは、マイグレーションがモデル・オブジェクトにアクセスでき、そのモデルの状態に基づいて論理判断が行えるためです。そこで、script/generate migration add_open_for_comments とタイプします。belongs_to という関係をキャプチャーし、新しいマイグレーションを書くために、コメントに変更を加える必要があります。リスト11 は、モデル・オブジェクトと新しいマイグレーションを示しています。

リスト 11. モデル・オブジェクトと新しいマイグレーション
class AddOpenForComments < ActiveRecord::Migration
  def self.up
    Article.find_all.each do |article|
      if article.comments.size == 0
        Comment.new do |comment|
          comment.name = 'Welcome.'
          comment.body = "Article '#{article.name}' is open for comments."
          article.comments << comment
          comment.save
          article.save
        end
      end
    end
  end

  def self.down
  end
end

ここで皆さんは、リスト 11 に示すマイグレーションに対して、戦略的な判断を行う必要があります。ユーザーはいったんウェルカム・メッセージが追加されたら消えないように望んでいる、と皆さんは判断し、そのためダウン・マイグレーションでは何もレコードを削除しないことにします。マイグレーションの中でデータ変更に対応できるという機能は、非常に貴重です。つまりデータの変更とスキーマの変更を同期できるのです。また、モデル・オブジェクトに対する論理操作を伴うデータ変更にも対応することができます。

以上で、マイグレーションでできることの大部分を紹介しました。これらの他にも、便利なツールがいくつかあります。既存のデータベースに対してマイグレーションを使い始める場合には、rake schema_dump を使って既存のスキーマのスナップショットをとることができます。このrake タスクは、適切なマイグレーション構文を持った Ruby スキーマを db/schema.rbの中に作成します。そうなると、マイグレーションを生成することができ、マイグレーションの中にダンプしたスキーマをコピーできるようになります。(詳細については参考文献を参照。) また、この記事ではテスト・フィクスチャーについても触れませんでした。テスト・フィクスチャーは、テスト・データの設定や、データベースに初期データを設定する際に有効です。詳細については、私の境界を越えるシリーズで、ユニット・テストを取り上げた以前の記事を見てください。


最終的な比較

Java プログラミングでのマイグレーションは、それほど堅牢なものではありません。一部の製品では、一部のスキーマ・マイグレーションの問題に対して部分的なソリューションを持っていますが、スキーマ変更(戻る変更と進む変更の両方) に対する体系的なプロセスがない限り、データとオブジェクト・モデルの変更への対応は困難です。一方、Railsのソリューションには、次のような重要な利点があります。

  • Rails のマイグレーションは、DRY (don't repeat yourself) です。つまり Railsでは、それぞれの列定義を、1 度だけ、マイグレーションの中で行えば済むのです。他の一部のマッパーでは、列の指定を6 回も行う必要があります (スキーマの中で、ゲッターで、セッターで、モデルのインスタンス変数で、「from」マッピングで、そして「to」マッピングで)。
  • Rails のマイグレーションでは、データのマイグレーションもスキーマのマイグレーションも行うことができます。
  • Rails のマイグレーションでは、データ・マイグレーションにモデル・ロジックを使うことができますが、SQLスクリプトでは使えません。
  • Rails のマイグレーションはデータベースに対して独立ですが、SQL スクリプトは独立ではありません。
  • Rails のマイグレーションは、サポートされていない拡張 (ストアード・プロシージャーや制約など)に対してダイレクト SQL が使えますが、一部の ORM マッパーでは使えません。

マイグレーションの持つ、こうした利点を見ると、コードは複雑だと思われるかもしれませんが、実は信じられないほど単純なのです。マイグレーションは、意味のある名前とバージョン番号を持っています。各マイグレーションは、upメソッドと down メソッドを持っています。そして最後に、それらが正しい順序で実行されるように、rake タスクが調整します。この単純な手法が、革命的でもあるのです。各スキーマ変更を、モデルの中ではなく明確なマイグレーションとして表現するという概念は、優雅であり、また効果的でもあります。データの変更とスキーマの変更の両方を調整するという概念も、もう1 つの、そして効果的なパラダイム・シフトです。何よりも良いことに、こうした概念は言語をまったく意識しません。もし皆さんが新しいJava ラッピング・フレームワークを構築しようとするのであれば、ぜひマイグレーションを検討してみてください。

このシリーズの次回の記事では、Rails でのメタプログラミングをフルに活用した、Ajaxと Web サービスに対応する新しいフレームワークについて調べることにします。それまで、気持ちをオープンに、境界を越え続けてください。

参考文献

学ぶために

  • Beyond Java (Bruce Tate著、2005年、O'Reilly刊) は、この記事の著者による本です。Java言語の台頭と停滞について、また、一部のニッチな領域でJava プラットフォームに対抗しうる技術について解説しています。
  • ActiveRecord::Migration に関する Rails API のドキュメンテーションは、マイグレーションの最新機能について知るための情報源として好適です。
  • 「Book review: Agile Web Development with Rails」(Darren Torpey 著、developerWorks、2005年12月) は、Rails に関する理解を深め、またアジャイル開発手法の背景を知る上で役立つ書評です。
  • Understanding Migrations を見てください。この Rails Wiki は、マイグレーションの概要を適切に説明しており、またコード以外の最新情報も知ることができます。
  • From Java To Ruby: Things Your Manager Should Know (2006 年、Pragmatic Bookshelf 刊) は、この記事の著者による本です。Javaプログラミングから Ruby on Rails に切り替えることに意味があるのは、いつ、どういった場合なのか、そしてその方法について説明しています。
  • Programming Ruby (2005 年、Pragmatic Bookshelf 刊) も、Rubyのプログラミングに関する本として人気があります。
  • Java technology ゾーンには、Java プログラミングのあらゆる側面を網羅した記事が豊富に用意されています。

製品や技術を入手するために

  • オープンソースの Web フレームワーク、Ruby on Rails をダウンロードしてください。
  • Ruby プロジェクトの Web サイトからRuby を入手してください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology, Web development
ArticleID=219194
ArticleTitle=境界を越える: Rails のマイグレーション
publish-date=08152006