[Ruby] Creating an EC site with Rails 5 ⑩ ~Creating an order function~

9 minute read

Introduction

This is a continuation of the series of Create an EC site with Rails 5 ⑨,aseriesforcreatinganECsitewhereyoucanshopatafictitiousbakery.WewillimplementaroundtheOrdermodel(orderfunction), which is the main function and the most difficult part of this site.

The user’s movement is as follows.

Put the product in the cart and press the "Go to order screen" button
↓
Order information input (orders/new) screen
·Payment Method
·Addressee
Enter, and press the "Proceed to confirmation screen" button
↓
Order information confirmation (orders/confirm) screen
Confirm the contents and press the "Confirm order" button
↓
Display orders/thanks screen (static page)

You can check the order history on the list and details screen

The point is that the delivery address entered on the orders/new screen can be selected from “my address”, “registered address”, and “new address”. If you select a new address and enter the contents in the form on the new screen, it will be saved as Order data and also as new data in the Address model. Nesting forms and going through a confirmation screen before saving the data are factors that make this feature difficult to implement.

Source code

https://github.com/Sn16799/bakeryFUMIZUKI

Model Association

fumizuki_ER.jpg

Controller

app/controllers/orders_controller.rb


class OrdersController <ApplicationController
  before_action :authenticate_customer!
  before_action :set_customer

  def index
    @orders = @customer.orders
  end

  def create
    if current_customer.cart_items.exists?
      @order = Order.new(order_params)
      @order.customer_id = current_customer.id

  #Adjust arguments depending on radio button selection of address
      @add = params[:order][:add].to_i
      case @add
        when 1
          @order.post_code = @customer.post_code
          @order.send_to_address = @customer.address
          @order.addressee = full_name(@customer)
        when 2
          @order.post_code = params[:order][:post_code]
          @order.send_to_address = params[:order][:send_to_address]
          @order.addressee = params[:order][:addressee]
        when 3
          @order.post_code = params[:order][:post_code]
          @order.send_to_address = params[:order][:send_to_address]
          @order.addressee = params[:order][:addressee]
      end
      @order.save

      Address model search with #send_to_address, create new if no corresponding data
      if Address.find_by(address: @order.send_to_address).nil?
        @address = Address.new
        @address.post_code = @order.post_code
        @address.address = @order.send_to_address
        @address.addressee = @order.addressee
        @address.customer_id = current_customer.id
        @address.save
      end

      Newly register the contents of # cart_items in order_items
      current_customer.cart_items.each do |cart_item|
        order_item = @order.order_items.build
        order_item.order_id = @order.id
        order_item.product_id = cart_item.product_id
        order_item.quantity = cart_item.quantity
        order_item.order_price = cart_item.product.price
        order_item.save
        cart_item.destroy Deletes cart_item after transferring information to #order_item
      end
      render :thanks
    else
      redirect_to customer_top_path
Flash[:danger] ='The cart is empty. '
    end
  end

  def show
    @order = Order.find(params[:id])
    if @order.customer_id != current_customer.id
      redirect_back(fallback_location: root_path)
      flash[:alert] = "Access failed."
    end
  end

  def new
    @order = Order.new
  end

  def confirm
    @order = Order.new
    @cart_items = current_customer.cart_items
    @order.how_to_pay = params[:order][:how_to_pay]
# Adjust arguments depending on radio button selection of address
    @add = params[:order][:add].to_i
    case @add
      when 1
        @order.post_code = @customer.post_code
        @order.send_to_address = @customer.address
        @order.addressee = @customer.family_name + @customer.first_name
      when 2
        @sta = params[:order][:send_to_address].to_i
        @send_to_address = Address.find(@sta)
        @order.post_code = @send_to_address.post_code
        @order.send_to_address = @send_to_address.address
        @order.addressee = @send_to_address.addressee
      when 3
        @order.post_code = params[:order][:new_add][:post_code]
        @order.send_to_address = params[:order][:new_add][:address]
        @order.addressee = params[:order][:new_add][:addressee]
    end
  end

  def thanks
  end

  private
  def set_customer
    @customer = current_customer
  end

  def order_params
    params.require(:order).permit(
      :created_at, :send_to_address, :addressee, :order_status, :how_to_pay, :post_code, :deliver_fee,
      order_items_attributes: [:order_id, :product_id, :quantity, :order_price, :make_status]
      )
  end

end

It would be nice if the information sent by form_with could be saved as is, as usual, but form_with cannot sandwich the confirmation screen. Therefore, the confirm and create actions fetch the parameters received from the view in the form of params[:hoge].

Also, in create, within one action, “Create new Order data” “Move product data from CartItem to OrderItem (Create new OrderItem and delete registered CartItem)”** Only if there is no matching data in the Address model, you need to complete the 3 new registrations **”. Each operation is not complicated, but the total code amount is quite large.

View

new

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


<div class="col-lg-10 offset-1 space">

  <div class="row">
    <div class="col-lg-4">
      <h2>Enter order information</h2>
    </div>
  </div>

  <%= form_with(model: @order, local: true, url: {action:'confirm'}) do |f| %>

  <!-- payment method -->
  <div class="row space">
    <h3><strong><%= f.label: payment method %></strong></h3>
  </div>

  <div class="row"><div class="col-lg-4 btn-group" data-toggle="buttons">
      <label class="btn btn-outline-secondary active" style="width:50%">
        <%= f.radio_button :how_to_pay, true, {checked: true} %> Credit Card
      </label>
      <label class="btn btn-outline-secondary" style="width:50%">
        <%= f.radio_button :how_to_pay, false, {} %> Bank transfer
      </label>
    </div>
  </div>

  <!-- Delivery address -->
  <div class="row space">
    <h3><strong><%= f.label :Delivery %></strong></h3>
  </div>
  <!-- Your address -->
  <div class="row">
    <p>
      <label><%= f.radio_button :add, 1, checked: true, checked: "checked" %> your address </label><br>
      <%= @customer.post_code %>
      <%= @customer.address %>
      <%= @customer.full_name %>
    </p>
  </div>

  <!-- Registered address -->
  <div class="row space-sm">
    <p>
      <label><%= f.radio_button :add, 2, style: "display: inline-block" %>Select from registered address</label><br>
      <%= f.collection_select :send_to_address, @customer.addresses, :id, :address %>
    </p>
  </div>

  <!-- New address -->
  <div class="row space-sm">
    <p><label><%= f.radio_button :add, 3 %>new delivery address</label></p>
  </div>
  <div class="row">
    <div class="col-lg-12">
      <%= f.fields_for :new_add do |na| %>
      <div class="row">
        <div class="col-lg-3">
          <strong>Postal code (without hyphens)</strong>
        </div>
        <div class="col-lg-6">
          <%= na.text_field :post_code, class:'form-control' %>
        </div>
      </div>

      <div class="row">
        <div class="col-lg-3">
          <strong>Address</strong>
        </div>
        <div class="col-lg-6">
          <%= na.text_field :address, class:'form-control' %>
        </div>
      </div>

      <div class="row">
        <div class="col-lg-3">
          <strong>Address</strong>
        </div>
        <div class="col-lg-6">
          <%= na.text_field :addressee, class:'form-control' %>
        </div>
      </div>
      <% end %>
    </div>
  </div>
  <!-- To the destination here -->

  <div class="row space">
    <div class="col-lg-2 offset-lg-7">
      <%= f.submit "Proceed to confirmation screen", class: "btn btn-danger"%>
    </div>
  </div>

  <% end %>
</div>

confirm

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


<div class="col-lg-10 offset-1 space">
  <div class="row">
    <h2>Check order information</h2>
  </div>

  <%= form_with(model: @order, local: true) do |f| %>

  <div class="d-none d-lg-block space">
    <div class="row">
      <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">
      <%= cart_item.quantity %>
    </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>
  <% end %>

  <div class="row space">
    <div class="col-lg-12">
      <div class="row">
        <div class="col-lg-3">
          <strong>Shipping</strong>
        </div>
        <div class="col-lg-3">
          <%= @order.deliver_fee %> Yen
        </div>
      </div>
      <div class="row">
        <div class="col-lg-3">
          <strong>Product total</strong>
        </div>
        <div class="col-lg-3">
          <%= sum_all.to_i %> Yen
        </div>
      </div>
      <div class="row">
        <div class="col-lg-3">
          <strong>Billing amount</strong>
        </div>
        <div class="col-lg-3">
          <% billling_amount = sum_all + @order.deliver_fee.to_i %>
          <%= billling_amount.to_i %> Yen
        </div>
      </div>
    </div>
  </div>

  <div class="row space-sm">
    <div class="col-lg-2">
      <h3>Payment method</h3>
    </div>
    <div class="col-lg-4">
      <%= how_to_pay(@order.how_to_pay) %>
    </div>
  </div>

  <div class="row space-sm">
    <div class="col-lg-2">
      <h3>Delivery address</h3>
    </div>
    <div class="col-lg-4">
      <%= @order.post_code %>
      <%= @order.send_to_address %>
      <%= @order.addressee %>
    </div>
  </div>

  <%= f.hidden_field :customer_id, :value => current_customer.id %>
  <%= f.hidden_field :post_code, :value => "#{@order.post_code}" %>
  <%= f.hidden_field :send_to_address, :value => "#{@order.send_to_address}" %>
  <%= f.hidden_field :addressee, :value => "#{@order.addressee}" %>
</div>

<div class="row space">
  <div class="col-lg-2 offset-lg-5">
    <%= f.submit "Confirm purchase", class: "btn btn-danger btn-lg" %>
  </div>
</div>


<% end %>
</div>

thanks

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


<div class="col-lg-10 offset-1 space">
  <h2> Thank you for your purchase! </h2>
  <h3><%= link_to'Return to TOP', customer_top_path %></h3>
</div>

This page is displayed after clicking the “Confirm purchase” button. Since it is a static page and there is no such function, it is good to put only plain language as above, or to display a photo with a slider etc.

index

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


<div class="col-lg-10 offset-1 space">

  <div class="row">
    <h2>Order history list</h2>
  </div>

  <div class="d-none d-lg-block space">
    <div class="row">
      <div class="col-lg-2">Order date</div>
      <div class="col-lg-3">Shipping destination</div>
      <div class="col-lg-2">Order product</div>
      <div class="col-lg-2">Payment amount</div>
      <div class="col-lg-1">Status</div>
      <div class="col-lg-2">Order details</div>
    </div>
  </div>

  <% @orders.each do |order| %>
  <div class="row space-sm">
    <div class="col-lg-2">
      <%= simple_time(order.created_at) %>
    </div>
    <div class="col-lg-3">
      <div class="row">
        <%= order.post_code + "" + order.send_to_address %>
      </div>
      <div class="row">
        <%= order.addressee %>
      </div>
    </div>
    <div class="col-lg-2">
      <% sum_all = 0 %>
      <% order.order_items.each do |order_item| %>
      <%= order_item.product.name %><br>
      <% sub_total = price_include_tax(order_item.order_price).to_i * order_item.quantity %>
      <% sum_all += sub_total.to_i %>
      <% end %>
    </div>
    <div class="col-lg-2">
      <%= sum_all += order.deliver_fee.to_i %> Yen
    </div>
    <div class="col-lg-1">
      <%= order_status(order) %>
    </div>
    <div class="col-lg-2">
      <%= link_to'display', order_path(order), class: "btn btn-sm btn-danger" %>
    </div>
  </div>
  <% end %>

</div>

show

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


<div class="col-lg-10 offset-1 space">
  <div class="row">
    <h2>Order history details</h2>
  </div>

  <div class="row">
    <div class="col-lg-7">
      <div class="row space">
        <h3>Order information</h3>
      </div>
      <div class="row">
        <div class="container">
          <div class="row space-sm">
            <div class="col-lg-3">
              <strong>Order date</strong>
            </div>
            <div class="col-lg-9">
              <%= simple_time(@order.created_at) %>
            </div>
          </div>
          <div class="row space-sm">
            <div class="col-lg-3">
              <strong>Shipping destination</strong>
            </div>
            <div class="col-lg-9">
              <%= @order.send_to_address %>
            </div>
          </div>
          <div class="row space-sm">
            <div class="col-lg-3">
              <strong>Payment method</strong>
            </div>
            <div class="col-lg-9">
              <%= how_to_pay(@order.how_to_pay) %>
            </div>
          </div>
          <div class="row space-sm">
            <div class="col-lg-3">
              <strong>Status</strong>
            </div>
            <div class="col-lg-9">
              <%= order_status(@order) %>
            </div>
          </div>
        </div>
      </div>

      <div class="row space">
        <h3>Order details</h3>
      </div>
      <div class="d-none d-lg-block">
        <div class="row">
          <div class="col-lg-4">
            <strong>Products</strong>
          </div>
          <div class="col-lg-3">
            <strong>Unit price (tax included)</strong>
          </div>
          <div class="col-lg-2">
            <strong>number</strong>
          </div>
          <div class="col-lg-2">
            <strong>Subtotal</strong>
          </div>
        </div>
      </div>
      <% sum_all = 0 %>
      <% @order.order_items.each do |order_item| %>
      <div class="row space-sm">
        <div class="col-lg-4">
          <%= order_item.product.name %>
        </div>
        <div class="col-lg-3">
          <%= price_include_tax(order_item.order_price) %>
        </div>
        <div class="col-lg-2">
          <%= order_item.quantity %> pieces
        </div>
        <div class="col-lg-2">
          <%= sub_total = price_include_tax(order_item.order_price).to_i * order_item.quantity %> Yen
          <% sum_all += sub_total %>
        </div>
      </div>
      <% end %>
    </div>

    <div class="col-lg-5">
      <div class="row space">
        <h3>Billing information</h3>
      </div>
      <div class="row">
        <div class="container">
          <div class="row space-sm">
            <div class="col-lg-6">
              <strong>Product total</strong>
            </div>
            <div class="col-6">
              <%= sum_all %> Yen
            </div>
          </div>
          <div class="row space-sm">
            <div class="col-lg-6">
              <strong>Shipping charges</strong>
            </div>
            <div class="col-lg-6">
              <%= @order.deliver_fee %> Yen
            </div>
          </div>
          <div class="row space-sm">
            <div class="col-lg-6">
              <strong>Billing amount</strong>
            </div>
            <div class="col-lg-6">
              <%= sum_all + @order.deliver_fee.to_i %> Yen
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
```![orders_show.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647951/04c8ce70-fadd-9f0c-3343-0c0c66022be5.jpeg)

In the above figure, I want to display the total price of the product in the "Billing information" column, but if I build the screen in order from the top, I have to turn a loop for "Billing information" and "Order details" twice It will be a hassle. So, thinking that it's a sloppy way, split the screen vertically with col and first create a column of "order information" "order content", and then calculate the total amount in the "order content" loop It stores it in a variable and calls the variable in the "Billing information" column.
(In the "Billing information" column, I forgot to set the default value of the shipping fee in the DB, so the total amount charged is the same as the product total. It is a specification to be added, and the view code also corresponds to it.)

# Postscript
There was only a central function of this EC site, so there was a lot of code and the content was complicated. In this implementation, while roughly following what I made in the team implementation of the school in the rough part, we modified the methods such as newly defining helper and model methods to make the code easier to see and the screen to be responsive. Was added.

If you select a new address with the create action, it will be added to the specifications that address data will be registered in Address as well as Order. In the above description, the flow is to search the address column of the Address model and register if there is no match. For a more accurate search,

unless Address.find_by(addressee: @order.addressee, address: @order.send_to_address).exists? ``` As a result, it may be possible to check whether the address and address match. The code will be extra long.

After all, the functionality of the customer site is now complete. Happy New Year.