ソフトウェアの一覧表示でキーワードを表示

一覧表示でKeyword_id列を表示したい。

キーワードテーブルを関連付ける下準備は全て終わった...。ここでソフトウェアの一覧表示にkeyword_id列が表示されているのを期待して、アクセスしてみる。created_on、updated_onの時は、フィールドを追加しただけで表示されたのだから、今回も当然、表示されると考えたのだ。
しかし、webブラウザではkeyword_idの列はどこにも見当たらない...。念のため、CocoaMySQLでテーブルの状態を確認してみると、keyword_idフィールドはちゃんと登録されている。

よくよく考えてみたら、idフィールドだって表示されてない...。list.rhtnlで描画部分のコードを確認してみると...

ビュー list.rhtml
...(途中省略)...
  <% for column in Software.content_columns %>
    <th><%= column.human_name unless column.name == 'url'%></th>
  <% end %>
...(途中省略)...
  <% for column in Software.content_columns %>
    <td><%= link_to_if column.name == 'title',
                       h(software.send(column.name)), 
                       URI.encode(software.send('url')) unless column.name == 'url'  %></td>
  <% end %>
...(途中省略)...

列タイトルも、その項目の値も、どちらもSoftware.content_columnsで、Softwareモデルのcontent_columnsメソッドを呼び出して、フィールドを取り出している。きっと、content_columnsメソッドではidや_idで終わるフィールドは無視される仕様なのかもしれない...。
そこで、自分でタイトルと項目の値の描画コードを1行ずつ追加した。

ビュー list.rhtml
<h1>Listing softwares</h1>

<table>
  <tr>
  <% for column in Software.content_columns %>
    <th><%= column.human_name unless column.name == 'url'%></th>
  <% end %>
    <th>Keyword</th>
  </tr>
  
<% for software in @softwares %>
  <tr>
  <% for column in Software.content_columns %>
    <td><%= link_to_if column.name == 'title',
                       h(software.send(column.name)), 
                       URI.encode(software.send('url')) unless column.name == 'url'  %></td>
  <% end %>
    <td><%=h software.keyword_id %></td>
    <td><%= link_to 'Show', :action => 'show', :id => software %></td>
    <td><%= link_to 'Edit', :action => 'edit', :id => software %></td>
    <td><%= link_to 'Destroy',
                    { :action => 'destroy', :id => software },
                      :confirm => "#{software.title} is deleted. Are you sure?",
                      :post => true %></td>

すると、こんな感じでKeyword列が表示された。フィールドを追加したばかりなので、キーワード列の中身は何も無いが...。

キーワードを選択して入力したい。

今度は、新規作成や編集ページでキーワードをプルダウンリストから選択して入力するようにしたい。
入力フォームの描画は、_form.rhtnlで処理しているので、以下のようにコードを追加した。

ビュー _form.rhtml
...(途中省略)...
<p><label for="software_keyword_id">Keyword</label><br/>
<%= collection_select :software, :keyword_id, Keyword.find(:all), :id, :name %></p>

重要なのは、collection_selectの構文。第1引数、第2引数は、text_fieldやtext_areaと同じ意味合い。自分としては、以下のように理解した。

要素 実例 意味合い
第1引数 :software コントローラから渡されるインスタンス変数@softwareを対象にする。
第2引数 :keyword_id @softwareが示すkeyword_idフィールドが対象になる。
第3引数 Keyword.find(:all) キーワードテーブルからすべてを抽出したレコードの固まりを設定する。
第4引数 :id keyword_idと連動するフィールド。idフィールドの値が、keyword_idフィールドに設定される。
第5引数 :name プルダウンリストに表示するフィールド。nameフィールドの値が表示される。

これで、キーワードを選択して表示できるようになった。こんな感じ。

しかし、一覧表示で見るとkeyword_idの値が数値として見えている。

一覧表示でちゃんとキーワードを表示したい。

本当に見たいのは、その値が示すキーワードテーブルのnameフィールドの内容だ。そこで以下のようにlist.rhtmlを修正した。

<td><%=h software.keyword.name %></td>

software.keyword_idとなっている箇所を、software.keyword.nameに修正した。_idが、.nameに置き換わっただけ。
これで、keyword_idの値をキーに、キーワードテーブルのnameフィールドの値を取り出せるはず...。が...、しかし、 またもやエラー発生。

このエラーには結構はまった...。

  • 何か1文字、入力を間違ったかと思い(また複数形のsとか...)、注意深く確認したが問題なし。
  • サーバーがおかしいのかと思い、何度かServersタブで再起動。しかしエラーは直らず。
  • しまいには、パソコンがおかしいのかと思いMacBookまで再起動。でもエラーは直らず。

本日は諦めようと思ったその時に、何かが閃いた。そう、おかしいのはサーバーでもパソコンでもない、自分の設定したデータベースの値だった...。

You have a nil object when you didn't expect it!
The error occured while evaluating nil.name

よく見ればエラーメッセージの中にも書いてあるじゃないか...。nil object、nil.nameにもっと早く気付けばよかった...。
つまり、途中からkeyword_idフィールドを追加したので、項目の内容が何もない状態のデータ(nilと呼ばれる値)がkeyword_idフィールドに設定されていて、その状態でsoftware.keyword.nameメソッドを呼び出すと、何もないものに対する呼び出しのためエラーになっていたのだ。
編集ページから、全部のソフトウェアにキーワードを設定してあげれば済むのだが、今後のことも考えて、以下のようにコードも修正した。

ビュー list.rhtml
<td><%=h software.keyword.name unless software.keyword_id == nil %></td>

keyword_idがnilでない時だけ、software.keyword.nameを処理するようにした。

モデル software.rb
validates_presence_of :title, :description, :url, :keyword_id
validates_numericality_of :keyword_id

keyword_idは、入力されていること、数値であることを検証するようにした。

これで、一覧表示でもちゃんと表示できるようになった!キーワードの設定が無くても大丈夫!