読者です 読者をやめる 読者になる 読者になる

気軽に楽しくプログラムと遊ぶ

自分が興味があってためになるかもって思う情報を提供しています。

書籍「Railsによるアジャイル Webアプリケーション開発」11章

タスクF:Ajaxの追加

11.1 イテレーションF1:カートの移動

サイドバーにカートを移動し、Ajaxでサイドバーのみの更新で
カート内情報を再表示する。

まず、カート表示部分を部分テンプレート(partial)として、外だしにする。

depot/app/veiws/carts/show.html.erb

<div class="cart_title">カート</div>
<table>
   #renderメソッドは項目の繰り返し処理を行う
  <%= render(@cart.line_items) %>

  <!-- カート内の商品の合計金額を表示 -->
  <tr class="total_line">
    <td colspan="2">合計</td>
    <td class="total_cell"><%= number_to_currency(@cart.total_price) %></td>
  </tr>

</table>

外だしファイルは頭にアンダースコア(_)をつけたレンダリング対象オブジェクトと同名称に設定し、同ディレクトリ以下に配置する。

depot/app/veiws/line_items/_line_item.html.erb

<!-- 各商品の個数、タイトル、合計金額を表示 -->
<tr>
  <td><%= line_item.quantity %>&times;</td>
  <td><%= line_item.product.title %></td>
  <td class="item_price"><%= number_to_currency(line_item.total_price) %></td>
</tr>

renderメソッドを用いることで繰り返し処理が除去できた。Listオブジェクトの要素は
テンプレート名と同様の名称を用いることで参照可能となる。(line_item)

次にサイドバーに埋め込むようのカートを部分テンプレートで同様に定義する

depot/app/veiws/line_items/_line_item.html.erb

<div class="cart_title">カート</div>
<table>
  <%= render(cart.line_items) %>

  <!-- カート内の商品の合計金額を表示 -->
  <tr class="total_line">
    <td colspan="2">合計</td>
    <td class="total_cell"><%= number_to_currency(cart.total_price) %></td>
  </tr>

</table>

<%= button_to 'カートを空にする', cart, method: :delete, confirm: '本当によろしいですか?' %>

基本的に既存のカートと同じ内容だが、cartの部分が
コントローラーのインスタンス変数でなく、テンプレートに渡されるパラメータ変数を
参照するように変更。コントローラーとの結合を避ける意味。


同じ処理が複数回書かれるのはDRYの思想に反するので元のカートを新たに定義した
部分テンプレートで置き換える。

depot/app/veiws/carts/show.html.erb

<% if notice %>
  <p id="notice"><%= notice %></p>
<% end %>

<%= render(@cart) %>

<%= button_to 'カートを空にする', @cart, method: :delete, confirm: '本当によろしいですか?' %>


次に全体レイアウトのビューのサイドバーにカートの部分テンプレートを組み込む。

depot/app/veiws/layouts/application.html.erb

    <div id="side">
      <div id="cart">
        <%= render @cart %>
      </div>


商品リストを表示するsotreコントローラーにインスタンス変数のcartは存在しないため、定義

depot/app/controllers/store_controller.rb

  def index
    @products = Product.order(:title)
    @cart = current_cart


カートに入れるボタン押下時の遷移先をカタログページに変更する。

depot/app/controllers/line_items_controller.rb

  def create
    ・・・
    respond_to do |format|
      if @line_item.save
        # カートのみを表示する画面を廃止にするため、カタログページ遷移へ変更にする
        format.html { redirect_to store_url }
        format.json { render :show, status: :created, location: @line_item }
      else
        format.html { render :new }
        format.json { render json: @line_item.errors, status: :unprocessable_entity }
      end
    end


カタログページへカートを表示したことで再表示コストがあがった。
そこでAjaxを利用したページ再表示を行うように修正する。


11.2 イテレーションF2:Ajaxベースのカートの作成

Ajaxリクエストを送信できるようにするために
ヘルパーメソッドのbutton_toにオプションで remote: trueを追加

depot/app/view/index.html.erb

<h1>Programticカタログ</h1>

<% @products.each do |product| %>
  <div class="entry">
    ・・・
    <div class="price_line">
      <span class="price"><%= number_to_currency(product.price) %></span>
      <!-- カートに入れるボタン コントローラー名+_pathでURL指定。追加商品のIDを指定 -->
      <%= button_to 'カートに入れる', line_items_path(product_id: product),
      remote: true %>
    </div>
  </div>
<% end %>


Ajax通信時はredirectを行わず、画面の一部のみを更新するようにするために
js時のフォーマットパターンを追加

depot/app/controllers/line_items_controller.rb

  def create
    ・・・
    respond_to do |format|
      if @line_item.save
        # カートのみを表示する画面を廃止にするため、カタログページ遷移へ変更にする
        format.html { redirect_to store_url }
        format.js
        format.json { render :show, status: :created, location: @line_item }
    ・・・


format.jsが呼ばれるとレンダリング対象のcreateテンプレートを探しに行く。
そこでcreate.js.erbテンプレートを作成する。これによりJavascriptを生成できる。

depot/app/view/line_items/create.js.erb

<!-- j()ヘルパーメソッドを用い、カートHTMLを生成する -->
$('#cart').html("<%=j render @cart %>");

ここでカタログページのカートに入れるボタンを押すと、
カート部分のみ更新された。なんか、かっこいい。


11.3 イテレーションF3:変更内容の強調表示

jqueryUIを用いて強調表示をおこなう。以下、jsへjQueryUIの読み込み設定を追記

depot/app/assets/javascripts/application.js

//= require jquery
//= require jquery-ui


追加商品のみを光らせるために、追加商品のインスタンスをviewに送るように設定

depot/app/controllers/line_items_controller.rb

      if @line_item.save
        # カートのみを表示する画面を廃止にするため、カタログページ遷移へ変更にする
        format.html { redirect_to store_url }
        format.js {@current_item = @line_item}


個々の項目veiw内で追加商品かどうかを判定するように変更

depot/app/view/line_items/_line_item.html.erb

<% if line_item == @current_item %>
<tr id="current_item">
<% else %>
<tr>
<% end %>


背景を目立つものに変えてから徐々に元に戻す処理をJavascriptで実現する

depot/app/view/line_items/create.js.erb

<!-- j()ヘルパーメソッドを用い、カートHTMLを生成する -->
$('#cart').html("<%=j render @cart %>");


作成したアイテムを保持する。

depot/app/controllers/line_items_controller.rb

    respond_to do |format|
      if @line_item.save
        # カートのみを表示する画面を廃止にするため、カタログページ遷移へ変更にする
        format.html { redirect_to store_url }
        format.js { @current_item = @line_item }


保持したアイテムをビュー側で読み取り、作成アイテムの場合
id属性のcssが反映されるように修正

depot/app/views/line_items/_line_item.html.erb

<% if line_item == @current_item %>
<tr id="current_item">
<% else %>
<tr>
<% end %>
  <td><%= line_item.quantity %>&times;</td>


current_itemがIdに設定されている場合、jquerycssメソッドで色を設定
animateメソッドで1000ミリ、1秒間かけて背景色に変化していくようにする。

depot/app/view/line_items/create.js.erb

<!-- j()ヘルパーメソッドを用い、カートHTMLを生成する -->
$('#cart').html("<%=j render @cart %>");

$('#current_item').css({'background-color' : '#88ff88'})
.animate({'background-color' : '#114411'}, 1000);

:を,にしててちょっとはまる。。
cssの書き方なんですね。


11.4 イテレーションF4:空のカートを隠す

空のカートを隠すために全体に適用するためのapplication_helper.rbへ
ヘルパメソッドを追加する

ヘルパーメソッドの使い方としては以下

depot/app/views/layouts/application.html.erb

  <div id="columns">
    <div id="side">
      <%= hidden_div_if(@cart.line_items.empty?, id: 'cart') do %>
      <div id="cart">
        <%= render @cart %>
      </div>
      <% end %>

depot/app/helpers/application_helper.rb

module ApplicationHelper
  def hidden_div_if(condition, attributes = {}, &block)
    if condition
      attributes["style"] = "display: none"
    end
    content_tag("div", attributes, &block)
  end
end

・moduleの復習
クラスと似ているがオブジェクト生成ができない。
mixinを行うことでクラスに定義されているようにメソッドを使用可能となる。

content_tagで&block(表示するカート)をラップするdivタグを生成し、
その属性にattributesの内容を設定する。
attributesは引数で指定したcart


カートを空にした際のメッセージを消す

depot/app/controllers/line_items_controller.rb

  def destroy
	・・・
    respond_to do |format|
      format.html { redirect_to store_url}

redirect_to に渡すnoticeを削除


11.5 イテレーションF5:画像をクリック可能にする

depot/app/asserts/javascripts/store.js.coffee

$ ->
  #該当属性をクリックされた場合の処理
  $('.store .entry > img').click ->
    #クリックされたjavascriptオブジェクトの親のsubmitを
    $(this).parent().find(':submit').click()


11.6 Ajaxへの変更をテストする

rake testとするとline_itemsがnilClassであるというエラーとなる。
Product#indexにおいてcartを作成していないため。cartが存在しない場合は非表示に変更する

depot/app/views/layouts/application.html.erb

  <div id="columns">
    <div id="side">
      <% if @cart %>
        <%= hidden_div_if(@cart.line_items.empty?, id: 'cart') do %>
        <div id="cart">
          <%= render @cart %>
        </div>
        <% end %>
      <% end %>


line_item作成後のリダイレクト先が変更していたのでテストも同様に変更

depot/test/controllers/line_items_controller_test.rb

  test "should create line_item" do
    assert_difference('LineItem.count') do
      post :create, product_id: products(:ruby).id
    end

    assert_redirected_to store_path
  end

自由課題。省略。11章完了