マイグレーションは、データベースのスキーマの変更をプロジェクトのレベルで管理するもので、スキーマを複数の開発者の間で共有することを助けてくれます。具体的には、データベースのスキーマに変更がある度に、その変更をデータベースに反映するためのRubyコードを記述します。また、その変更を取り消すためのコードも同じマイグレーションファイル内に記述します。どの変更が実際にDBに反映されているかの状態はschema_migrationsテーブルに保持されます。これによって、ほかの開発者が新しいスキーマ変更処理を取り込んだり、任意のバージョンまで状態を戻したりすることができるようになります。
複数の開発者の間で、どのようにマイグレーションが利用されるかのイメージを図にすると、次のようになります。まず、スキーマへの変更が発生したら、対応するマイグレーションファイルを作成して、自分の開発環境に反映するとともに、ソースコードを管理するためのレポジトリに追加します。別の開発者は、新しく追加されたマイグレーションファイルをレポジトリから取得し、自分の開発環境でマイグレーションを実行して、変更をデータベースに反映することができます。
マイグレーションを開発環境に反映させるには、rakeコマンドを用います。なお、オプションなどについては、本連載の第1回の解説を参照してください。
> rake db:migrate
それでは、マイグレーションファイルの内容を見ていきましょう。
先ほどのUserクラスの例では、rails gコマンドで次のようなマイグレーションファイルが生成されます。このマイグレーションファイルは、「usersというテーブルを作成する」というスキーマ変更に対応するとともに、「20110403005910」というスキーマバージョンを表しています。なお、このバージョンの数字は、マイグレーションファイルをコマンドで生成する際に日時をもとに自動で付与されます。
class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :name t.string :email t.date :birthday t.integer :number t.timestamps end end def self.down drop_table :users end end
マイグレーションファイルの内容は、upとdownの2つのメソッドで構成されます。upにはデータベースにこの変更を反映する(このスキーマバージョンに上げる)処理を、downにはこの変更を取り消す(前のバージョンに戻す)処理を記述します。この例では、upではcreate_tableというメソッドを使ってテーブルを作成し、downではそのテーブルを削除します。
create_tableに渡すブロックの中では、引数tに対して、t.string :name のようにカラムを記述していきます。このように、マイグレーションでは、「VARCHAR(20)」のようなデータベースのデータ型を直接具体的に指定するのではなく、抽象化されたデータ型を用います。基本的には以下のデータ型が使用できます。
このような抽象化されたデータ型を使うことには、シンプルで覚えやすく、データベースに詳しくなくても簡単に開発できるというメリットがあります。さらに、データベースの種類ごとの差異を吸収してくれるため、利用するRDBMSを変えてもコードにほとんど手を加えずに済むという効果もあります。
ちなみに、上記のマイグレーションを、デフォルトのsqlite3で実行すると、次のようなテーブルが作成されます。stringがvarchar(255)にマッピングされているのが分かりますね。
文字列の長さは、:limitオプションを指定することで変えることができます。
t.string :name, :limit => 20
また、:decimal型のカラムには :precision と :scale で精度とスケールを設定できます。
このほか、デフォルト値を設定したり、NULL不可かどうかを指定するには次のようにします。
t.string :name, :default => 'unknown' t.integer :number, :null => false
このほか、create_table の記述について特筆すべき点としては、プライマリキーとタイムスタンプが挙げられます。続いてこれらについて説明していきます。
ActiveRecordでは、テーブルは基本的にidという名前の自動発番のプライマリキーを持つという想定になっており、create_tableの記述で特に何も書かなければそのようにスキーマが作成されます。もしも規約に外れたテーブルを作りたければ、:idオプションでプライマリキーの有無を、:primary_keyオプションでプライマリキーの名前を変えることができます。例えば次の例ではプライマリキーの名前をkaiin_idにしています。
create_table :users, :primary_key => :kaiin_id do |t| ...略... end
なお、テーブルのプライマリキーの名前が何であるかに関わらず、ActiveRecordオブジェクトではプライマリキーに対応する属性名としてidが使われます。
Userクラスの例では、マイグレーションのcreate_tableブロックの最後に次のような記述があります。
t.timestamps
これは、テーブルにタイムスタンプ用のカラムを追加するという意味で、具体的にはcreated_atとupdated_atが追加されます。これらのカラムはActiveRecordでは特別な意味を持っています。新しいActiveRecordオブジェクトがはじめてDBに保存されたときにはcreated_atとupdated_at、更新されたときにはupdated_atに、自動的にその時点の日時がセットされます。また、created_on、updated_onという日付型のカラムがあれば、日時ではなく日付を保存します。これらの動作は、こういった名前のカラムがあれば自動的に行われます(従って、timestampsメソッドではなくadd_columnなどでこれらのカラムを追加しても同じ効果が得られます)。
テーブルの作成/削除のほか、マイグレーションでは、既存のテーブルに対するカラムの操作をしたい場面もよくあります。カラム操作のためのメソッドとしては以下が用意されています。
また、インデックスに関するメソッドも用意されています。
マイグレーションでは、executeメソッドでSQLを直接実行することもできるので、用意されたメソッドでは実現できないような処理も行うことができます。
ここまでは、ActiveRecordの基本的な考え方や、マイグレーションについて解説しました。ここからは、個別の機能について詳しく見ていきます。更新系や複雑な処理については次回以降に譲り、今回はActiveRecordを使ったデータの検索方法について見て行くことにします。
Rails3からActiveRecord内部でArelというライブラリが使われるようになり、以前と比べてAPIが大きく変わっています。Arelとは、データベースクエリを生成するためのライブラリで、プログラムとしてより柔軟に検索条件を組み立てることができるようになっています。
例えばRails2では検索の際、以下のようにしていました。
User.all(:conditions => [‘age = ?’, 31])
Rails3のArelを使ったやり方では以下のようになります。
User.where(:age => 31)
また、「order by」や「limit」を指定する場合、Rails2では以下のようしていました。
User.all(:conditions => [‘age = ?’, 31], :order => ‘created_at desc’, :limit => 20)
Rails3では以下のようになります。
User.where(:age => 31).order(‘created_at desc’).limit(20)
次のように、各条件を順番に適用していくといったやり方もできます。
users = User.where(:age => 31) users = users.order(’created_at desc’) users = users.limit(20)
User.where(:age => 31)としたときに何が起きるのでしょうか。User.where(:age => 31).classとすると、返ってくるオブジェクトがActiveRecord::Relationというクラスのインスタンスであることが分かります。
> User.where(:age => 31).class => ActiveRecord::Relation
さらに、User.where(:age => 31).limit(20)としたときに返ってくるのもActiveRecord::Relationオブジェクトです。
> User.where(:age => 31).limit(20).class => ActiveRecord::Relation
つまりwhereやlimitなどのメソッド呼び出しによって連鎖的にActiveRecord::Relationオブジェクトが作られているのです。また、ActiveRecord::Relationオブジェクトを作っただけではデータベースに対して検索は行われません。例えば以下のようにしてデータを利用しようとしたタイミングで初めて検索が行われます。
User.where(:age => 31).limit(20).each do |user| p user end
検索方法について詳しく見て行きましょう。
rails consoleなどを使って手元の環境で試す場合、to_sqlメソッドが役に立ちます。to_sqlメソッドを使うとActiveRecord::Relationがデータベースに対して実行するSQLを確認することができます。使い方は以下の通りです。
> puts User.where(:age => 31).limit(20).to_sql SELECT "users".* FROM "users" WHERE "users"."age" = 31 LIMIT 20
プライマリキーによる検索にはfindを使います。
> User.find(1) => #<User id: 1, name: "jugyo", …>
データが存在しなかったときは例外(ActiveRecord::RecordNotFound)が発生します。
firstで最初のデータを1件取得することができます。
User.first => #<User id: 1, name: "jugyo", …>
同様に、lastで最後のデータを取得することができます。
User.last => #<User id: 3, name: "jugyo3", …>
first、lastともに、データが存在しなかったときはnilが返ります。
findの引数にはidを複数指定することができます。
User.find(1, 2) => [#<User id: 1, name: "jugyo", …>, #<User id: 2, name: "jugyo2", …>]
すべてのデータを取得するにはallを使用します。
User.all.each do |user| … end
allはすべてのデータを一度に取得します。上記のコードを実行した場合、以下のSQLが実行されます。
SELECT "users".* FROM "users"
テーブルに大量のデータがある際にそれらを一定数ずつ取り出して処理を行いたい場合はfind_eachを使用します。
User.find_each do |user| … end
find_eachを使った場合は以下のSQLが実行されました。
LIMITを指定して1000件ずつデータを取得しようとしていることが分かります。:batch_sizeオプションを使って一度に取得するデータの件数を指定することもできます。以下の例では5000件ずつ取得しています。
User.find_each(:batch_size => 5000) do |user| … end
検索条件を指定するにはwhereを使います。Userモデルからageが31のデータを検索する場合は以下のようにします。
User.where(:age => 31)
実行されるSQLは以下のようになります。
SELECT "users".* FROM "users" WHERE "users"."age" = 31
以下のようにしてageに複数の値を指定することもできます。
User.where(:age => [31, 32])
このとき実行されるSQLは以下のようになります。
SELECT "users".* FROM "users" WHERE "users"."age" IN (31, 32)
whereには、SQLの一部となるような文字列を直接指定することもできます。また、その際はプレースホルダーを使って値を埋め込むことができます。
User.where('age = ? AND name = ?', 31, 'jugyo')
なお、文字列を指定するときに、プレースホルダーを使わずに #{} などを使って直接データを埋め込むと、データにSQLインジェクションの危険性がある場合には問題となります。危険性があるデータを文字列に埋め込んで指定したい場合は、必ずプレースホルダーを使うべきです。
プレースホルダーとして“?”の代わりにキーワードを使うこともできます。
Rangeオブジェクトを使って範囲指定を簡単に表現することができます。
User.where(:age => 30..40)
このとき実行されるSQLは以下のようになります。
SELECT "users".* FROM "users" WHERE ("users"."age" BETWEEN 30 AND 40)
この他にもSQLの各種操作に対応した以下のようなメソッドが定義されています。
上記の方法以外にもダイナミックファインダーと呼ばれる機能を使って検索を行うこともできます。
例えば、以下のような検索を行いたい場合、
User.where(:name => 'jugyo').first
ダイナミックファインダーを使うとこのように書けます。
User.find_by_name('jugyo')
以下のように複数の条件を組み合わせることもできます。
User.find_by_name_and_age('jugyo', 31)
検索条件によってはダイナミックファインダーのほうがより分かりやすく書けます。状況に応じて使い分けると良いでしょう。
ダイナミックファインダーは、あらかじめメソッドが用意されているわけではなく、呼ばれたときにはじめて使えるようになります。これは、Rubyの動的言語としての特性が大変うまく生かされている例といえます。どのように実装されているのか調べてみると面白いかもしれません。
ActiveRecordでは、whereなどを用いて検索条件を記述するだけでなく、特定の検索条件に自分で好きな名前を付けてメソッドとして使うことができます。これはスコープと呼ばれます。
例えば、有料会員を示すspecialフラグがusersテーブルにあるとします。有料会員のみを検索する通常の書き方は次のようになります。
special_users = User.where(:special => true)
しかし、次のように書ければソースコードはさらに読みやすくなるでしょう。
special_users = User.special
これは、以下のようなスコープをUserクラスに記述することで可能になります。
class User < ActiveRecord::Base scope :special, where(:special => true) end
スコープは、重ねて呼ぶことができます。例えば、女性の有料会員を次のように絞り込むことができるようになります。とても読みやすくなりますね!
special_users = User.special.women
連載第3回となる本記事では、Ruby on Railsのモデル層の標準の実装であるActiveRecordについて、基本的な考え方や操作方法、使い始めるために必要となるマイグレーションの知識、そして参照機能について、主に検索条件の組み立て方に焦点を当てて解説しました。ActiveRecordの役割や使い方に親しんでもらうとともに、データベースの検索を驚くほど柔軟に読みやすく記述できる魅力を実感していただけたなら幸いです。次回は、更新系の機能や値の検証の仕組みについて詳しく紹介していきます。
Copyright © ITmedia, Inc. All Rights Reserved.