6

Ruby newbie here. I'm going through Agile Web Development With Rails. In chapter 11 it challenges you to add a 'decrease quantity' button to items in the shopping cart. I went ahead and tried to implement an increase link as well.

The problem is it's not doing anything when I click on the links.

line_items_controller.rb

def decrease
  @cart = current_cart
  @line_item = @cart.decrease(params[:id])

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

def increase
  @cart = current_cart
  @line_item = @cart.increase(params[:id])

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

cart.rb

def decrease(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

def increase(line_item_id)
  current_item = line_items.find(line_item_id)
  current_item.quantity += 1
  current_item
end

routes.rb

resources :line_items do
  put 'decrease', on: :member
  put 'increase', on: :member
end

_line_item.html.erb

<% if line_item == @current_item %>
<tr id="current_item">
<% else %>
<tr>
<% end %>
  <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><%= link_to "-", decrease_line_item_path(line_item), method: :put, remote: true %></td>
  <td><%= link_to "+", increase_line_item_path(line_item), method: :put, remote: true %></td>
  <td><%= button_to 'Remove Item', line_item, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>

/line_items/increase.js.erb

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

/line_items/decrease.js.erb

$('#cart').html("<%= escape_javascript(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);
}

Let me know if I forgot anything crucial. Thanks in advance!

----EDIT----

I changed the code to what Rich posted, and this is what shows up in the console when I click the '+' link.

Started GET "/line_items/25/qty" for ::1 at 2016-01-30 23:49:11 -0600

ActionController::RoutingError (No route matches [GET] "/line_items/25/qty"):

So I see that it needs a route for qty but I'm not quite sure how to set that up. I'm guessing the JS alert we set up isn't firing because it's snagging up at this point?

----EDIT 2----

Now I'm passing the links as POST and getting this name error, from both the up and down links:

Started POST "/line_items/25/qty" for ::1 at 2016-01-31 09:49:04 -0600
Processing by LineItemsController#qty as JS
  Parameters: {"id"=>"25"}
Completed 500 Internal Server Error in 38ms (ActiveRecord: 0.0ms)

NameError (undefined local variable or method `current_cart' for #<LineItemsController:0x007fbfb11ea730>):
  app/controllers/line_items_controller.rb:70:in `qty'

What confuses me here is that current_cart works in the increase and decrease methods but not in qty.

tronerta
  • 432
  • 6
  • 12
tdog
  • 147
  • 2
  • 10
  • is `increase.js.erb` and `decrease.js.erb` in the same folder as `_line_item.html.erb`? – Vic Jan 30 '16 at 16:57
  • As a simple debugging tip I would do 2 things: 1) check for JS errors in your console; 2) add debugger or binding.pry (for example on the first line of _line_item.html.erb) – Peter de Ridder Jan 30 '16 at 17:55
  • @PeterdeRidder Thanks, obviously sometimes I forget about checking for errors in the console. – tdog Jan 31 '16 at 05:52

2 Answers2

4

Some time passed since you asked this - did you solved it? I have one answer to this issue - I managed to increase/decrease numbers in cart, but without Ajax calls - everytime button clicked - page reloads (I removed remote: true). I have not followed @Richard Peck solution, even dough it might be a better way. So, as he pointed out, routes had to use get method, instead of put (I am not very sure why get instead of post should be used, but if Rails asks it I kindly do it):

  resources :line_items do
    get 'decrease', on: :member
    get 'increase', on: :member
  end

Also, I have been noticed by other guy on StackExchange that i forgot to allow increase / decrease actions in controller, since we restrict most of them by default:

line_items_controller.rb:

  before_action :set_cart, only: [:create, :decrease, :increase]

So, I guess these two issues was your problem (just like mines).

line_items_controller.rb:

  def decrease
    product = Product.find(params[:product_id])
    @line_item = @cart.remove_product(product)

    respond_to do |format|
      if @line_item.save
        format.html { redirect_to cart_path, notice: 'Line item was successfully updated.' }
        format.js
        format.json { render :show, status: :ok, location: @line_item }
      else
        format.html { render :edit }
        format.json { render json: @line_item.errors, status: :unprocessable_entity }
      end
    end
  end

  def increase
    product = Product.find(params[:product_id])
    @line_item = @cart.add_product(product)

    respond_to do |format|
      if @line_item.save
        format.html { redirect_to :back, notice: 'Line item was successfully updated.' }
        format.js
        format.json { render :show, status: :ok, location: @line_item }
      else
        format.html { render :edit }
        format.json { render json: @line_item.errors, status: :unprocessable_entity }
      end
    end
  end

cart.rb:

  def add_product(product)
    current_item = line_items.find_by(product_id: product.id)
    if current_item
      current_item.quantity += 1
    else
      current_item = line_items.build(product_id: product.id)
    end
    current_item
  end

  def remove_product(product)
    current_item = line_items.find_by(product_id: product.id)
    if current_item.quantity > 1
      current_item.quantity -= 1
    elsif current_item.quantity = 1
      current_item.destroy
    end
    current_item
  end

and links:

<td><%= link_to "-", decrease_line_item_path(product_id: line_item.product), class:"btn btn-danger" %></td>
<td><%= link_to "+", increase_line_item_path(product_id: line_item.product), class:"btn btn-success" %></td>

Would this make what you need? And maybe any ideas why Ajax is not working? Here a link to full my question:

https://stackoverflow.com/questions/40551425/rails-ajax-jquery-cant-make-to-render-changes-without-page-refresh

Community
  • 1
  • 1
Julius Dzidzevičius
  • 10,775
  • 11
  • 36
  • 81
3

With this type of pattern, you're best DRYing up your logic into a single action:

#config/routes.rb
resources :line_items do
   match :qty, action: :qty, via: [:post, :delete], on: :member #-> url.com/line_items/qty
end

#app/models/line_item.rb
class LineItem < ActiveRecord::Base
   after_update :check_qty, if: "qty_changed?"

   private

   def check_qty
     self.destroy if self.qty.zero?
   end
end 

#app/controllers/line_items_controller.rb
class LineItemsController < ApplicationController
   def qty
      @cart = current_cart
      @item = @cart.line_items.find params[:id]

      if request.post? #-> increment
         method = "increment"
      elsif request.delete? #-> decrement
         method = "decrement"
      end

      @item.send(method, :qty, params[:qty])

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

This will allow you to pass a single link (with the potential of qty) to your controller. If you leave it blank, it will just use 1 as the qty:

 <%= button_to "+", line_items_qty_path(@line_item), params: { qty: 5 } %>

 <%= link_to "-", line_items_qty_path(line_item), method: :post, remote: true %>
 <%= link_to "+", line_items_qty_path(line_item), method: :delete, remote: true %>

Debugging

Since you're new, you need to understand about debugging.

When doing something like this, there are many places it could "go wrong". Like many inexperienced devs, you've basically said "it's not working"... the problem is that many experienced devs know that there has to be a problem somewhere.

The best thing you can do is find out where it's going wrong. This is a tedious process (test each part); you should start with your JS:

#app/views/line_items/qty.js.erb
alert("test");

If the above fires, it means you're doing everything right up to that point.

If you add the above file with my recommended code, we'll have a much better idea as to what the problem may be. You'll also want to post your console logs for the requests sent to your line_items controller (this will indicate whether Rails treats the request as successful).

Once you've found the problem, you can then pinpoint what needs to be done to fix it, which is where many people expect a question to be based.


As an aside, we've built a cart before (it uses sessions rather than db):

enter image description here

I could write up how to do it if you want. It uses a session-based model.


Update

The error you're seeing is because you're sending a GET request through your link; my routes were POST & DELETE respectively. You need something like the following:

 <%= link_to "-", line_items_qty_path(line_item), method: :post, remote: true %>

I think I got it wrong in my post (apologies) - you have to make sure you're passing the method as POST or DELETE


Update 2

To initialize the current_cart method, you need to make sure you have it available.

Ryan Bates alludes to this in his "Session Model" Railscast --

#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
   before_filter :current_cart
   helper_method :current_cart

   private

   def current_cart
     @current_cart = session[:cart_id] ? Cart.find(session[:cart_id]) : Cart.create
     session[:cart_id] = @current_cart.id if @current_cart.new_record?
   end
end
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • 1
    Thanks Rich. I edited my post, it's a routing error. – tdog Jan 31 '16 at 05:58
  • Thanks again, I've edited my post again. We're on the right track! – tdog Jan 31 '16 at 15:56
  • Also `line_items_qty_path(line_item)` threw an error, checked the routes and changed it to `qty_line_item_path(line_item)`. The first problem was because I only changed that part of the link instead of adding the `POST` method. – tdog Jan 31 '16 at 16:03
  • Yes, you will need to make sure the routes are okay. I should have stipulated that the routing helpers I posted were only meant as placeholders – Richard Peck Jan 31 '16 at 16:08
  • So how do I get that `current_cart` variable working? – tdog Jan 31 '16 at 16:20
  • Don't you have the code to support it? Last time I checked, it should be supported with some sort of session array, but I don't know because I don't know which tutorial you're using – Richard Peck Jan 31 '16 at 16:22
  • Do you have a github repo I could look at? – Richard Peck Jan 31 '16 at 16:39
  • Somehow my github configuration got all screwed up so I had to get that sorted out. https://github.com/TylerTaylor/depot – tdog Jan 31 '16 at 18:52
  • Okay as I thought, you don't have any code to define `current_cart` - I'll write an update – Richard Peck Jan 31 '16 at 21:20
  • Okay now I'm getting the same error but for `line_item`. Session stuff is definitely new to me, so I'll have to check out that link. – tdog Jan 31 '16 at 21:59
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/102178/discussion-between-rich-peck-and-tdog). – Richard Peck Jan 31 '16 at 22:00