Ruby on Rails ショッピングカート part6 チェックアウト

Ruby on Rails において ショッピングカート とは以下を示します。

  1. テーブル作成
     注文を格納するテーブルを作成する。
    
    1. create.sql
       create table orders (
        id              int           not null auto_increment,
        name            varchar(100)  not null,
        email           varchar(255)  not null,
        address         text          not null,
        pay_type        char(10)      not null,
        shipped_at      datetime      null,
        primary key (id)
       );
      
  2. 既存テーブルを変更
     line_itemsテーブルに参照制約を追加する。
     これは単にDDLを変更して実行する。
     モデルの再作成はしないで、後でbelongs_toを追加するだけ。
    
    1. drop.sql
       drop table line_items;
      
    2. create.sql
       create table line_items (
         id              int           not null auto_increment,
         product_id      int           not null,
         order_id        int           not null,
         quantity        int           not null default 0,
         unit_price      decimal(10,2) not null,
         constraint fk_items_product   foreign key (product_id) references products(id),
         constraint fk_items_order     foreign key (order_id) references orders(id),
         primary key (id)
       );
      
  3. モデル作成
     次のスクリプトでモデルを作成する。
    
     %cd depot/
     %ruby script/generate model Order
           exists  app/models/
           exists  test/unit/
           exists  test/fixtures/
           create  app/models/order.rb
           create  test/unit/order_test.rb
           create  test/fixtures/orders.yml
           exists  db/migrate
           create  db/migrate/002_create_orders.rb
    
  4. Railsにも変更を伝達
     注文には複数の品目が存在することをRailsに明記する。has_many。
     品目が注文に属していることもRailsに明記する。belongs_to。
    
    1. /app/models/order.rb
       class Order < ActiveRecord::Base
         has_many :line_items
       end
      
    2. /app/models/line_item.rb
       class LineItem < ActiveRecord::Base
      
         belongs_to :product
         belongs_to :order
      
         def self.for_product(product)
           item = self.new
           item.quantity   = 1
           item.product    = product
           item.unit_price = product.price
           item
         end
       end
      
  5. メソッドを追加
     注文の詳細情報を取得するアクションを定義する。checkout()メソッド。
    
    1. /app/controllers/store_controller.rb
       def checkout
         @cart = find_cart
         @items = @cart.items
         if @items.empty?
           redirect_to_index("カートに商品が入っていません!")
         else
           @order = Order.new
         end
       end
      
  6. ビュー作成
     ここでは、checkout.rhtmlを作成する。
     中身のコードは暫定的に書きました。
    
    1. /app/views/store/checkout.rhtml
       <% @page_title = "Checkout" -%>
      
       <h3>Please enter your details below</h3>
      
       <table>
        <tr>
         <td>Name:</td>
      
         <td><%= text_field("order", "name", "size" => 40 ) %></td>
        </tr>
        <tr>
         <td>EMail:</td>
         <td><%= text_field("order", "email", "size" => 40 ) %></td>
        </tr>
        <tr valign="top">
         <td>Address:</td>
         <td><%= text_area("order", "address", "cols" => 40, "rows"  => 5) %></td>
        </tr>
      
        <tr>
          <td>Pay using:</td>
          <td><%=
            options = [["Select a payment option", ""]]
            select("order", "pay_type", options)
          %></td>
        </tr>
        <tr>
          <td></td>
          <td><%= submit_tag(" CHECKOUT ") %></td>
        </tr>
       </table>
      
  7. チェックアウト実行
     とりあえずチェックアウトしてみる!!
    
    • http://www.bishounen.sakura.ne.jp/rails/images/knowledge/149_01_checkout.jpg
    • Railsとフォーム
        scaffoldジェネレータを使うと、全てのデータを取得するための
        フォームを自動的に作成してくれた。それは、"_form.rhtml"の
        おかげで、ビューで(例えばnew.rhtml)そのフォームをサブフォームを
        参照している。
      
        <h1>New product</h1>
      
        <%= start_form_tag :action => 'create' %>
          <%= render :partial => 'form' %>
          <%= submit_tag "Create" %>
        <%= end_form_tag %>
      
        <%= link_to 'Back', :action => 'list' %>
      
  8. ビュー編集
     先ほどのビューを編集する。
    
     入力された情報(inputタグ)は、モデル内に取得、モデル内に格納、
     必要に応じデータベースに格納、というような順番で進められる。
    
    • form_tag()メソッド
        htmlのformタグを使用して書くのもありますが、ここでは
        Railsのヘルパーメソッドform_tag()メソッドを使う。
      
    1. /app/views/store/checkout.rhtml
       <% @page_title = "チェックアウト" -%>
       <%= start_form_tag(:action => "save_order") %>
       <table>
        <tr>
         <td>氏名:</td>
      
         <td><%= text_field("order", "name", "size" => 40 ) %></td>
        </tr>
        <tr>
         <td>電子メール:</td>
         <td><%= text_field("order", "email", "size" => 40 ) %></td>
        </tr>
        <tr valign="top">
         <td>住所:</td>
         <td><%= text_area("order", "address", "cols" => 40, "rows"  => 5) %></td>
        </tr>
      
        <tr>
          <td>支払い方法:</td>
          <td><%=
            options = [["支払い方法を選択してください", ""]] + Order::PAYMENT_TYPES
            select("order", "pay_type", options)
          %></td>
        </tr>
        <tr>
          <td></td>
          <td><%= submit_tag(" チェックアウト ") %></td>
        </tr>
       </table>
       <%= end_form_tag %>
      
  9. リスト
     上記では、選択肢のリストがOrderモデルの属性として格納されていると想定。
    
     この選択肢の配列の定義をorder.rbモデル内に定義。
     最初の要素:選択肢として表示される文字列。
     2番目の要素:データベースに格納される値。
    
    1. /app/models/order.rb
       class Order < ActiveRecord::Base
         has_many :line_items
      
         PAYMENT_TYPES = [
           [ "小切手",          "check" ],
           [ "クレジットカード",    "cc"    ],
           [ "購入注文書", "po"    ]
         ].freeze
      
       end
      
  10. チェックアウト実行
     チェックアウトしてみる!!
     リストの表示もできました!!
    
    • http://www.bishounen.sakura.ne.jp/rails/images/knowledge/149_02_checkout_view.jpg
    • http://www.bishounen.sakura.ne.jp/rails/images/knowledge/149_03_checkout_list.jpg
  11. 「チェックアウト」ボタン - メソッド追加
     現在、save_order()メソッドを実装していないので、
     「チェックアウト」ボタンを押下してもエラー。
    
    • save_order()メソッド
        3行目
           新しいオブジェクト作成とフォームデータによる初期化
           注文オブジェクトに関連つけられているすべてのフォーム
           データを取得するので、パラメータとして :orderハッシュ
           を選択している。
        4行目
           既にカートに格納されている品目を注文に追加する。
        5行目
           データベースに保存するように指示。また検証も行う。
      
    1. /app/controllers/store_controller.rb
       def save_order
         @cart = find_cart
         @order = Order.new(params[:order])
         @order.line_items << @cart.items
         if @order.save
           @cart.empty!
           redirect_to_index('ご注文ありがとうございました')
         else
           render(:action => 'checkout')
         end
       end
      
  12. 「チェックアウト」実行
     実際に注文をしてみる。
     しかし、このままでは未入力のままにデータベースに登録できてしまう。
    
    • Rails
        「品目テーブルのorder_id列に、参照する注文のidをセットする」
        なんて処理はRailsが自動的にしてくれる!!
      
    • http://www.bishounen.sakura.ne.jp/rails/images/knowledge/149_04_db_no_validate.jpg
    • http://www.bishounen.sakura.ne.jp/rails/images/knowledge/149_05_db_no_validate.jpg
  13. validate()追加
     入力チェックを追加する。また、
    
    1. /app/models/order.rb
       class Order < ActiveRecord::Base
         has_many :line_items
      
         PAYMENT_TYPES = [
           [ "小切手",          "check" ],
           [ "クレジットカード",    "cc"    ],
           [ "購入注文書", "po"    ]
         ].freeze
      
         validates_presence_of :name, :email, :address, :pay_type
      
       end
      
  14. 「チェックアウト」実行
     上記のままでは、save_order()メソッドが実行されても、
     "render(:action => 'checkout')"に遷移する。
     (URLはsave_orderになっているんだな。)
     だから、エラー表示を行う必要がある!!
    
    • http://www.bishounen.sakura.ne.jp/rails/images/knowledge/149_06_render_checkout.jpg
  15. ビューを編集
     以下を2行目に追加する。
    
    1. /app/views/store/checkout.rhtml
       <%= error_messages_for(:order) %>
      
  16. 「チェックアウト」実行
     ちゃんとエラーが表示されている。リストもエラーに含まれている。
    
    • http://www.bishounen.sakura.ne.jp/rails/images/knowledge/149_07_display_errors.jpg
  17. エラー表示のスタイルを追加
     今までは、depot.cssだけを参照していた。
     それを、scaffold.cssも参照するように変更。
    
    1. /app/views/layouts/store.rhtml
       <%= stylesheet_link_tag "scaffold", "depot", :media => "all" %>
      
  18. 「チェックアウト」実行
     ちゃんとエラーにスタイルが付いている
    
    • http://www.bishounen.sakura.ne.jp/rails/images/knowledge/149_08_display_errors_add_style.jpg
  19. ビュー編集
     チェックアウトページでもカートの内容がほしい。
     以下を追加すれば大丈夫。
    
    1. /app/views/store/checkout.rhtml
       <%= render_component(:action => "display_cart") %>
      
  20. 「チェックアウト」実行
     カートの内容は表示されているが・・・・・。ちょっとおかしい。
    
    • http://www.bishounen.sakura.ne.jp/rails/images/knowledge/149_09_display_checkout_plus_cart.jpg
  21. ビュー編集、メソッド修正
     display_cart()メソッドに引数を渡して、外側のレイアウトを除いて表示してみる。
    
    1. /app/views/store/checkout.rhtml
       <%= render_component(:action => "display_cart",
                            :params => { :context => :checkout }) %>
      
    2. /app/controllers/store_controller.rb
       def display_cart
         @cart = find_cart
         @items = @cart.items
         if @items.empty?
           redirect_to_index('現在、カートには商品が入っていません')
         end
         if params[:context] == :checkout
           render(:layout => false)
         end
       end
      
    • http://www.bishounen.sakura.ne.jp/rails/images/knowledge/149_10_display_checkout_plus_cart_layout.jpg
  22. 最後に、ビュー編集
     display_cart.rhtmlの右側のメニューを表示制御を行う。
     あと最後はおまけで<h3>を追加しました。
    
    1. /app/views/store/display_cart.rhtml
       <div id="cartmenu">
         <ul>
           <li><%= link_to 'ショッピングを続ける', :action => "index" %></li>
           <% unless params[:context] == :checkout -%>
           <li><%= link_to 'カートを空にする', :action => "empty_cart" %></li>
           <li><%= link_to 'チェックアウトする', :action => "checkout" %></li>
           <% end -%>
         </ul>
       </div>
      
    2. /app/views/store/checkout.rhtml
       <%= render_component(:action => "display_cart",
                            :params => { :context => :checkout }) %>
      
       <h3>お客様の連絡先と支払い方法をご指定ください</h3>
      
       <%= start_form_tag(:action => "save_order") %>
      
    • http://www.bishounen.sakura.ne.jp/rails/images/knowledge/149_11_display_checkout_rightmenu_deleted.jpg

ご訪問頂き有難う御座います。 当サイトを効率良く使うためにまずは FrontPage を見て下さい。 検索方法、一覧表示などの各情報を纏めています。
当サイトの説明 → Frontpage