Railsでのクラス設計とDB構造。ORマッピング

Ruby on Railsの特徴の一つとして、データベースのテーブルと、WEBアプリケーションのクラス構造が、自動的にマッピングされます。そのおかけで、アプリケーションを開発する際に、DBアクセスのためのModelメソッドをほとんど記述する必要がありません。
MVCフレームワーク内で、ORM( Object/Relational Mapping )を実現しているのですが、その恩恵を受けるためには、Railsの作法に則ったDBの使い方をすることがコツのようです。その辺りを、Rails 解説書をもとにメモ。

Railsでの、基本的なMySQLの設定と、Railsプロジェクトの作成は以下の記事を参照。

Ruby on Rails の MySQL設定 Mac OSX

データベースの作成

まずは、データベースのTable作成から。ホーム以下に、Railsというディレクトリを作成しているので以下の場所(tagsはRailsのプロジェクト名)。DBのテーブルを作成するSQL文は、こちらに置いておきます。

Rails/tags/db/create.sql

create.sqlの中身は、こんな感じのSQL文がテーブルの数だけあります。ここでのポイントは、二つあります。

最初は、テーブルの名前のつけかた。アプリケーション内で利用したいクラス・オブジェクト名の複数形をテーブルの名前にします。例えば、WEBアプリケーションで「tag」というクラス・オブジェクトを使いたい場合は、DBのテーブル名を「tags」にしておきます。英語ネイティブの人には直感的なのでしょうが、我々日本人はよく複数形を忘れるので(w。

二つ目は、PRIMARY KEYになるカラムを「id」という名前で作成しておくこと。「tag_id」とかにしてしまうと、Railsアプリケーションで、このテーブルをうまく扱うことができなくなります。とりあえず、アプリケーションから検索したいテーブルのPRIMARY KEYになるカラムは、「id」にしておくのが無難。

Drop table if exists tags;
CREATE TABLE tags (
  id MEDIUMINT unsigned NOT NULL AUTO_INCREMENT,
  name varchar(100) NOT NULL,
  PRIMARY KEY(id),
  UNIQUE (name),
);
テーブルを作成するには、ファイルからSQLを実行
$ mysql tags_development <db/create.sql

テスト(tags_test)や、本番用(tags_production)のデータベースも忘れずにcreateしておきます。

Rails Modelの作成

作成したデータベースのテーブルには、Railsのgenerateコマンドで、自動生成されるアプリケーションの雛形(saffold)からアクセスできます。アプリケーションの雛形を作成するのは以下のコマンド。この時に注意するポイントは、generateする雛形の名前。ここでは、クラス・オブジェクトの名前になるので「単数形」になります。
すでに作成したDBのテーブルは「tags(複数形)」でした。このテーブルにORマッピングされるオブジェクトは、「tag(単数形)」になります。

Modelを作成するコマンド

$ ruby script/generate model Tag

scaffoldを作成するコマンド

$ ruby script/generate scaffold Tag

ずらずら〜と、create文でアプリケーションの雛形が作成されます。そして、ここで作成した雛形アプリケーションにアクセスするには、以下のURL。プロジェクト名とTable名が同じになってしまったので、まぎらわしいですが、「http://localhost/プロジェクト名/オブジェクト名の複数形」ですね。
http://localhost/tags/tags/

そして、WEBブラウザに表示される「New Tag」をクリックすると、自動生成されたフォーム入力画面からデータベースに登録することが可能です。登録されたDBのカラムに対しては、MVCのコントローラーであるTagsControllerから、 @tags や @tagというオブジェクトとして利用が可能です。

class TagsController < ApplicationController
  def index
    list
    render :action => 'list'
  end

  def list
    @tag_pages, @tags = paginate :tag, :per_page => 10
  end

  def show
    @tag = Tag.find(params[:id])
  end

RailsにおけるORマッピングと外部制約

解説書によれば、Railsではテーブル間の親子関係にあたる外部制約にも対応するようです。親子関係は、以下のような形で表現されるそう。

Agile Web Development with Rails

rails_orm.png

例えば、itemというオブジェクトに対して、複数のreviewが存在する場合、例として以下のようなTable構造があったとして、

CREATE TABLE items (
  id MEDIUMINT unsigned NOT NULL AUTO_INCREMENT,
  title varchar(255) NOT NULL,
  uri varchar(255) NOT NULL,
  PRIMARY KEY(id),
  UNIQUE (uri)
);

CREATE TABLE reviews (
  id MEDIUMINT NOT NULL AUTO_INCREMENT,
  item_id MEDIUMINT unsigned NOT NULL,
  PRIMARY KEY(id),
  CONSTRAINT fk_reviews_item FOREIGN KEY (item_id) REFERENCES items(id)
);

この場合は、app/model/review.rbに以下のように記述する

class Reveiw < ActiveRecord::Base
  belongs_to :item
end

この外部制約とクラスの親子関係が、アプリケーション内でどのようにハンドルされるのかは、まだ勉強不足でよくわかりません。itemをDeleteしたときに、関連するreviewも削除するとかできるのかな?

なんとなく、まとめ

こんな感じで、Railsではデータベースのスキーマと、アプリケーション内のクラス設計が非常に緊密な関係にあります。そのために、アプリケーションをつくる過程で、何度もDBのテーブル構造を変えたくなってしまいます。ただ、generateコマンドによる雛形の自動生成が可能なので、スキーマ修正の手間は軽減されています。

いままでは、最初に色々と考えてテーブルを作成するものの、一度DBアクセス用のモデル層を作成してしまうと、どうしても修正が面倒になってしまっていました。そのために、アプリケーションのクラス構造のほうに、ゴニョゴニョと手を入れて、完成する頃には、同じデータを表しているのに、DBとクラスが違う構造になっていることもよくありました。

Railsは、確かに手間を軽減してくれるフレームワークでもあるのですが、僕のような週末プロブラマーにとっては、「ORマッピングって、こういうことだったのね」というような、色々な気づきがあるので、「やってて面白い感」が強いです。