Servlet Garden

Java, Web Application and beyond

Flower

Archive for September, 2007

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をまとめておこうと思います。

  1. Itemsモデルを作る
    • - Projectsウィンドウへ移動 -> ToDoプロジェクトで右クリック -> Generate…を選択 -> Generate: modelを選択 -> Argumentsにitemと入力 -> OK
    • 4Days on Rails Generating a Model for Item
    • 以上の操作で次のようにコードが自動生成されます。
    • 4Days on Rails Outputs of Generating a Model for Item
    • - 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
    • 4Days on Rails Editing a Model for Item
    • 上記のコードのうち、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モジュールのメソッドです。
  2. Notesモデルを作る
    • - Projectsウィンドウへ移動 -> ToDoプロジェクトで右クリック -> Generate…を選択 -> Generate: modelを選択 -> Argumentsにnoteと入力 -> OK
    • 4Days on Rails Generating a Model for Note
    • 以上の操作で次のようにコードが自動生成されます。
    • 4Days on Rails Outputs of Generating a Model for Note
    • - 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
    • 4Days on Rails Editing a Model for Note
  3. ItemsとNotesそれぞれのScaffoldを作成する
    • - Projectsウィンドウへ移動 -> ToDoプロジェクトで右クリック -> Generate…を選択 -> Generate: scaffoldを選択 -> Model NameにItemと入力 -> OK
    • 4Days on Rails Generating a Scaffold for Item
    • 以上の操作で次のようにコードが自動生成されます。
    • 4Days on Rails Outputs of Generating a Scaffold for Item
    • - Projectsウィンドウへ移動 -> ToDoプロジェクトで右クリック -> Generate…を選択 -> Generate: scaffoldを選択 -> Model NameにNoteと入力 -> OK
    • 4Days on Rails Generating a Scaffold for Note
    • 以上の操作で次のようにコードが自動生成されます。
    • 4Days on Rails Outputs of Generating a Scaffold for Note
  4. Viewのカスタマイズ – 全てのページに共通のレイアウトを作る
    • - ToDoプロジェクト/Views/layouts以下の全てのファイルをShiftキーを押しながらクリックして選択->右クリック -> Delete -> Yes
    • -上記の操作でlayouts以下の全てのrhtmlファイルを削除して、次のようにして新たにapplication.rhtmlファイルを作成します。
    • layoutsで右クリック -> New -> RHTML file… -> File Nameにapplicationと入力(拡張子は入力しません) ->Finish
    • 4Days on Rails Creating new rhtml file
    • 4Days on Rails Inputting new rhtml file name
    • ただし、この方法で作ったrhtmlファイルには最低限のタグしか入っていないので、どれかrhtmlファイルを一つを残しておいて、Renameした方が便利でしょう。
    • -application.rhtmlを作りますが、rhtmlファイルにJavaScriptを入力すると、このようにエラーになってしまいます。JavaScriptだけfocus.jsという名前にして、ToDoプロジェクト/Public/javascripts以下に作りました。
    • NetBeans 6.0M10 incorrect JavaScript errors in a rhtml file
    • <!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;
      
      }
      
      }
      
      }
      
      }
    • 4Days on Rails Editing a layout for all pages
    • 4Days on Rails Editing a javascript file for all pages
    • JavaSciptを.jsファイルに書いたら、このようにcode completionが動きました。便利かも。
    • NetBeans 6.0M10 JavaScript code completion
    • -スタイルシートの名前をディフォルトの scaffold.cssからtodo.cssに変更します。
    • ToDoプロジェクト/Public/stylesheets以下にある scaffold.cssを選択して右クリック -> Rename -> New Nameにtodoを入力(拡張子は書きません) -> OK
    • 4Days on Rails Renaming a stylesheet
    • スタイルシートのcode completionもこのようにsuggestionは表示されるのですが、選択してreturnキーを押してもエディタ上に補完されませんでした。今のところ、間違えていないかどうかの参考にできる程度のようです。ただ、Preview画面にもエディタ上にも、どのようなテキストになるのかが見えるのは便利です。
    • NetBeans 6.0M10 CSS code completion
    • NetBeans 6.0M10 CSS text preview
  5. 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",
       :o nClick => "parent.location='" + categories_url + "'" } %>
      
      <% end %>
      
      <%= "Page: " + pagination_links(@item_pages, :params => {
      
      :action => @params["action"] || "index" }) + "<hr />" if @item_pages.page_count>1 %>
    • 4Days on Rails Editing a view for Item’s list
    • 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'
  6. 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,
       :o rder_by => 'priority, due_date'
      
      render_action 'list'
      
      end  def list_by_description
      
      @item_pages, @items = paginate :items, :per_page => 10,
       :o 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
  7. Viewのカスタマイズ – ヘルパーの追加
    • ToDoプロジェクトを作ったときに自動生成されたapplication_helper.rb, application.rbにコードを追加します。Four Days…記載のコードと同じなので画面のキャプチャだけ付けました。
    • 4Days on Rails Editing an application helper
    • 4Days on Rails Editing a controller for the application
  8. Viewのカスタマイズ – テーブル内の各行の部分を作る
    • - Projectsウィンドウへ移動 -> ToDoプロジェクト、Views以下にあるitemsを選択して右クリック -> New -> RHTML file… -> File Nameに _list_stripesと入力(拡張子は付けません)->Finish
    • 4Days on Rails Creating new partial rhtml file
    • 次のように _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;
      
      }
  9. 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上にもこのように表示されます。
    • NetBeans 6.0M10 Displaying copied images
  10. 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',
       :o 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]-->
  11. 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ボタンを押したのですが、表示されるのはこのような例外処理の部分にきてしまったメッセージのみです。
    • 4Days on Rails a Displayed error message
    • 適当なところにbreakpointをセットして、デバッグモードで起動して例外を見てみました。右下のLocal Variablesウィンドウのexのところ、Value欄の”…”をクリックすると、どうやらどこかでcategory_idとするべきところをcatgory_idとtypoしているこのがわかりました。_form.rhtmlに該当個所があり、これを修正したら無事動きました。
    • NetBeans 6.0M10 Ruby Pack Debugging
    • なお、デバッグモードはJRubyでは動きませんので、デバッグのためにNative RubyとMySQLに切替えて試しました。
  12. 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;
      
        }
  13. 実行
    • “Run Main Project”ボタンをクリックしてアプリケーションを動かし、ブラウザでhttp://localhost:3000/itemsをリクエストします。最初は何もToDoが入っていないので、まずは”New To Do…”ボタンをクリックしてフォームに入力します。
    • 4Days on Rails Adding NewToDo
    • SaveボタンをクリックするとNew To Doが登録されます。いくつか登録するとこのように表示されます。
    • 4Days on Rails After Adding few NewToDos
    • 各行の右から2番目のアイコンがEditのためのボタンでこれをクリックすると、編集用の画面が表示されます。
    • 4Days on Rails Editing each ToDo
    • この画面でCompleteにチェックしてEditボタンをクリックし、確認画面でBackのリンクをクリックすると左端の欄にチェックが付きます。
    • 4Days on Rails After Editing a ToDo
    • この状態でテーブルのヘッダ部分の左端にあるチェックマークになっているアイコンをクリックすると確認メッセージが表示され、OKをクリックするとチェックされているToDoが削除されます。
    • 右端のごみ箱アイコンはクリックしても確認メッセージが表示されるもののなぜか削除されません。どこがまずいのかは後ほど調べようと思います。
    • -ポート番号は3000ではないこともあります。このようにNetBeans上に何番で動いているかが表示されるので、その番号を指定します。この場合、http://localhost:3001/itemsをリクエストします。ポート番号を間違っていると修正が反映されていなかったり、breakpointで止まらなかったりと、さらに悩むことになります。
    • NetBeans 6.0M10 WEBrick’s port number

以上、難関のDay 3でしたが、なんとかここまで動きました。”Four Days on Rails”で使われているRailsのバージョンが古いということもありますが、データベースのテーブルのカラム名が付くメソッドが使えるなどのルールはあまり説明がなく理解しがたいところでした。RailsのルールやRails的な方法などをなにか他の入門書である程度勉強しておかないと、たったの3日でここまで理解して動かすのは難しいのではないかと思いました。