第3回 ActiveRecordを使ったソースコードを読もう

倉貫 義人
松村 章弘
TIS株式会社
SonicGarden

2009/1/28

テーブルの関連

 SKIPのユーザーのデータには、SNSらしく足跡やプロフィールなどが付随しています。これらの足跡やプロフィールの情報は、ユーザーと同じUsersテーブルに保存されているわけではなく、それぞれ別のテーブルで管理されています。

 テーブル同士は、外部キーを持たせることで関連付けています。Usersテーブルの1行に対して、足跡テーブルにはほかのユーザーがアクセスした履歴という形で複数のレコードが残るため、ユーザーと足跡は、1対多の関係になっているといえます。

 このようなテーブル同士の関連も、ActiveRecordでは非常にシンプルに定義することができるようになっています。

 Userモデルを見てみましょう。

27
has_many :tracks, :order => "updated_on DESC", :dependent => :destroy'
app/models/user.rb

 足跡のデータはTracksテーブルに保存され、それを扱うためのActiveRecordのクラスがTrackモデルになります。ユーザー1人に対して複数の足跡があるので、Tracksテーブルのレコードには、誰にとっての足跡かを示すためのuser_idというカラムを持たせています。こうしたデータ構造になっているのであれば、has_manyメソッドを用いて1対多の関連をRubyで表現することができます。

トランザクション

 ActiveRecordでのトランザクション処理の説明のために、SKIPの管理機能にあるユーザー情報のCSVアップロード機能のソースコードを読んでいくことにします。CSVアップロード機能は、CSVファイルに用意したデータを読み込み、一度に多数のユーザーの初期登録を実施するために使われます。

 そこで、「複数のレコードを一度に登録したり、1件でも失敗したりした場合はすべてやり直し」ということで、トランザクション機能を利用しています。

図5 CSVアップロード機能

 それではコードを見ていきましょう。

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def import
  @topics = [[_('Listing %{model}') % {:model => _('user')}, admin_users_path], _('New user from csv')]
  @error_row_only = true
  if request.get? || !valid_file?(params[:file], :content_types => ['text/csv', 'application/x-csv', 'application/vnd.ms-excel'])
    @users = []
    return
  end
  @users = Admin::User.make_users(params[:file], params[:options],   params[:update_registered].blank?)
  import!(@users, false)
  flash[:notice] = _('CSVファイルからのユーザ登録/更新に成功しました。')
  redirect_to admin_users_path
rescue ActiveRecord::RecordInvalid,
       ActiveRecord::RecordNotSaved => e
  @users.each {|user, user_uid| user.valid?}
  flash.now[:error] = _('CSVファイルに不正な値が含まれています。')
end
app/controllers/admin/users_controller.rb

 137行目から始まるimportメソッドが、WebブラウザからポストされたCSVデータをデータベースに登録するアクションです。

 このimportメソッドのポイント部分は、144行目と145行目の処理になります。そこまでは、必要な変数の準備と入力形式のチェックを行っているにすぎません。

 144行目で、Userモデルのmake_usersメソッドを用いて、送られてきたファイルからUserモデルの配列を構築して取得しています。このmake_usersメソッドの内部では、CSVファイルの解析を行い、そのデータを基に、すでに登録済みのユーザーが見つかればそのオブジェクトを、見つからなければ新規のオブジェクトとして、配列を構築しています。

 そして、そのUserモデルオブジェクトの配列を145行目で、引数として渡してimport!メソッドを呼び出しています。このimport!メソッドの中でトランザクションを利用しています。では、privateメソッドであるimport!メソッドの中身を見ることにします。

247
248
249
250
251
252
253
254
255
256
257
def import!(users, rollback = true)
  Admin::User.transaction do
    users.each do |user, user_uid|
      user.save!
      unless user.new_record?
        user_uid.save!
      end
    end
    raise ActiveRecord::Rollback if rollback
  end
end

 トランザクション処理を開始するには、ActiveRecordで用意されたtransactionメソッドを使います。248行目のように、Rubyのブロックの形でトランザクション処理を記述します。

 249行目から256行目の間に、処理に失敗して例外が発生すると、ロールバック処理が実行され、ブロック内のそれまでの処理がなかったことになります。

 250行目、252行目で、save!メソッドを呼んでいます。前述したとおり、この!マークの付いたメソッドは、バリデーションなどの失敗時には例外を発生させるため、トランザクション処理内で使う場合は、必ず!マークの付いたsave!を呼ぶようにします。transactionブロックを抜けるタイミングで、コミット処理が実行されるようになっています。

 今回は、ActiveRecordの機能を中心に、モデルの使い方などを解説してきました。ActiveRecordに備わっている基本的な機能と、便利に使えそうだという可能性は感じてもらえたのではないでしょうか。

 今回紹介した機能は、まだまだActiveRecordのほんの入り口の一部にすぎません。また、SKIPで使っている実際のソースコードということもあり、一般的によく使われるRailsバージョン1系から存在している機能の説明に終始しました。Railsバージョン2系で拡張された、named_scopeや、dirtyオブジェクトなどの便利で面白い機能については、ぜひとも学んでみてください。

 次回は、MVCのうちのビュー(V)の部分を中心に、AjaxのRailsアプリケーションでの実装などを解説する予定です。

4/4
 

Index
ActiveRecordを使ったソースコードを読もう
  Page1
SKIPバージョン1.0リリース!
MVCモデルのM
  Page2
データの検索
データの更新
  Page3
値の検証(バリデーション)
コールバック
Page4
テーブルの関連
トランザクション
Railsコードリーディング 〜scaffoldのその先へ〜

 Ruby/Rails関連記事
プログラミングは人生だ
まつもと ゆきひろのコーディング天国
 ときにプログラミングはスポーツであり、ときにプログラミングは創造である。楽しいプログラミングは人生をより実りあるものにしてくれる
生産性を向上させるRuby向け統合開発環境カタログ
Ruby on Rails 2.0も強力サポート
 生産性が高いと評判のプログラミング言語「Ruby」。統合開発環境を整えることで、さらに効率的なプログラミングが可能になる
かんたんAjax開発をするためのRailsの基礎知識
Ruby on RailsのRJSでかんたんAjax開発(前編)
 実はAjaxアプリケーション開発はあなたが思うよりも簡単です。まずはRuby on Railsの基礎知識から学びましょう
Praggerとnetpbmで作る画像→AA変換ツール
Rubyを使って何か面白いものを作ってみよう!
 一般的な画像をアスキーアートに変換するツールを作ってみる。さらに出力にバリエーションを持たせてみよう
コードリーディングを始めよう
Railsコードリーディング〜scaffoldのその先へ〜(1)
 優れたプログラマはコードを書くのと同じくらい、読みこなす。優れたコードを読むことで自身のスキルも上達するのだ
  Coding Edgeフォーラムフィード  2.01.00.91


Coding Edge フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

>

Coding Edge 記事ランキング

本日 月間