I’ll just leave my notes here:

The button_to that worked for me: <%= button_to ‘Remove one’, decrement_line_item_path(line_item), remote: true %>

In my decrement method, I had if @line_item.update_attributes(params[:id]) which contained respond_to. Turns out this didn’t work if the line_item should disappear because if you run @line_item.destroy BEFORE update_attributes, you get a “frozen hash” error that not only returns false but returns a runtime error. I got around this with the following:

if @line_item.quantity == 0
  @line_item.destroy
end
begin @line_item.update_attributes(params[:line_item])
rescue
end
respond_to do |format|
  format.js {@current_item = @line_item}
end

Also, turns out that the book uses a little hack to make the jquery blind methods go. I had to use the following to get the cart to disappear when the last item had been decremented out of existence:

$(’#cart’).html(”<%= j render @cart %>”); if ($(’#cart tr’).length 1) { $('#cart').hide('blind', 1000); } $('#current_item').css({'background-color':'#88ff88'}).animate({'background-color':'#114411'}, 1300);

When I had if ($('#cart tr').length 0, it would never work because the cart grand total is inside a tr that never goes away. This wasn’t made obvious when we were building our create.js.erb in the example so it took me a bit to figure out.

I am a little glad I learned so much, but mostly just angry right now.

Christian says:

Hi there, could you do me a big favour and share the complete code? I’ve been fiddling around with this task for hours but I just can’t get it to work (the ajax part).

Johan says:

I believe the button_to should be: <%= button_to ‘Del’, decrement_line_items_path(line_item_id: line_item), remote: true %> I.e “decrement_line_items(!)_path”, at least that worked for me.

Tiger says:

Should this decrement function be written in the model or the controller?

Exl says:

1. Create a route:


  resources :line_items do
    #member do
    #  put 'decrement'
    #end
    put 'decrement', on: :member
  end

Note: you can use either of the two ways: member do/end or put/on. But member do/end is for multiple routes, so just stick with put/on in this case. Read: http://guides.rubyonrails.org/routing.html#adding-more-restful-actions

2. Add a decrement button (the value of mine is ‘V’, i.e. like a down arrow). Because of the above route, we get a helper called path (decrement_line_item_path).


      <td><%= button_to 'V', decrement_line_item_path(line_item), method: :put, remote: true %></td>

3. Create the ‘decrement’ method in line_items_controller.rb (again, all thanks to the route).


  # PUT /line_items/1
  # PUT /line_items/1.json
  def decrement
    @cart = current_cart

    # 1st way: decrement through method in @cart
    @line_item = @cart.decrement_line_item_quantity(params[:id]) # passing in line_item.id

    # 2nd way: decrement through method in @line_item
    #@line_item = @cart.line_items.find_by_id(params[:id])
    #@line_item = @line_item.decrement_quantity(@line_item.id)

    respond_to do |format|
      if @line_item.save
        format.html { redirect_to store_path, notice: 'Line item was successfully updated.' }
        format.js {@current_item = @line_item}
        format.json { head :ok }
      else
        format.html { render action: "edit" }
        format.js {@current_item = @line_item}
        format.json { render json: @line_item.errors, status: :unprocessable_entity }
      end
    end
  end

Again, there is more than one way to do it, I show two above, but another still would be to put all the logic into this controller method (though that would go against the point of MVC). I vote for the 1st way because it is less roundabout (i.e. one less call).

Note: I don’t know what is the right thing to do for the else-clause in respond_to. Please reply if you know.

Here is the 1st way method, in cart.rb:


  def decrement_line_item_quantity(line_item_id)
    current_item = line_items.find(line_item_id)

    if current_item.quantity > 1
      current_item.quantity -= 1
    else
      current_item.destroy
    end

    current_item
  end

Here is the 2nd way method, in line_item.rb:


  def decrement_quantity(line_item_id)
    current_item = LineItem.find_by_id(line_item_id)

    if current_item.quantity > 1
      current_item.quantity -= 1
    else
      current_item.destroy
    end

    current_item
  end

4. Add the js.erb partial. Because the method in line_items_controller is called ‘decrement’, the partial will be /app/views/line_items/decrement.js.erb and it will contain:


$('#cart').html("<%=j render @cart %>");
$('#current_item').css({'background-color':'#88ff88'}).animate({'background-color':'#114411'}, 1000);

This renders the cart, and then flashes the item that was decremented.

Peter says:

Exl, your code works great. One thing… to get the cart to disappear after the last line item is removed, change /app/views/line_items/decrement.js.erb to:


$('#cart').html("<%=j render @cart %>");
$('#current_item').css({'background-color':'#88ff88'}).animate({'background-color':'#114411'}, 1000);
if ($('#cart tr').length==1) {
    // Hide the cart
    $('#cart').hide('blind', 1000);    
}

The if statement checks if there is only one line in the table (the total line) and if true, hides the cart the same way as in the empty cart code.

Exl says:

Thanks, Peter. By the way, I don’t see an RSS feed or a way to be alerted when these wiki pages change, so I have started monitoring via

https://www.changedetection.com/log/pragprog/pt-f-2-31_log.html

Johan says:

Thanks for the code Exl. Just asking, is it normal to use “put” in this case?

Exl says:

Johan, thanks for bringing this up. Although PUT works in this case, the correct HTTP method for such operations is actually POST! The reason is because POST is not idempotent, whereas PUT is (actually, all of the main HTTP methods are idempotent (meaning, they are operations that should produce the same results even if executed multiple times). Idempotent is derived from Latin: idem ‘same’ + potent.

See http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

See http://en.wikipedia.org/wiki/Idempotent#Computer_science_meaning

PUT is idempotent and implies putting a resource. Do it as many times as you like, and the result is the same. x=5 (the assignment) is idempotent.

POST updates a resource, adds a subsidiary resource, or causes a change. A POST is not idempotent, in the way that x++ is not idempotent. Doing it repeatedly will produce different results. In the case of each of our “decrement”, the resulting value changes each time.

Thus I have updated my code in this regard:


-bash> git diff .
diff --git a/app/controllers/line_items_controller.rb b/app/controllers/line_items_controller.rb
index 4a9717b..4915e07 100644
--- a/app/controllers/line_items_controller.rb
+++ b/app/controllers/line_items_controller.rb
@@ -85,8 +85,8 @@ class LineItemsController < ApplicationController
   end

-  # PUT /line_items/1
-  # PUT /line_items/1.json
+  # POST /line_items
+  # POST /line_items.json
   def decrement
     @cart = current_cart

diff --git a/app/views/line_items/_line_item.html.erb b/app/views/line_items/_line_item.html.erb
index 03020b5..4bcfe6a 100644
--- a/app/views/line_items/_line_item.html.erb
+++ b/app/views/line_items/_line_item.html.erb
@@ -7,7 +7,7 @@
       <td><%= line_item.quantity %>&times;</td>
       <td><%= line_item.product.title %></td>
       <td class="item_price"><%= number_to_currency(line_item.total_price) %></td>
-      <td><%= button_to 'V', decrement_line_item_path(line_item), method: :put, remote: true %></td>
+      <td><%= button_to 'V', decrement_line_item_path(line_item), method: :post, remote: true %></td>
       <td><%= button_to 'X', line_item, method: :delete, confirm: 'Are you sure?' %></td>
     </tr>

diff --git a/config/routes.rb b/config/routes.rb
index 27dd485..6a5c17c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -3,9 +3,9 @@ Depot::Application.routes.draw do

   resources :line_items do
     #member do
-    #  put 'decrement'
+    #  post 'decrement'
     #end
-    put 'decrement', on: :member
+    post 'decrement', on: :member
   end

   resources :carts