Rails の勉強を始めたが記事が3日坊主にすらなってなく、このままだと K 子先輩 (仮名) に会わせる顔がないので、3日目を書いておく。今日はテーブルのアソシエーションをやってみる。
テーブルのアソシエーション
前回までに作ったあぶない刑事アプリに、scaffold で階級テーブルを追加する。カラムは階級名 (name) と順位 (rank) とする。
$ rails generate scaffold grade name:string rank:integer
$ rake db:migrate
scaffold にアクセスして、データをせっせと登録する。
http://localhost:3000/grades
で、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” は混同しがちなので、わからなくなったときは次のページを参考にすると良い。
- belongs_toとhas_one の違い – WEBデザイン Tips
そして、ビューで今まで 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日目に続く。
コメント
どうも、K 子先輩 (仮名) です。
ページ送りはkaminariがオススメです。
AdventCalendar – kaminari徹底入門 – Qiita [キータ]