Checking the url to see if user is on the checkout page. <% unless current_page?(new_order_path) %> <%= button_to ‘Checkout’, new_order_path, method: :get %> | <% end %>

The problem is that pressing “Checkout” again will clear anything the user has entered in the checkout.rhtml field.

ilari says:

You can disable both buttons when the checkout view is shown to the user. Modify _cart.rhtml with the following:

<% form_remote_tag :url => { :action => :empty_cart } do %>
  <%= submit_tag "Empty Cart", :disabled => !@order.nil? %>
<% end %>
<%= button_to "Checkout", {:action => :checkout}, {:disabled => !@order.nil?} %>

However, the user can still remove individual line items from the cart emptying the cart this way…

bryan says: Just ad if not @checkout to the button and set the @checkout to true in the checkout action of the store controller.


<%= button_to "Check out", :action => :checkout if not @checkout -%>

Eric asks:

Why does this works ? Reading ths code I see that button is disabled if order is empty. But when you are on the checkout page your cart isnt empty…So can anyone explain me why does this work ?

k9d adds:

I prefer the ilari’s method for stopping the user from checking out again over the rest because it doesn’t change the interface around on the user (the buttons remain, but are disabled) and the user can still see what they have in their cart.

Also I think it’s good to allow users to remove items from their cart at the checkout stage—many times it’s not until I check out that I realize I have two or something I thought I had only one of, etc. I tested ilari’s method and removing a cart item before hitting “Place Order” works great (only the items listed in the cart when you hit “Place Order” show up in the line_items in the database).

However, if the user empties the cart to zero items, they’re left staring at the checkout page with no way to cancel their order. This could be remedied a few different ways (“Cancel Order” button returning to index, automatically returning to index when there’s no items in the cart ala blind_up).

k9d asks:

There are plenty of answers on this page that solve this problem by restricting user behavior, but why not nip the problem in the bud and change the behavior of the “Checkout” button so that it only blanks the field out when no data has been previously entered? I’m having trouble getting that to work because of the trickery involved with the RAM copy of @order VS the one that gets saved to the database, can anyone guide me there?

Dave says:

One way to hide the checkout_button is to set a variable:


 @hide_checkout_button = true
You can then test for it in the layout:

<% if @hide_checkout_button -%>
   ...
<% end -%>

Because instance variable default to the value nil when they haven’t been assigned, this works fine if the non-checkout actions do not set the value.

You can take this a step further and set the variable inside the checkout view instead:

<% @hide_checkout_button = true -%>

Because the view template is evaluated before the layout, this variable will then be available for inspection in the layout.

Which is better depends on your application flow: what is the logical thing that determines you’re looking at a checkout screen?

Nicolas says:

To me the most straightforward answer to your last question, at this stage of development, is whether or not the @order instance variable is set or not. If it is, there is not much point clicking on “checkout”. This may change further along the process, but my solution is simply this, in _cart.rhtml:


<%= button_to "Checkout", :action => :checkout if @order.nil? %>

David M says:

It would probably make sense to hide the empty cart button as well on the checkout page to prevent a user from logging an empty order.

linoj says:

I had changed the template earlier to use form_remote_tag instead of button_to on Empty cart. So it seems cleaner to wrap both in an if-end (or is that too un-ruby-esque?)


<% if @order.nil? %>
   <%= button_to "Checkout", :action => :checkout %>

   <% form_remote_tag :url => { :action => :empty_cart } do %>
     <%= submit_tag "Empty Cart" %>
   <% end %>
<% end %>

Javier says:

You can also add this “OR”. It avoids re-checkouts and items removal:


<%= hidden_div_if(@cart.items.empty? || !@order.nil?, :id => "cart") %>
<%= render(:partial => "cart", :object => @cart) %>

Carsten says:

Why not sticking to what we have already? Using the previously developed hidden_div_if in _cart.rhtml is quite convenient:


<%= hidden_div_if !@order.nil? %>
  <%= button_to "Checkout", :action => :checkout %>
</div></div>

Anthony Ettinger says:

I think that hiding via javascript and disabling the button is not a good solution. What if javascript is disabled? What if the form submits via a text field in the cart added later on (ie: the user hits “enter”).

I think it makes sense to change the “Checkout” button to a “Cancel Order” button if you are in the checkout process…you can use the @order object to determine this in your cart.rhtml template:


<% if @order %>
  <!-- show "Cancel Order" button -->
<% else %>
  <!-- show "Checkout" button -->
  <!-- show "Empty Cart" button -->
<% end %>

Then you would need a “cancel_checkout” method in store_controller.rb. I simply made mine redirect to index inside store_controller.rb:


def cancel_order
  redirect_to_index("You have cancelled your order")
end

I left the cart full, in case they want to add more items, etc. At this point, the user can still empty the cart should they choose.

Side Note: With the ajax-ified “Empty Cart” button working, you are still left on the “Checkout” page if you empty it there. Perhaps “Empty Cart” should always redirect to index. This is negated by implementing a “Cancel Order” button that redirects to index.

Alan says:

There is a problem if you have AJAX click-to-remove on your cart and uses !@order.nil? to disable the buttons – At checkout, the buttons are disabled because the variable @order has been created, but when a user clicks on an item in the cart (to remove it), the cart updates and the buttons are enabled again. This is probably because @order is not present in the remove_from_cart action.

JP says:

Alan, you’re correct. If you keep the AJAX click-to-remove, the !@order.nil? doesn’t work to disable the buttons.

Adding a session variable for order does the trick

1. I set session[:order] to @order when @order is created

  def checkout
    @cart = find_cart
    if @cart.items.empty?
      redirect_to_index("Your cart is empty")
    else
      @order = Order.new
      session[:order] = @order
    end
  end
2. Check for session[:order] in the remove_to_cart class

def remove_from_cart
    begin
      if !session[:order].nil?
        @order = Order.new
      end

    # ...
3. Reset session[:order] to nil on save_order

  def save_order

  # ...

    if @order.save
      session[:cart] = nil
      session[:order] = nil
      redirect_to_index("Thank you for your order")

  # ...

  end
4. Reset session[:order] to nil on index (in case the user abandons the checkout process)

  def index
    @products = Product.find_products_for_sale
    @cart = find_cart
    session[:order] = nil
  end

Is there a more elegant solution? This works, but seems a bit more cumbersome than I had imagined.

Tom Says:

I changed two things to be able to hide both buttons upon clicking checkout. This is my first week with RoR? so please tell me if I am wrong here.

The first edit was on store_controller.rb, I changed the def checkout to the following:

def checkout
  @hide_buttons = true
  @cart = find_cart
  if @cart.items.empty?
    redirect_to_index('Your cart is empty')
  else    
    @order = Order.new
  end
end

I added the instance variable @hide_buttons to be set to true when that action was called. The only other edit came on the _cart.rhtml view.


<% if @hide_buttons != true -%>
  <%= button_to('Checkout', :action => :checkout) %>
  <br />
  <% form_remote_tag :url => { :action => :empty_cart } do -%>
    <%= submit_tag "Empty cart" %>
  <% end -%>
<% end -%>

This hides the buttons from view if the @hide_buttons variable is set to true. It works and both buttons do not show when you click checkout. Please let me know if there are problems with this method.

The only potential problems I can see with the @hide_buttons variable solution above are: *you need that variable set in at least on other place (when item is deleted from cart after checkout screen) *that you have logic in your view template (not strictly MVC-esque)

There is the related problem of saving an empty order. The save_order action should check if @cart is empty (like the checkout action does), before saving the order. It is not enough to simply disable or hide the “Empty cart” button, since you can go to the checkout page in one browser window, then open up another one and go to the index page and empty the cart from there. Going back to the checkout page and submitting the form will invoke the save_order action on an empty cart, and lead to bad data in our database (orders with no line_items).

Scott says:

Tom, here is an example of what you suggested re: checking if the cart is empty:


def save_order
  @cart = find_cart
  if @cart.items.empty?
    redirect_to_index('Your cart is empty')
  else
    @order = Order.new(params[:order])
    @order.add_line_items_from_cart(@cart)
    if @order.save
      session[:cart] = nil
      redirect_to_index('Thank you for your order')
    else
      render :action => :checkout
    end
  end
end

iFlash says:

I tried a completely different approach which seems to work with very little change in _cart.html.erb. Look at this:


<%= button_to "Empty Cart", :action => :empty_cart unless @order %>
<%= button_to "Checkout", :action => :checkout unless @order %>
The
unless @order 
was just intuition really, but it makes a good job.

Jinyoung says:

Does we have to hide the button? Play time quest on this book is just ask to disable the checkout button. :-)


class StoreController < ApplicationControlle
  # ...

  def checkout
    @cart = find_cart
    if @cart.empty?
      redirect_to_index("Your cart is empty" )
    else
      @order = Order.new
      @disable_checkout_button = true
    end
  end

  # ...

  # ...

  <!-- yeah... I use button_to instead of link_to. :-) -->
  <%= button_to "Checkout" , {:action => :checkout}, {:disabled => @disable_checkout_button} %>

  #...

McWild says:

There is another way to disable both buttons while checkout view is shown to the customer. We can use session for that!! So customer could change line items quantity and have both buttons(Empty cart, Checkout) disabled anyway until He/She leave the checkout page.


class StoreController < ApplicationControlle
  # ...

  def index
    @products = Product.find_products_for_sale    
    @cart = find_cart
    session[:disabled_button] = false
  end

  # ...

  # ...

  def checkout
    @cart = find_cart    
    if @cart.items.empty?
      redirect_to_index("Your cart is empty" )
    else
      @order = Order.new
      session[:disabled_button] = true
    end
  end

  # ...

  # ...

  def save_order
    @cart = find_cart
    @order = Order.new(params[:order])
    @order.add_line_items_from_cart(@cart)
    if @order.save
      session[:cart] = nil
      session[:disabled_button] = false
      redirect_to_index("Thank you for your order" )
    else
      render :action => :checkout
    end
  end

  # ...

  # ...

<% form_remote_tag :url => {:action => :empty_cart} do %>
  <%= submit_tag "Empty cart", :confirm => "Are you sure, that you want empty all products in your cart?", :style => 'float:left;', :disabled => session[:disabled_button] %>
<% end %>
<%= button_to "Checkout" , {:action => :checkout}, {:style => 'float:right; margin-right:5px;', :disabled => session[:disabled_button]} %>

  #...

Carnator says:

The question is about disabling the button. Here is what I used:


# ...
<%= button_to "Checkout", {:action => 'checkout'}, :disabled => !@order.nil? %>
# ...

The button_to helper has an HTML option called :disabled, so if the order is nil, then a negation will tell the :disabled option to activate.

Kedar Mhaswade says:

I think most of the suggestions above work for me as well. Thank you. I also thought that showing “Check Out” and “Empty” buttons when the cart is empty can be avoided too. Thus, my cart partial looks like this:


<% unless @cart.empty? %> 
  <%= button_to "Checkout!", {:action => :checkout}, {:disabled => @checkout_in_progress}%>
  <%= button_to "Empty", {:action => :empty_cart}, {:disabled => @checkout_in_progress, :class => "empty-button"} %>
<% end %>
The variable

@checkout_in_progress
is toggled (appropriately) in save_order and checkout methods of store controller. Does anyone have any comments on this?

Mike says:

I must be dense. What am I missing?

  1. the cart is only displayed if there are items in it, so we really don’t care about the button when it’s empty
  2. the only time we don’t want to display the checkout button is when we are creating the order, but we want to display the cart then and also the empty-the-cart button.

So it seems to me that adding a conditional to the button_to method in _cart.html.erb should do it:


<%= button_to "Checkout", new_order_path, :method => :get if @order.nil? %>

Am I wrong?

Jim says:

Based on what the author wrote above I simply added this:

At the top of app/views/orders/_form.html.erb


<% @hide_checkout_button = true -%>

and in app/views/carts/_cart.html.erb


<% unless @hide_checkout_button == true %>
    <%= button_to 'Checkout', new_order_path, :method => :get %>
<% end %>
It seems to work.

Alan says: (2011.06.20)

This page may be confusing for users of the Fourth edition as some of the posted code is for older versions.

That said, this code works fine for me:

<% if @order.nil? %>
  <%= button_to "Checkout", new_order_path, :method => :get %>
<% end %>

Dave says: (2011.10.19)

Keep it simple?

I originally used…


<%= button_to 'Checkout', new_order_path, method: :get, :disabled => !@order.nil? %>

... until my far more experienced friend suggested this…


<%= button_to 'Checkout', new_order_path, method: :get, :disabled => @order.present? %>

emtw says: (2012.06.11)

Is it possible to do it this way? I’ve added code to remove the Checkout and Empty Cart buttons, removed the increase and decrease buttons (if you did that in another playtime), and also added a link to go back to the store in case the customer wanted to change their order. I’m new to Rails, so would like to know what people think, thanks!

app/views/carts/cart.html.erb :

<% if @order.nil? %>
<%= button_to "Checkout", new_order_path, method: :get %>
<%= button_to 'Empty cart', cart, method: :delete,
    confirm: 'Are you sure?', remote: true %>
<% end %>
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>
  <td><%= line_item.product.title %></td>
  <td class="item_price"><%= number_to_currency(line_item.total_price) %></td>
  <% if @order.nil? %>
  <td><%= link_to "+1", increase_line_item_path(line_item), :method => :put, remote: true %></td>
  <td><%= link_to "-1", decrease_line_item_path(line_item), :method => :put, remote: true %></td>
  <% end %>
</tr>

app/views/orders/_form.html.erb :

  <div class="actions">
    <%= f.submit 'Place Order' %>
  </div>
  <div class="actions">
      <%= link_to "Back to Store", store_path, :method => :get %>
  </div>

That seems to me to remove some of the chance of submitting an empty order by using the increase/decrease buttons, and also gives the chance to go back to the store and change the order. What do you reckon?