Chapter 14

注文履歴に関する機能を実装しよう(管理者)

FarStep
FarStep
2023.02.27に更新

はじめに

本 Chapter では、管理者側の注文履歴に関する機能を実装していきます。
実装内容は、前 Chapter で扱った顧客側の注文履歴に関する機能とほぼ同じです。

コントローラを作成しよう

コンテナが立ち上がっていることが確認できましたら、ecommerce_web コンテナに入ります。
その後、下記コマンドを実行して、必要なコントローラを作成しましょう。

今回、注文履歴の一覧については、管理者側のトップページに表示します。
そのため、まずは PagesController を作成します。

$ rails g controller admin/pages home --no-helper

続いて、OrdersController を作成しましょう。

$ rails g controller admin/orders show --no-helper

続いて、ルーティングの設定を行います。
config/routes.rb を開いて下記コードを追加してください。

config/routes.rb
Rails.application.routes.draw do
  devise_for :admins, controllers: {
    sessions: 'admin/sessions'
  }
  devise_for :customers, controllers: {
    sessions: 'customer/sessions',
    registrations: 'customer/registrations'
  }
  root to: 'pages#home'
  namespace :admin do
+   root to: 'pages#home'
    resources :products, only: %i[index show new create edit update]
+   resources :orders, only: %i[show update]
  end
  scope module: :customer do
    resources :products, only: %i[index show]
    resources :cart_items, only: %i[index create destroy] do
      member do
        patch 'increase'
        patch 'decrease'
      end
    end
    resources :checkouts, only: [:create]
    resources :webhooks, only: [:create]
    resources :orders, only: %i[index show] do
      collection do
        get 'success'
      end
    end
  end

  get '/up/', to: 'up#index', as: :up
  get '/up/databases', to: 'up#databases', as: :up_databases
end

namespace :admin ブロックの中で、 root to: 'pages#home' と記述すると、admin/ という URL にアクセスしたときに、PagesController の home アクションが呼び出されるようになります。

生成されたパスを確認してみましょう。

$ rails routes | grep -e orders -e pages | grep admin

grep を用いて表示するルーティングを絞りました。

 admin_root GET    /admin(.:format)               admin/pages#home
admin_order GET    /admin/orders/:id(.:format)    admin/orders#show
            PATCH  /admin/orders/:id(.:format)    admin/orders#update
            PUT    /admin/orders/:id(.:format)    admin/orders#update

上記のように、想定通りのパスとコントローラが指定されていますね。

PagesController

では次に、コントローラのアクションの中身を記述していきます。
app/controllers/admin/pages_controller.rb を開いて下記コードを記述してください。

app/controllers/admin/pages_controller.rb
class Admin::PagesController < ApplicationController
  before_action :authenticate_admin!

  def home
    @orders, @selected = get_orders(params)
    today_orders = Order.created_today
    @today_total_orders = total_orders(today_orders)
    @today_total_sales = total_sales(today_orders)
  end

  private

  def get_orders(params)
    if !params[:status].present? || !Order.statuses.keys.to_a.include?(params[:status])
      return [Order.latest,
              'all']
    end

    get_by_enum_value(params[:status])
  end

  def get_by_enum_value(status)
    case status
    when 'waiting_payment'
      [Order.latest.waiting_payment, 'waiting_payment']
    when 'confirm_payment'
      [Order.latest.confirm_payment, 'confirm_payment']
    when 'shipped'
      [Order.latest.shipped, 'shipped']
    when 'out_of_delivery'
      [Order.latest.out_of_delivery, 'out_of_delivery']
    when 'delivered'
      [Order.latest.delivered, 'delivered']
    end
  end

  def total_orders(orders)
    orders.count
  end

  def total_sales(orders)
    orders.sum(:billing_amount)
  end
end

アクションは一つだけですが、

  • 注文履歴を status カラムの値で絞り込む
  • 一日の売り上げ、注文数をビューで表示する

といったことを想定して、少々複雑になっています。一行一行確認していきましょう。

まず home アクションの一行目で、ビューから送信された params[:status] の値によって、取得する注文履歴を変化させています。ちなみに、@selected というインスタンス変数は、絞り込み検索を行うセレクトボックスの値を制御するためのものです。
get_orders メソッド内では

  • params[:status] が存在しない
  • params[:status] の値が enum で設定したキー以外である

場合に、orders テーブルに登録されている全レコードを最新のものから順に 全て 取得しています。
もしも、

  • params[:status] が存在する
  • params[:status] の値が enum で設定したキーのいずれかである

場合には、params[:status] で指定されたステータスの orders を最新のものから順に取得しています。

get_by_enum_value メソッド内では、これから定義するスコープを用いて、params[:status] で指定された enum のキーの値から該当レコードを取得しています。
それでは、enum で指定されたキーの値からレコードを取得するためにスコープを定義してあげましょう。app/models/order.rb を開いて、下記コードを追加してください。

app/models/order.rb
class Order < ApplicationRecord
  belongs_to :customer
  enum status: {
    waiting_payment: 0,
    confirm_payment: 1,
    shipped: 2,
    out_of_delivery: 3,
    delivered: 4
  }
  has_many :order_details, dependent: :destroy

+ scope :waiting_payment, -> { where(status: 'waiting_payment') }
+ scope :confirm_payment, -> { where(status: 'confirm_payment') }
+ scope :shipped, -> { where(status: 'shipped') }
+ scope :out_of_delivery, -> { where(status: 'out_of_delivery') }
+ scope :delivered, -> { where(status: 'delivered') }
end

今回追加したスコープでは、where 文を使うことで status の値からレコードを取得するようにしています。
これで、商品一覧画面に検索フォームを設置すると、status によるデータの絞り込みが実現します。

そして、home アクションの二行目以降は、一日の売り上げ、注文数を算出するためのコードです。
これから定義する created_today というスコープを用いて過去一日のデータを全て取得します。
その後、そのデータ数・全データの billing_amount カラムの合計値を取得することで、一日の売り上げ、注文数が算出できるというわけです。

それでは、まずスコープを定義しましょう。
app/models/order.rb を開いて下記コードを記述してください。

app/models/order.rb
class Order < ApplicationRecord
  belongs_to :customer
  enum status: {
    waiting_payment: 0,
    confirm_payment: 1,
    shipped: 2,
    out_of_delivery: 3,
    delivered: 4
  }
  has_many :order_details, dependent: :destroy

  scope :waiting_payment, -> { where(status: 'waiting_payment') }
  scope :confirm_payment, -> { where(status: 'confirm_payment') }
  scope :shipped, -> { where(status: 'shipped') }
  scope :out_of_delivery, -> { where(status: 'out_of_delivery') }
  scope :delivered, -> { where(status: 'delivered') }

+ scope :created_today, -> { where('orders.created_at >= ?', Time.zone.now.beginning_of_day) }
end

上記のように、Time.zone.now.beginning_of_day とすると、一日の始まり(0:00)を表す Time を返してくれます。この値と where 文を組み合わせることで、その日の 0:00 以降のデータを取得することができます。

過去一日のデータが取得できたら、total_orders メソッドと total_sales メソッドにそのデータを渡します。

まず、total_sales メソッドは、一日の売り上げを返すメソッドです。
sum メソッドを用いて、取得した全レコードの billing_amount カラムの値の合計値を算出しています。
そして、total_orders メソッドです。このメソッドは、一日の注文数を返すメソッドです。
count メソッドを用いてレコード数を取得しています。

少々複雑でしたが、PagesController の解説は以上になります。

OrdersController

続いて、app/controllers/admin/orders_controller.rb を開いて下記コードを記述しましょう。

app/controllers/admin/orders_controller.rb
class Admin::OrdersController < ApplicationController
  before_action :authenticate_admin!
  before_action :set_order

  def show; end

  def update
    @order.update(order_params)
    redirect_to admin_order_path(@order), notice: 'Successfully updated order status'
  end

  private

  def set_order
    @order = Order.find(params[:id])
  end

  def order_params
    params.require(:order).permit(:status)
  end
end

通常の CRUD 機能を処理するコントローラとなっています。
一つ注意していただきたいのは、order_params メソッドの中身です。
今回、order は status のみの編集となるので、許可するパラメータは status のみです。
そのため、permit(:status) としています。

ビューを作成しよう

それでは、最後に Tailwind CSS を使ってビューを作成していきましょう。
まずは、注文履歴の一覧画面です。app/views/admin/pages/home.html.erb を開いてください。

app/views/admin/pages/home.html.erb
<div class="w-full mx-auto p-6">
  <div class="flex flex-wrap">
    <div class="w-full max-w-full px-3 mb-6 md:w-1/2 sm:flex-none">
      <div class="flex flex-col min-w-0 break-words bg-white shadow-md rounded-xl bg-clip-border">
        <div class="p-4">
          <div class="flex justify-center items-center">
            <div class="w-2/3 max-w-full px-3">
              <div>
                <p class="mb-0 font-sans font-semibold leading-normal text-sm">Today's Sales</p>
                <p class="mb-0 font-bold text-xl">
                  <%= number_to_currency(@today_total_sales, unit: "¥", strip_insignificant_zeros: true) %>
                </p>
              </div>
            </div>
            <div class="px-3 text-right basis-1/3 flex items-center justify-end">
              <div class="inline-block w-12 h-12 text-center rounded-lg bg-gradient-to-tl from-purple-700 to-pink-500">
                <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="w-full p-3 text-white" viewBox="0 0 16 16">
                  <path d="M4 11H2v3h2v-3zm5-4H7v7h2V7zm5-5v12h-2V2h2zm-2-1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1h-2zM6 7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7zm-5 4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-3z"/>
                </svg>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="w-full max-w-full px-3 mb-6 md:w-1/2 sm:flex-none">
      <div class="flex flex-col min-w-0 break-words bg-white shadow-md rounded-xl bg-clip-border">
        <div class="p-4">
          <div class="flex justify-center items-center">
            <div class="w-2/3 max-w-full px-3">
              <div>
                <p class="mb-0 font-sans font-semibold leading-normal text-sm">Today's Orders</p>
                <p class="mb-0 font-bold text-xl">
                  <%= @today_total_orders %>
                </p>
              </div>
            </div>
            <div class="px-3 text-right basis-1/3 flex items-center justify-end">
              <div class="inline-block w-12 h-12 text-center rounded-lg bg-gradient-to-tl from-purple-700 to-pink-500">
                <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="w-full p-3 text-white" viewBox="0 0 16 16">
                  <path d="M0 1.5A.5.5 0 0 1 .5 1H2a.5.5 0 0 1 .485.379L2.89 3H14.5a.5.5 0 0 1 .491.592l-1.5 8A.5.5 0 0 1 13 12H4a.5.5 0 0 1-.491-.408L2.01 3.607 1.61 2H.5a.5.5 0 0 1-.5-.5zM3.102 4l1.313 7h8.17l1.313-7H3.102zM5 12a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm7 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-7 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm7 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
                </svg>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<%= form_with url: admin_root_path, method: :get, class: "px-9", local: true  do |f|  %>
  <div class="flex items-center">
    <h1 class="text-xl font-semibold leading-5 text-gray-800">Order Status</h1>
    <%= f.select :status, options_for_select(Order.statuses.keys.to_a, @selected), { prompt: "all" }, {class: "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-3 appearance-none ml-4"} %>
    <%= f.submit "Search", class: "inline-flex items-center justify-center rounded-md bg-indigo-500 p-3 text-white duration-100 ease-in-out hover:bg-indigo-600 focus:outline-none ml-4 cursor-pointer" %>
  </div>
<% end %>

<div class="flex flex-wrap p-6">
  <div class="flex-none w-full max-w-full px-3">
    <div class="relative flex flex-col min-w-0 mb-6 break-words bg-white border-0 border-transparent border-solid rounded-2xl bg-clip-border">
      <div class="flex-auto px-0 pt-0 pb-2">
        <div class="p-0 overflow-x-auto">
          <table class="items-center w-full mb-0 align-top border-gray-200 text-slate-500">
            <thead class="align-bottom">
              <tr>
                <th class="px-6 py-3 font-bold text-left uppercase align-middle bg-transparent border-b border-gray-200 shadow-none text-lg border-b-solid tracking-none whitespace-nowrap text-slate-400">Customer</th>
                <th class="px-6 py-3 pl-2 font-bold text-left uppercase align-middle bg-transparent border-b border-gray-200 shadow-none text-lg border-b-solid tracking-none whitespace-nowrap text-slate-400">Billings</th>
                <th class="px-6 py-3 font-bold text-center uppercase align-middle bg-transparent border-b border-gray-200 shadow-none text-lg border-b-solid tracking-none whitespace-nowrap text-slate-400">Status</th>
                <th class="px-6 py-3 font-bold text-center uppercase align-middle bg-transparent border-b border-gray-200 shadow-none text-lg border-b-solid tracking-none whitespace-nowrap text-slate-400">Order date</th>
                <th class="px-6 py-3 font-semibold capitalize align-middle bg-transparent border-b border-gray-200 border-solid shadow-none tracking-none whitespace-nowrap text-slate-400"></th>
              </tr>
            </thead>
            <tbody>
              <% @orders.each do |order| %>
                <tr>
                  <td class="p-2 align-middle bg-transparent border-b whitespace-nowrap shadow-transparent">
                    <div class="flex px-2 py-1">
                      <div class="flex flex-col justify-center">
                        <p class="mb-0 leading-normal text-md"><%= order.customer.email %></p>
                      </div>
                    </div>
                  </td>
                  <td class="p-2 align-middle bg-transparent border-b whitespace-nowrap shadow-transparent">
                    <p class="font-semibold leading-tight text-md"><%= number_to_currency(order.billing_amount, unit: "¥", strip_insignificant_zeros: true) %></p>
                  </td>
                  <td class="p-2 leading-normal text-center align-middle bg-transparent border-b text-sm whitespace-nowrap shadow-transparent">
                    <span class="bg-blue-100 text-blue-800 text-xs font-semibold p-2 rounded"><%= order.status %></span>
                  </td>
                  <td class="p-2 text-center align-middle bg-transparent border-b whitespace-nowrap shadow-transparent">
                    <span class="font-semibold leading-tight text-md"><%= l order.created_at, format: :short %></span>
                  </td>
                  <td class="p-2 align-middle bg-transparent border-b whitespace-nowrap shadow-transparent">
                    <%= link_to admin_order_path(order), class: "font-semibold leading-tight text-sm text-slate-400" do %>
                      Details
                    <% end %>
                  </td>
                </tr>
              <% end %>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
</div>

status でレコードを絞り込むためのフォームが用意されています。select タグ内の options_for_select では、第一引数に 選択肢として表示したい値 を、第二引数に 初期値 を設定しています。第二引数には、コントローラで定義した @selected を渡しています。

それでは、管理者でログインした後、http://localhost:8000/admin にアクセスしてみましょう。

上記のように、

  • 顧客の注文履歴一覧
  • 一日の売り上げ
  • 一日の注文数
  • 検索フォーム

が表示されていれば OK です。

また、status によるデータの絞り込みが正常に動作するか確認してみてください。

上記のように、status の値によってデータの絞り込みができていれば OK です。

注文一覧が表示できたところで、ナビゲーションバーに注文一覧画面へのリンクを追加しましょう。
app/views/layouts/application.html.erb を開いてください。
商品一覧画面へのリンクのコードの上に、注文一覧画面へのリンクを追加してください。

app/views/layouts/application.html.erb
<li class='px-4 text-white no-underline'>
  <%= link_to admin_root_path do %>
    <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="w-full p-3 text-white" viewBox="0 0 16 16">
      <path d="M4 11H2v3h2v-3zm5-4H7v7h2V7zm5-5v12h-2V2h2zm-2-1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1h-2zM6 7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7zm-5 4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-3z"/>
    </svg>
  <% end %>
</li>
<li class='px-4 text-white no-underline'>
  <%= link_to admin_products_path do %>
    <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd">
      <path fill="white" d="M11.5 23l-8.5-4.535v-3.953l5.4 3.122 3.1-3.406v8.772zm1-.001v-8.806l3.162 3.343 5.338-2.958v3.887l-8.5 4.534zm-10.339-10.125l-2.161-1.244 3-3.302-3-2.823 8.718-4.505 3.215 2.385 3.325-2.385 8.742 4.561-2.995 2.771 2.995 3.443-2.242 1.241v-.001l-5.903 3.27-3.348-3.541 7.416-3.962-7.922-4.372-7.923 4.372 7.422 3.937v.024l-3.297 3.622-5.203-3.008-.16-.092-.679-.393v.002z"/>
    </svg>
  <% end %>
</li>

ナビゲーションバーに、注文一覧画面へのリンクが付与されたアイコンが表示されれば OK です。

それでは、最後に注文履歴の詳細画面を作成しましょう。app/views/admin/orders/show.html.erb を開いて、下記コードを記述してください。

app/views/admin/orders/show.html.erb
<div class="px-6">
  <div class="flex justify-start item-start space-y-2 flex-col mb-8">
    <h1 class="mb-1 text-3xl lg:text-4xl font-semibold leading-7 lg:leading-9 text-gray-800">Order ID #<%= @order.id %></h1>
    <p class="text-base font-medium leading-6 text-gray-600">CreatedAt: <%= l @order.created_at, format: :short %></p>
    <p class="text-base font-medium leading-6 text-gray-600">UpdatedAt: <%= l @order.updated_at, format: :short %></p>
  </div> 
  <div class="flex flex-col xl:flex-row jusitfy-center  w-full xl:space-x-8 space-y-4 md:space-y-6 xl:space-y-0">
    <div class="flex flex-col justify-start items-start w-full space-y-4 md:space-y-6 xl:space-y-8">
      <div class="flex flex-col justify-start items-start bg-gray-50 px-4 py-4 md:py-6 md:p-6 xl:p-8 w-full">
        <p class="text-lg md:text-xl font-semibold leading-6 xl:leading-5 text-gray-800">Customer’s Cart</p>
        <% @order.order_details.each do |order_detail| %>
          <div class="mt-4 md:mt-6 flex flex-row justify-start items-start md:items-center space-x-6 xl:space-x-8 w-full">
            <div class="pb-4 md:pb-8 w-40">
              <%= image_tag order_detail.product.image, class: "aspect-square w-full rounded-xl object-cover" %>
            </div>
            <div class="border-b border-gray-200 md:flex-row flex-col flex justify-between items-start w-full pb-8 space-y-4 md:space-y-0">
              <div class="w-full flex flex-col justify-start items-start space-y-8">
                <p class="text-3xl font-semibold leading-6 text-gray-800"><%= order_detail.product.name %></p>
                <div class="flex justify-start items-start flex-col space-y-2">
                  <p class="text-lg leading-none text-gray-800"><%= number_to_currency(order_detail.price, unit: "¥", strip_insignificant_zeros: true) %></p>
                  <p class="text-md leading-none text-gray-800">Qty: <%= order_detail.quantity %></p>
                </div>
              </div>
            </div>
          </div>
        <% end %>
      </div>
      <div class="flex justify-center flex-col md:flex-row items-stretch w-full space-y-4 md:space-y-0 md:space-x-6 xl:space-x-8">
          <div class="flex flex-col px-4 py-6 md:p-6 xl:p-8 w-full bg-gray-50 space-y-6">
              <h3 class="text-xl font-semibold leading-5 text-gray-800">Summary</h3>
              <div class="flex justify-center items-center w-full space-y-4 flex-col border-gray-200 border-b pb-4">
                <div class="flex justify-between w-full">
                  <p class="text-base leading-4 text-gray-800">Subtotal</p>
                  <p class="text-base leading-4 text-gray-600"><%= number_to_currency(@order.billing_amount - @order.postage, unit: "¥", strip_insignificant_zeros: true) %></p>
                </div>
                <div class="flex justify-between items-center w-full">
                  <p class="text-base leading-4 text-gray-800">Shipping</p>
                  <p class="text-base leading-4 text-gray-600"><%= number_to_currency(@order.postage, unit: "¥", strip_insignificant_zeros: true) %></p>
                </div>
              </div>
              <div class="flex justify-between items-center w-full">
                <p class="text-base font-semibold leading-4 text-gray-800">Total</p>
                <p class="text-base font-semibold leading-4 text-gray-600"><%= number_to_currency(@order.billing_amount, unit: "¥", strip_insignificant_zeros: true) %></p>
              </div>
          </div>
      </div>
    </div>
    <div class="flex flex-col justify-start items-start w-full space-y-4 md:space-y-6 xl:space-y-8">
      <div class="bg-gray-50 w-full h-fit px-4 py-6 md:p-6 xl:p-8 ">
        <h3 class="text-xl font-semibold leading-5 text-gray-800">Customer</h3>
        <div class="flex flex-col md:flex-row xl:flex-col justify-start w-full md:space-x-6 lg:space-x-8 xl:space-x-0">
            <div class="flex flex-col justify-start items-start flex-shrink-0">
                <div class="flex justify-center w-full md:justify-start items-center space-x-4 py-8 border-b border-gray-200">
                  <div class="flex justify-start items-start flex-col space-y-2">
                    <p class="text-base font-semibold leading-4 text-left text-gray-800">
                      <%= @order.customer.name %>
                    </p>
                    <p class="text-sm leading-5 text-gray-600">
                      <%= @order.customer.orders.count %> Previous Orders
                    </p>
                  </div>
                </div>
                <div class="flex justify-center text-gray-800 md:justify-start items-center space-x-4 py-4 border-b border-gray-200 w-full">
                  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
                    <path d="M0 3v18h24v-18h-24zm6.623 7.929l-4.623 5.712v-9.458l4.623 3.746zm-4.141-5.929h19.035l-9.517 7.713-9.518-7.713zm5.694 7.188l3.824 3.099 3.83-3.104 5.612 6.817h-18.779l5.513-6.812zm9.208-1.264l4.616-3.741v9.348l-4.616-5.607z"/>
                  </svg>
                  <p class="text-sm"><%= @order.customer.email %></p>
                </div>
            </div>
            <div class="flex justify-between w-full flex-col mt-6">
              <div class="flex justify-center md:justify-start xl:flex-col flex-col md:space-x-6 lg:space-x-8 xl:space-x-0 space-y-4 xl:space-y-12 md:space-y-0 md:flex-row items-center md:items-start">
                <div class="flex justify-center md:justify-start items-center md:items-start flex-col space-y-4">
                  <p class="text-base font-semibold leading-4 text-center md:text-left text-gray-800">Shipping Address</p>
                  <div class="w-full xl:w-48 text-center md:text-left text-sm leading-5 text-gray-600">
                    <p class="mb-2 leading-tight text-sm">Name: <span class="font-semibold text-slate-700 sm:ml-2"><%= @order.name %></span></p>
                    <p class="mb-2 leading-tight text-sm">Postal Code: <span class="font-semibold text-slate-700 sm:ml-2"><%= @order.postal_code %></span></p>
                    <p class="mb-2 leading-tight text-sm">Prefecture: <span class="font-semibold text-slate-700 sm:ml-2"><%= @order.prefecture %></span></p>
                    <p class="mb-2 leading-tight text-sm whitespace-nowrap">Address1: <span class="font-semibold text-slate-700 sm:ml-2"><%= @order.address1 %></span></p>
                    <p class="mb-2 leading-tight text-sm whitespace-nowrap">Address2: <span class="font-semibold text-slate-700 sm:ml-2"><%= @order.address2 %></span></p>
                  </div>
                </div>
              </div>
            </div>
        </div>
      </div>
      <div class="flex justify-center flex-col md:flex-row items-stretch w-full space-y-4 md:space-y-0 md:space-x-6 xl:space-x-8">
        <div class="px-4 py-6 md:p-6 xl:p-8 w-full bg-gray-50 space-y-6">
          <h3 class="text-xl font-semibold leading-5 text-gray-800">Order Status</h3>
          <p>Current Status: <span class="mt-5 bg-blue-100 text-blue-800 text-md font-semibold py-2 px-3 rounded"><%= @order.status %></span></p>
          <%= form_with model: @order, url: admin_order_path(@order), local: true  do |f|  %>
            <div class="mb-6">
              <%= f.select :status, Order.statuses.keys.to_a, {}, {class: "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-3 appearance-none"} %>
            </div>
            <%= f.submit class: "inline-flex w-full items-center justify-center rounded-md bg-indigo-500 p-3 text-white duration-100 ease-in-out hover:bg-indigo-600 focus:outline-none cursor-pointer" %>
          <% end %>
        </div>
      </div>
    </div>
  </div>
</div>

上記のコードの終盤に、フォームを設置しました。
このフォームは、表示されている注文情報の status を変更するためのものです。
セレクトボックスの選択肢には、enum で設定した値を表示したいため、Order.statuses.keys.to_a としました。Order.statuses.keys とすると enum のキー、すなわち、app/models/order.rb で設定した waiting_paymentconfirm_payment といった値が取得できます。そして、取得した enum のキーを配列にするために、配列に変換するメソッド to_a を使っています。
これで、enum のキーを選択肢として表示することができました。

ビューの記述が完了しましたら、http://localhost:8000/admin/orders/1 にアクセスして、注文履歴の詳細が表示されるか確認してみましょう。id には適切な order の id を指定してください。

上記のように、注文履歴の詳細・フォームが表示されれば OK です。
試しに、order の status を更新してみましょう。
今回は、confirm_payment から shipped に変更してみました。

上記のように、フラッシュメッセージが表示され、「Current Status: shipped」に変更されていれば正しく編集できています。

以上で管理者側の注文に関する機能は全て実装し終わりました 🎉
ついでに、管理者がログインした後のリダイレクト先を注文履歴一覧画面に変更しておきましょう。
app/controllers/admin/sessions_controller.rb を開いて下記コードを追加してください。

app/controllers/admin/sessions_controller.rb
# frozen_string_literal: true

class Admin::SessionsController < Devise::SessionsController
  # before_action :configure_sign_in_params, only: [:create]

  # GET /resource/sign_in
  # def new
  #   super
  # end

  # POST /resource/sign_in
  # def create
  #   super
  # end

  # DELETE /resource/sign_out
  # def destroy
  #   super
  # end

+ protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_in_params
  #   devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute])
  # end

+ def after_sign_in_path_for(_resource)
+   admin_root_path
+ end
end

after_sign_in_path_for とは devise が用意しているメソッドで、サインイン後にどこに遷移するかを設定しているメソッドです。 devise のデフォルトは root_path になっています。今回は、管理者側のトップページ admin_root_path としました。

それでは、管理者ユーザでログインしてみましょう。
下記のように注文履歴一覧画面にリダイレクトされれば OK です。

最後に、RuboCop を実行し、必要であればコードを修正しましょう。

$ rubocop

今回は、RuboCop の静的解析を全て PASS したようです。

Inspecting 60 files
............................................................

60 files inspected, no offenses detected

もしも指摘事項があれば修正してください。
それでは、コミットしておきましょう。

$ git add . && git commit -m "Implementation on the admin's side of the order history"

おわりに

お疲れ様でした。
本 Chapter では、管理者側の注文履歴に関する機能を実装しました。
次の Chapter にて、顧客情報に関する機能を実装します。