備忘録のため,内容の正当性については責任を持ちません。

Rails の勉強を始めたが記事が3日坊主にすらなってなく、このままだと K 子先輩 (仮名) に会わせる顔がないので、3日目を書いておく。今日はテーブルのアソシエーションをやってみる。


テーブルのアソシエーション

前回までに作ったあぶない刑事アプリに、scaffold で階級テーブルを追加する。カラムは階級名 (name) と順位 (rank) とする。

$ rails generate scaffold grade name:string rank:integer
$ rake db:migrate

scaffold にアクセスして、データをせっせと登録する。

http://localhost:3000/grades
grades モデルの scaffold

grades モデルの scaffold

で、policemen の scaffold を再度開く。

http://localhost:3000/policemen

この時点では何の設定もしていないため、当然ながら grade は数値表記のままだ。

修正前

修正前

これをアソシエーションにより grades モデルと紐付けて、階級名を表示するようにする。アソシエーションはモデルで設定する。刑事と階級は1対1なので、”belongs_to” を使う。

$ git diff app/models/policeman.rb
diff --git a/app/models/policeman.rb b/app/models/policeman.rb
index c37df76..89e471d 100644
--- a/app/models/policeman.rb
+++ b/app/models/policeman.rb
@@ -1,3 +1,4 @@
 class Policeman < ActiveRecord::Base
   attr_accessible :birthday, :grade_id, :hometown, :name, :sex
+  belongs_to :grade
 end

※ “belongs_to” と “has_one” は混同しがちなので、わからなくなったときは次のページを参考にすると良い。

そして、ビューで今まで grade_id を表示していたところに階級名を表示するようにする。なんと grade_id を grade.name に書き換えるだけで良い。Rails 賢い。

$ git diff app/views/policemen/index.html.erb
diff --git a/app/views/policemen/index.html.erb b/app/views/policemen/index.html
index ae7bcfc..2b92994 100644
--- a/app/views/policemen/index.html.erb
+++ b/app/views/policemen/index.html.erb
@@ -15,7 +15,7 @@
 <% @policemen.each do |policeman| %>
   <tr>
     <td><%= policeman.name %></td>
-    <td><%= policeman.grade_id %></td>
+    <td><%= policeman.grade.name if policeman.grade %></td>
     <td><%= policeman.sex %></td>
     <td><%= policeman.birthday %></td>
     <td><%= policeman.hometown %></td>

ただし “if policeman.grade” という文を書いておかないと、grade が nil の行を含むとき

undefined method `name' for nil:NilClass

というエラーになってしまうので注意。

再度 policemen の scaffold を再度開くと、ちゃんと表示が切り替わっている。注目すべきは、今日はまだ一度もコントローラのコードに触れていないこと。

修正後

修正後

登録画面と編集画面のフォームも、grade_id を階級名の表記に変えよう。モデルからデータを取り出す記述をコントローラに追記する。このとき、new メソッドと edit メソッドで同じ処理を書くのは冗長なので、”before_filter” を使って、各メソッドが実行される前に共通で呼ばれる処理にする。

$ git diff app/controllers/policemen_controller.rb
diff --git a/app/controllers/policemen_controller.rb b/app/controllers/policemen
index 82b5ec0..c4b5b9f 100644
--- a/app/controllers/policemen_controller.rb
+++ b/app/controllers/policemen_controller.rb
@@ -1,4 +1,10 @@
 class PolicemenController < ApplicationController
+  before_filter :_get_data
+
+  def _get_data
+    @grades = Grade.all
+  end
+
   # GET /policemen
   # GET /policemen.json
   def index

※ メソッド名はもっと推敲した方が良いと思う。

そしてフォームのビューを変更する。”f.select” ヘルパを使って select ボックスを出力するようにする。

$ git diff app/views/policemen/_form.html.erb
diff --git a/app/views/policemen/_form.html.erb b/app/views/policemen/_form.html
index 514bc11..0a70183 100644
--- a/app/views/policemen/_form.html.erb
+++ b/app/views/policemen/_form.html.erb
@@ -17,7 +17,7 @@
   </div>
   <div class="field">
     <%= f.label :grade_id %><br />
-    <%= f.number_field :grade_id %>
+    <%= f.select :grade_id, @grades.map{|t| [t.name, t.id]}, :include_blank => true %>
   </div>
   <div class="field">
     <%= f.label :sex %><br />

編集画面にアクセスしてみると、ちゃんと select ボックスになっている。

修正後のフォーム

修正後のフォーム

このようにしてテーブルのアソシエーションが設定できた。


外部テーブルの値でソート

さらにテーブルに適当なデータを追加しておく。それで、外部テーブルの値、例えば今回なら階級の偉い順 (grades テーブルの rank) で並び替えたいときはどうすれば良いだろうか。

まずは素直に、コントローラに order を書いてみる。

$ git diff app/controllers/policemen_controller.rb
diff --git a/app/controllers/policemen_controller.rb b/app/controllers/policemen
index c4b5b9f..0af5137 100644
--- a/app/controllers/policemen_controller.rb
+++ b/app/controllers/policemen_controller.rb
@@ -8,7 +8,7 @@ class PolicemenController < ApplicationController
   # GET /policemen
   # GET /policemen.json
   def index
-    @policemen = Policeman.all
+    @policemen = Policeman.all(:order => "grades.rank DESC")

     respond_to do |format|
       format.html # index.html.erb

一覧画面にアクセスしてみるものの、エラーになる。

エラー画面

エラー画面

エラーメッセージと SQL を見てみると、

SQLite3::SQLException: no such column: grades.rank: SELECT "policemen".* FROM "policemen"  ORDER BY grades.rank DESC

となっており、grades.rank なんて列はないと言っていることがわかる。どうやらこの時点では grades テーブルと結合されていないようだ。

それでどうすれば良いかと言うと、”includes” という記述を書けば良いらしい。

$ git diff app/controllers/policemen_controller.rb
diff --git a/app/controllers/policemen_controller.rb b/app/controllers/policemen
index c4b5b9f..ebe0794 100644
--- a/app/controllers/policemen_controller.rb
+++ b/app/controllers/policemen_controller.rb
@@ -8,7 +8,7 @@ class PolicemenController < ApplicationController
   # GET /policemen
   # GET /policemen.json
   def index
-    @policemen = Policeman.all
+    @policemen = Policeman.includes(:grade).all(:order => "grades.rank DESC")

     respond_to do |format|
       format.html # index.html.erb

再度アクセスしてみると、意図したソートがされている。

ソートされたデータ

ソートされたデータ

こんな感じで、ちょっとしたデータ集アプリ程度なら、本当に必要最小限のコーディングで実現できることがわかった。今後は下記のようなことをやってみたい。

  • ページ送り
  • Heroku へのデプロイ

4日目に続く。

コメント

コメント(1) “Ruby on Rails 入門 (3日目)”

  1. K 子先輩 (仮名)

    どうも、K 子先輩 (仮名) です。

    ページ送りはkaminariがオススメです。

    AdventCalendar – kaminari徹底入門 – Qiita [キータ]

コメントする




CAPTCHA