Four Days on Rails by NetBeans – Day 3
Day 2に引き続き、Four Days on Rails(http://rails.homelinux.org/)のDay 3もNetBeansのRuby Packを使って試してみましたが、Day 3は難関でした。急にコード量は増えるし、意味を理解できない(RubyもRailsもほとんど勉強せずにいきなりFour Days…を始めたので (^^;; )ところは多いし、手入力したところにtypoがあって動かなかったりとたいへんでした。でも、NetBeansのデバッガを使ってなんとかひととおり動いていそうなところまでこぎつけました。これでいいのかどうかやや疑問ですが、とりあえずDay 3をまとめておこうと思います。
- Itemsモデルを作る
- - Projectsウィンドウへ移動 -> ToDoプロジェクトで右クリック -> Generate…を選択 -> Generate: modelを選択 -> Argumentsにitemと入力 -> OK

- 以上の操作で次のようにコードが自動生成されます。

- - ToDoプロジェクト/Database Migrations/migration以下に生成された002_create_items.rbを開いて、テーブルを定義します。
-
class CreateItems < ActiveRecord::Migration def self.upcreate_table :items do |t|t.column :done, :integer, :null => false, :default => 0t.column :priority, :integer, :null => false, :default => 3t.column :description, :string, :limit => 40, :null => false, :default => ''t.column :due_date, :date, :null => true, :default => nilt.column :category_id, :integer, :null => false, :default => 0t.column :note_id, :integer, :null => true, :default => nilt.column :private, :integer, :null => false, :default => 0t.column :created_on, :timestamp, :null => false t.column :updated_on, :timestamp, :null => false end end def self.down drop_table :items end end
- - Projectsウィンドウへ移動 -> ToDoプロジェクトで右クリック -> Migrate Databaseを選択 -> To Current Versionを選択するとitemsテーブルが作られます。
- - 生成されたitem.rbにデータベースのテーブルcategoryとこれから作るnoteとの関係を表す belongs_toメソッドと各入力パラメータが適切であるかを検証するvalidates_xxxxxメソッドを書きます。さらにitemが削除されたときに参照元のないnoteが残ってしまうのを避けるために before_destroyメソッドを定義します。
-
class Item < ActiveRecord::Base belongs_to :categorybelongs_to :notevalidates_associated :categoryvalidates_format_of :done_before_type_cast,:with => /[01]/, :message => "must be 0 or 1"validates_inclusion_of :priority,:in => 1..5, :message => "must be between 1 (high) and 5 (low)"validates_presence_of :descriptionvalidates_length_of :description, :maximum => 40validates_format_of :private_before_type_cast, :with => /[01]/, :message => "must be 0 or 1" def before_destroy unless note_id.nil? Note.find(note_id).destroy end end end

- 上記のコードのうち、belongs_toはActiveRecord::Associations::ClassMethodsモジュールのメソッドでhttp://api.rubyonrails.org/で詳細を確認できます。このモジュールにはテーブル間の参照関係を表現するメソッドがありました。また、validates_xxxはActiveRecord::Validations::ClassMethodsモジュールのメソッドです。検証に使用している done_before_type_cast, private_before_type_castはattributes_before_type_castのメソッド名でAPIドキュメントに出ていました。ActiveRecord::Baseクラスのメソッドです。Railsの場合、カラム名=atttributeと考えていいようですね。どこからも参照されないnoteが残らないために定義しているbefore_destroyはActiveRecord::Callbacksモジュールのメソッドです。
- Notesモデルを作る
- - Projectsウィンドウへ移動 -> ToDoプロジェクトで右クリック -> Generate…を選択 -> Generate: modelを選択 -> Argumentsにnoteと入力 -> OK

- 以上の操作で次のようにコードが自動生成されます。

- - ToDoプロジェクト/Database Migrations/migration以下に生成された003_create_notes.rbを開いて、テーブルを定義します。
-
class CreateNotes < ActiveRecord::Migration def self.upcreate_table :notes do |t|t.column :more_notes, :text, :null => falset.column :created_on, :timestamp, :null => falset.column :updated_on, :timestamp, :null => falseendend def self.downdrop_table :notesendend
- - Projectsウィンドウへ移動 -> ToDoプロジェクトで右クリック -> Migrate Databaseを選択 -> To Current Versionを選択するとnotesテーブルが作られます。
- - 生成されたnote.rbにnoteが削除されたときに、そのnoteを参照しているitemからもnoteへの参照を削除するためにbefore_destroyメソッドを定義します。note.rbには
validates_presense_of :more_notesも書き込みますが、このmore_<attributes>のルールの説明がどこに書いてあるのかは見つけられませんでした。 -
class Note < ActiveRecord::Base validates_presence_of :more_notes def before_destroyItem.find_by_note_id(id).update_attribute('note_id', NIL)endend 
- ItemsとNotesそれぞれのScaffoldを作成する
- Viewのカスタマイズ – 全てのページに共通のレイアウトを作る
- - ToDoプロジェクト/Views/layouts以下の全てのファイルをShiftキーを押しながらクリックして選択->右クリック -> Delete -> Yes
- -上記の操作でlayouts以下の全てのrhtmlファイルを削除して、次のようにして新たにapplication.rhtmlファイルを作成します。
- layoutsで右クリック -> New -> RHTML file… -> File Nameにapplicationと入力(拡張子は入力しません) ->Finish


- ただし、この方法で作ったrhtmlファイルには最低限のタグしか入っていないので、どれかrhtmlファイルを一つを残しておいて、Renameした方が便利でしょう。
- -application.rhtmlを作りますが、rhtmlファイルにJavaScriptを入力すると、このようにエラーになってしまいます。JavaScriptだけfocus.jsという名前にして、ToDoプロジェクト/Public/javascripts以下に作りました。

-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head><meta http-equiv="content-type" content="text/html;charset=UTF-8" /><title><%= @heading %></title><%= stylesheet_link_tag 'todo' %><script type="text/javascript" src="/javascripts/focus.js"></script> </head> <body onload="setFocus()"> <h1><%= @heading %></h1> <p style="color: green"><%= flash[:notice] %></p> <%= yield %> </body> </html>
-
function setFocus() { if (document.forms.length > 0) {var field = document.forms[0];for (i=0; i lt; field.length; i++) {if ((field.elements[i].type == "text")|| (field.elements[i].type == "textarea") || (field.elements[i].type.toString().charAt(0) == "s")) { document.forms[0].elements[i].focus(); break; } } } } 

- JavaSciptを.jsファイルに書いたら、このようにcode completionが動きました。便利かも。

- -スタイルシートの名前をディフォルトの scaffold.cssからtodo.cssに変更します。
- ToDoプロジェクト/Public/stylesheets以下にある scaffold.cssを選択して右クリック -> Rename -> New Nameにtodoを入力(拡張子は書きません) -> OK

- スタイルシートのcode completionもこのようにsuggestionは表示されるのですが、選択してreturnキーを押してもエディタ上に補完されませんでした。今のところ、間違えていないかどうかの参考にできる程度のようです。ただ、Preview画面にもエディタ上にも、どのようなテキストになるのかが見えるのは便利です。


- Viewのカスタマイズ – To Do Listテーブルのヘッダとテーブル以外の部分を作る
- To Doのリストをテーブル形式で表示しますが、タイトル、テーブルのヘッダ、”New To Do,” “Categories”の2つのボタン、複数ページある場合のナビゲーションをlist.rhtmlに記述します。このファイルではdeprecatedになってしまったメソッドなどを使っているので、わかる範囲で今のバージョンに合わせました。なお、何がdeprecatedになったのかの情報はhttp://www.rubyonrails.org/deprecationにあります。
-
<% @heading = "To Do List" %><% form_tag :action => 'new', :id => @item do%><table><tr><th><%= link_to(image_tag("done.png"), {:action => 'purge_completed'}, :confirm => 'Are you sure tou want to permanently delete all complete To Dos?') %></th> <th><%= link_to(image_tag("priority.png", :alt => "Sort by Priority"), {:action => 'list_by_priority'}) %></th> <th><%= link_to(image_tag("description.png", :alt => "Sort by Description"), {:action => 'list_by_description'}) %></th> <th><%= link_to(image_tag("due_date.png", :alt => "Sort by Due Date"), {:action => 'list'}) %></th> <th><%= link_to(image_tag("category.png", :alt => "Sort by Category"), {:action => 'list_by_category'}) %></th> <th><%= show_image "note" %></th> <th><%= show_image "private" %></th> <th> </th> <th> </th> </tr> <%= render :partial => "list_stripes", :collection => @items %> </table> <hr/> <%= submit_tag "New To Do..." %> <%= submit_tag "Categories...", {:type => "button",
nClick => "parent.location='" + categories_url + "'" } %>
<% end %>
<%= "Page: " + pagination_links(@item_pages, :params => {
:action => @params["action"] || "index" }) + "<hr />" if @item_pages.page_count>1 %> 
- list.rhtmlで参照しているメソッドのうち、purge_completed, list_by_priority, list_by_description, list, list_by_categoryはitems_controller.rbで定義する(してある)メソッド、show_imageはapplication_helper.rbで定義するメソッドです。また、url_forはdeprecatedとなったメソッドのようなので、代わりにnamed routesを使いました(url_forメソッドは4種類あって、ここで使われているurl_forがdeprecatedなのかはわかりませんでしたが、ここではnamed routesを使ってみました)。ToDoプロジェクト/Configuration以下にあるroutes.rbにurlの名前を定義して、その名前に_urlを付けて参照します。この場合は categories_url になります。詳細はActionController::Routingモジュールにあります。
-
map.categories 'categories/list', :controller => 'categories', :action => 'list'
- Viewのカスタマイズ – 各ボタンの操作を定義する
- Itemのscaffoldを作成したときに自動生成されたitems_controller.rbにpurge_completed, list_by_priority, list_by_descriptionの各メソッドを追加します。これはテーブルのヘッダ部分にある各クリッカブルイメージに対応するメソッドです。
-
(excerpt) def purge_completedItem.destroy_all "done = 1"redirect_to :action => 'list' end def list_by_priority @item_pages, @items = paginate :items, :per_page => 10,
rder_by => 'priority, due_date'
render_action 'list'
end def list_by_description
@item_pages, @items = paginate :items, :per_page => 10,
rder_by => 'description, due_date'
render_action 'list'
end - list_by_categoriesは以外と難しいらしく、Railsらしい方法でできずSQLでなんとかした例が本文の最後、39ページに出ていましたが、ここではやっていません。
- New To Doの画面と、Editの画面ではCategoryをドロップダウンメニューから選べるようにします。ドロップダウンに表示する各項目をあらかじめ取得しておく必要があるので、newとeditメソッドにそれぞれつぎのように1行追加します。
-
def new @categories = Category.find_all @item = Item.new enddef edit @categories = Category.find_all @item = Item.find(params[:id]) end
- Viewのカスタマイズ – ヘルパーの追加
- Viewのカスタマイズ – テーブル内の各行の部分を作る
- - Projectsウィンドウへ移動 -> ToDoプロジェクト、Views以下にあるitemsを選択して右クリック -> New -> RHTML file… -> File Nameに _list_stripesと入力(拡張子は付けません)->Finish

- 次のように _list_stripes.rhtmlを作りました。ここでも、deprecatedとなっているメソッド link_to_imageが使われていたのでこれを修正しました。また、スタイルシートが有効になっていないようだったのでclassをidにするなどの修正を行いました。
-
<tr id="<%= list_stripes_counter.modulo(2).nonzero? ? "dk_gray" : "lt_gray" %>"> <td style="text-align: center"><%= list_stripes["done"] == 1 ?show_image("done_ico.gif") : " " %></td><td style="text-align: center"><%= list_stripes["priority"] %></td> <td><%=h list_stripes["description"] %></td> <%if list_stripes["due_date"].nil? %> <td> </td> <% else %> <%= list_stripes["due_date"] < Date.today ? '<td id="past_due">' : '<td style="text-align: center">' %> <%= list_stripes["due_date"].strftime("%m/%d/%y") %> </td> <% end %> <td> <%= h list_stripes.category ? list_stripes.category["category"] : "Unfiled" %> </td> <td> <%= list_stripes["note_id"].nil? ? " " : show_image("note_ico.gif") %> </td> <td> <%= list_stripes["private"] == 1 ? show_image("private_ico.gif") : " " %> </td> <td> <%= link_to(image_tag("edit.png"), { :controller => 'items', :action => 'edit', :id => list_stripes.id }) %> </td> <td> <%= link_to(image_tag("delete.png"), { :controller => 'items', :action => 'destroy', :id => list_stripes.id }, :confirm => 'Are you sure you want to delete this item?' ) %> </td> </tr> - -ToDoプロジェクト/Public/stylesheetsにあるtodo.cssに次の3つを追加しました。
-
#lt_gray { background-color: #e7e7e7; }#dk_gray { background-color: #d6d7d6; }#past_due {color: red;text-align: center; }
- Viewのカスタマイズ – アイコンを用意する
- “Four Days…”では触れられていませんが、アイコンを所定のディレクトリに置かないと、テーブルのヘッダや各行の右端にあるクリッカブルイメージがでないのでここでやっておきます。
- - Four Days on Rails(http://rails.homelinux.org/)のDownload the code used in Four Days on Rails .zip format (20k)のリンクからアーカイブを取得して適当なディレクトリで展開します。
- - アーカイブを展開するとpublic/images以下にイメージがあるので、これをNetBeansのプロジェクトにコピーします。私の場合、NetBeansをインストールするときに、プロジェクトを/home/yoko/M10NetBeansProjects以下に作るように指定したので、/home/yoko/M10NetBeansProjects/ToDo/public/imagesがコピー先のディレクトリです。
- コピーしてくると、NetBeans上にもこのように表示されます。

- Viewのカスタマイズ – New To Doの画面を作る
- -ToDoプロジェクト/Views/items以下にあるnew.rhtmlを次のように編集します。
-
<% @heading = "New To Do" %><%= error_messages_for 'item' %><% form_tag :action => 'create' do %><table><%= render :partial => 'form' %> </table> <hr /> <%= submit_tag "Save" %> <%= submit_tag "Cancel", { :type => 'button',
nClick => "parent.location='" +items_url + "'" } %>
<% end %>
<%= link_to 'Back', :action => 'list' %> - ToDoプロジェクト、Configuration以下にあるroute.rbに次の1行を追加します。
-
map.items 'items/list', :controller => 'items', :action => 'list'
- ここで使われているurl_forメソッドがdeprecatedなのかどうかわからなかったのですが、とりあえずnamed routesを利用することにしました。
- -ToDoプロジェクト、Views、items以下にある_from.rhtmlを次のように編集します。
-
<%= error_messages_for 'item' %><!--[form:item]--><tr> <td><b>Description: </b></td> <td> <%= text_field 'item', 'description', 'size' => 40, 'maxlength' => 40 %> </td> </tr> <tr> <td><b>Date due:</b></td> <td><%= date_select 'item', 'due_date', :use_month_numbers => true %></td> </tr> <tr> <td><b>Category:</b></td> <td><select id="item_category_id" name="item[category_id]"> <%= options_from_collection_for_select @categories, "id", "category", @item.category_id %> </select></td> </tr> <tr> <td><b>Priority:</b></td> <% @item.priority = 3 %> <td><%= select 'item', 'priority', [1,2,3,4,5] %></td> </tr> <tr> <td><b>Private?</b></td> <td><%= check_box 'item', 'private' %></td> </tr> <tr> <td><b>Complete?</b></td> <td><%= check_box 'item', 'done' %><a></td> </tr> <!--[eoform:item]-->
- Viewのカスタマイズ – 例外処理を追加する
- items_controller.rbで定義されているcreateメソッドにrescueキーワードを使って例外処理を追加します。NetBeansのCode Completionを利用してrescueを追加しました。
-
def create begin @item = Item.new(params[:item]) if @item.save flash[:notice] = 'Item was successfully created.' redirect_to :action => 'list_by_priority' else @Categories = Cateogry.find_all render :action => 'new' end rescue Exception => ex flash[:notice] = 'Item could not be saved.' redirect_to :action => 'new' end end
- この例外の捕捉がうまく動かないときにとても役に立ちました。New To Doを登録するためにフォームに入力してSaveボタンを押したのですが、表示されるのはこのような例外処理の部分にきてしまったメッセージのみです。

- 適当なところにbreakpointをセットして、デバッグモードで起動して例外を見てみました。右下のLocal Variablesウィンドウのexのところ、Value欄の”…”をクリックすると、どうやらどこかでcategory_idとするべきところをcatgory_idとtypoしているこのがわかりました。_form.rhtmlに該当個所があり、これを修正したら無事動きました。

- なお、デバッグモードはJRubyでは動きませんので、デバッグのためにNative RubyとMySQLに切替えて試しました。
- Viewのカスタマイズ – スタイルシートを修正する
- -ToDoプロジェクト/Public/stylesheets以下にあるtodo.cssにいくつか修正を加えます。
-
(excerpt) body { background-color: #c6c3c6; color: #333; }h1 { font_family: verdana, arial, helvetice, sans-serif; font-size: 14pt; font-weight: bold; } table { background-color: #e7e7e7; border: outset 1px; border-collapse: separate; border-spacing: 1px; } td {border: inset 1px;} #notice { color: red; background-color: white; } #lt_gray { background-color: #e7e7e7; } #dk_gray { background-color: #d6d7d6; } #past_due { color: red; text-align: center; }
- 実行
- “Run Main Project”ボタンをクリックしてアプリケーションを動かし、ブラウザでhttp://localhost:3000/itemsをリクエストします。最初は何もToDoが入っていないので、まずは”New To Do…”ボタンをクリックしてフォームに入力します。

- SaveボタンをクリックするとNew To Doが登録されます。いくつか登録するとこのように表示されます。

- 各行の右から2番目のアイコンがEditのためのボタンでこれをクリックすると、編集用の画面が表示されます。

- この画面でCompleteにチェックしてEditボタンをクリックし、確認画面でBackのリンクをクリックすると左端の欄にチェックが付きます。

- この状態でテーブルのヘッダ部分の左端にあるチェックマークになっているアイコンをクリックすると確認メッセージが表示され、OKをクリックするとチェックされているToDoが削除されます。
- 右端のごみ箱アイコンはクリックしても確認メッセージが表示されるもののなぜか削除されません。どこがまずいのかは後ほど調べようと思います。
- -ポート番号は3000ではないこともあります。このようにNetBeans上に何番で動いているかが表示されるので、その番号を指定します。この場合、http://localhost:3001/itemsをリクエストします。ポート番号を間違っていると修正が反映されていなかったり、breakpointで止まらなかったりと、さらに悩むことになります。

以上、難関のDay 3でしたが、なんとかここまで動きました。”Four Days on Rails”で使われているRailsのバージョンが古いということもありますが、データベースのテーブルのカラム名が付くメソッドが使えるなどのルールはあまり説明がなく理解しがたいところでした。RailsのルールやRails的な方法などをなにか他の入門書である程度勉強しておかないと、たったの3日でここまで理解して動かすのは難しいのではないかと思いました。
This entry was posted on Friday, September 14th, 2007 at 10:46 pm and is filed under Uncategorized. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.





