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 the put/on. But member do/end is for multiple routes, so just stick with put/on for now. 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.

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 card, and then flashes the item that was decremented.