[Ruby] Creating an EC site with Rails5 ⑨ ~Cart function~

4 minute read

Introduction

This is a continuation of the series of Create an EC site with Rails 5 ⑧, a series for creating an EC site where you can shop at a fictitious bakery. This time I will make a cart function. The appearance of the EC site is finally complete.

Source code

https://github.com/Sn16799/bakeryFUMIZUKI

Model Association

fumizuki_ER.jpg

The models related to the cart function are mainly Customer and Product. With the cart function, there is no such thing as the cart itself. When you select a product and press the “Add to cart” button, one item of data is registered for each product. The CartItem model acts as an intermediate table that stores only Customer and Product IDs and the number of products.

Controller

app/controllers/cart_items_controller.rb


class CartItemsController <ApplicationController

  before_action :authenticate_customer!
  before_action :set_cart_item, only: [:show, :update, :destroy, :edit]
  before_action :set_customer

  def create
    @cart_item = current_customer.cart_items.build(cart_item_params)
    @current_item = CartItem.find_by(product_id: @cart_item.product_id,customer_id: @cart_item.customer_id)
# Add new if there is no same product in cart, add with existing data if there is
    if @current_item.nil?
      if @cart_item.save
        flash[:success] ='The product has been added to your cart! '
        redirect_to cart_items_path
      else
        @carts_items = @customer.cart_items.all
        render'index'
        flash[:danger] ='Could not add product to cart. '
      end
    else
      @current_item.quantity += params[:quantity].to_i
      @current_item.update(cart_item_params)
      redirect_to cart_items_path
    end
  end

  def destroy
    @cart_item.destroy
    redirect_to cart_items_path
    flash[:info] ='You have canceled the cart item. '
  end

  def index
    @cart_items = @customer.cart_items.all
  end

  def update
    if @cart_item.update(cart_item_params)
      redirect_to cart_items_path
      flash[:success] ='The item in the cart has been updated! '
    end
  end

  def destroy_all #Delete all items in cart
    @customer.cart_items.destroy_all
    redirect_to cart_items_path
    flash[:info] ='The cart has been emptied. '
  end

  private

  def set_customer
    @customer = current_customer
  end

  def set_cart_item
    @cart_item = CartItem.find(params[:id])
  end

  def cart_item_params
    params.require(:cart_item).permit(:product_id, :customer_id, :quantity)
  end
end

If you write only save in the create action, when you try to buy the same product, the same product will be registered as separate data such as **Bread 1, bread 2, bread 1, ….. This happens due to the structure of the table, but it is convenient to be able to display the additional items added to the cart at a later time, so the processing is divided by if statements.

Also, I wanted to be able to delete the items in the cart individually and also to empty the contents of the cart all at once, a convenient destroy_all I found a method.

View

index screen

html:app/views/cart_items/index.html.erb


<div class="col-lg-10 offset-lg-1 space">
  <div class="container-fluid">
    <!-- title + delete all method -->
    <div class="row">
      <div class="col-lg-4">
        <h2>
          <span style="display: inline-block;">Shopping</span>
          <span style="display: inline-block;">Cart</span>
        </h2>
      </div>
      <div class="col-lg-4">
        <%= link_to'Empty Cart', destroy_all_cart_items_path, method: :delete, class:'btn btn-danger' %>
      </div>
    </div>

    <!-- Cart item list -->
    <div class="d-none d-lg-block">
      <div class="row space">
        <div class="col-lg-5"><h4>Product name</h4></div>
        <div class="col-lg-2"><h4>Unit price (tax included)</h4></div>
        <div class="col-lg-2"><h4>Quantity</h4></div>
        <div class="col-lg-2"><h4>Subtotal</h4></div>
      </div>
    </div>

    <% sum_all = 0 %>
    <% @cart_items.each do |cart_item| %>
    <div class="row space-sm">
      <div class="col-lg-3">
        <%= link_to product_path(cart_item.product) do %>
        <%= attachment_image_tag(cart_item.product, :image, :fill, 100, 100, fallback: "no_img.jpg") %>
        <% end %>
      </div>
      <div class="col-lg-2">
        <%= link_to product_path(cart_item.product) do %>
        <%= cart_item.product.name %>
        <% end %>
      </div>
      <div class="col-lg-2">
        <%= price_include_tax(cart_item.product.price) %>
      </div>
      <div class="col-lg-2">
        <%= form_with model: cart_item, local: true do |f| %>
        <%= f.number_field :quantity, value: cart_item.quantity, min:1, max:99 %>
        <%= f.submit "change", class: "btn btn-primary" %>
        <% end %>
      </div>
      <div class="col-lg-2">
        <%= sum_product = price_include_tax(cart_item.product.price).to_i * cart_item.quantity %> Yen
        <% sum_all += sum_product %>
      </div>
      <div class="col-lg-1">
        <%= link_to "delete", cart_item_path(cart_item), method: :delete, class: "btn btn-danger"%>
      </div>
    </div>
    <% end %>

    <!-- Total amount + Enter information -->
    <div class="row space">
      <div class="col-lg-2 offset-lg-7 space-sm">
        <%= link_to "Continue shopping", customer_top_path, class: "btn btn-danger "%>
      </div>
      <div class="col-lg-3 space-sm">
        <div class="row">
          <h4>Total amount: <%= sum_all %> Yen</h4>
        </div>
      </div>
    </div>
    <div class="row space">
      <div class="col-lg-3 offset-lg-9"><%= link_to "Proceed to enter information", new_order_path, class: "btn btn-danger btn-lg" %>
      </div>
    </div>

  </div>
</div>

The subtotal for each product and the total for all products are displayed by incorporating the calculation formula in each statement on the view. In general, it seems that it is not desirable to perform fine calculation or branch processing in view, is there another good way?

app/helpers/application_helper.rb


def price_include_tax(price)
  price = price * 1.08
  "#{price.floor} Yen"
end

It is a helper used to display the tax-included price of the product in the HTML above. The numbers after the decimal point are rounded down with a floor. For details on decimal point processing, see here.

app/assets/stylesheets/application.scss


.space-sm {
  padding-top: 20px;
}

Postscript

The implementation of the function has become a little complicated, and finally it has become interesting. Even if you look inside the site, you can feel a lot of shopping by putting products in the cart. After that, if you make an order (order information) area, the customer site is completed!

However, in this series, the admin site is also self-made, so the code amount is about half. However, the experience of the last time I made it was that the function of the Order model was extremely difficult, so it would be manageable if I could even do it.

Well, can I explain the most difficult Order model in an easy-to-understand manner? Continue to the next time!

Reference

Pikawaka [Ruby] Round off, round up, round down by specifying the number of decimal places!