
今回はECサイトで売上の詳細を表示する方法を紹介していきたいと思います。
Stripeという決済プラットフォームを使用し、購入履歴を作成します。
そしてその購入履歴を売上として表示するようにします。
作業内容
・Adminアカウントで過去の売上と今月の売上を見れるようにする
・Adminアカウントで売上の詳細を見れるようにする
作業手順
Adminアカウントで売れた商品を取得する
Admin
が作成したproducts
の中で、売れた商品(purchase_record_products
)を検索しなければいけません。そのため、下記のようにjoins
というメソッドを使って、Product
とPuchaseRecordProduct
を結びつけます。
joins
については以下のリンクで詳しく書いてあります!
https://pikawaka.com/rails/joins
$rails c
2.5.3 :001 >Admin.last.products.joins(:purchase_record_products)
=> #<ActiveRecord::AssociationRelation [#<Product id: 1, admin_id: 1, user_id: nil, name: "test1", description: "これはテスト1用の商品です。", price: 1000, created_at: "2020-10-10 10:14:37", updated_at: "2020-10-10 10:14:37", image: "alvin-mahmudov-vKuEhorbvYI-unsplash__1_.jpg">, #<Product id: 1, admin_id: 1, user_id: nil, name: "test1", description: "これはテスト1用の商品です。", price: 1000, created_at: "2020-10-10 10:14:37", updated_at: "2020-10-10 10:14:37", image: "alvin-mahmudov-vKuEhorbvYI-unsplash__1_.jpg">,
無事に2つのモデルを結ぶことができました。
それではadmin
モデル下記のようなメソッドを追加します。
class Admin < ApplicationRecord
devise :database_authenticatable,
:recoverable, :rememberable, :validatable
has_many :products, dependent: :destroy
def sales
end
def sales_one_month
end
end
salesメソッド
にまずは先ほどやったjoinsメソッド
を使って購入された商品の一覧を取得します。そして、それを計算して合計値を出します。
def sales
sold_products = self.products.joins(:purchase_record_products)
sold_products.sum(:price)
end
こんな感じになりますね。めちゃシンプルです。
次にsales_one_month
の方ですね。1ヶ月の売上を見れるようにしなければいけません。
そのためには、購入した今月の売上を検索しなければいけません。検索するには下記のようなコードを追加します。
where(created_at: Time.now.all_month)
これで今月に作成したものを検索することができるそうです。
さらにjoins
でpurchase_record_products
とテーブルを連結させたので、下記のように結合先のテーブル名を指定して検索をします。
モデル名.joins(:関連名).where(結合先のテーブル名: { カラム名: 値 })
これらを合わせると以下のようになりますね。
def sales_one_month
sold_products = products.joins(:purchase_record_products).where({ purchase_record_products: {created_at: Time.now.all_month} })
sold_products.sum(:price)
end
きちんと動作するかどうか確認してみます。
$ rails c
2.5.3 :001 > Admin.last.sales
Admin Load (0.3ms) SELECT `admins`.* FROM `admins` ORDER BY `admins`.`id` DESC LIMIT 1
(0.2ms) SELECT SUM(`products`.`price`) FROM `products` INNER JOIN `purchase_record_products` ON `purchase_record_products`.`product_id` = `products`.`id` WHERE `products`.`admin_id` = 1
=> 14000
2.5.3 :002 > Admin.last.sales_one_month
Admin Load (0.7ms) SELECT `admins`.* FROM `admins` ORDER BY `admins`.`id` DESC LIMIT 1
(0.3ms) SELECT SUM(`products`.`price`) FROM `products` INNER JOIN `purchase_record_products` ON `purchase_record_products`.`product_id` = `products`.`id` WHERE `products`.`admin_id` = 1 AND `purchase_record_products`.`created_at` BETWEEN '2020-10-01 00:00:00' AND '2020-10-31 23:59:59'
=> 14000
無事両方とも動作しました。よかったです。
そしたらこの二つをトップページに表示させます。
売上を表示させる
<div class="container">
<h2 class="text-center">Adminトップページ</h2>
<ul class="list-group list-unstyled">
<li class="admin-sales-all d-flex justify-content-between border">
<p>今までの売上 <span><%=current_admin.sales.to_s %>円</span></p>
<a class="p-0 m-0"><%=link_to '詳細', admins_sales_record_path, class: "btn btn-success"%></a>
</li>
<li class="admin-sales-one-month d-flex justify-content-between border">
<p>今月の売上 <span><%=current_admin.sales_one_month.to_s %>円</span></p>
<a><%=link_to '詳細', admins_sales_record_path, class: "btn btn-success"%></a>
</li>
</ul>
</h2>
</div>
ほとんどカートの中身と表示設定は変わりません。
それでは、売上の詳細を見れるようにします。
売上の詳細を見れるようにする
resource :sales_record, only: [:show]
class Admins::SalesRecordsController < Admins::ApplicationController
def show
end
end
Admin
からpurchase_record_products
を閲覧できるようにAdmin
にhas_many
を追加します。
has_many :purchase_record_products, through: :products
<table class="table">
<thead>
<tr>
<th>商品名</th>
<th>価格</th>
<th>購入日</th>
</tr>
</thead>
<tbody>
<% @purchase_record_products.each do |prd| %>
<% product = prd.product %>
<tr>
<td><%=product.name %></td>
<td><%=product.price %>円</td>
<td><%=product.created_at %></td>
</tr>
<% end %>
</tbody>
</table>
N+1問題
ここでN+1問題が発生するとのことです。
N+1問題とはなんだ。。
ループ処理の中で都度SQLを発行してしまい、大量のSQLが発行されてパフォーマンスが低下してしまう問題のことです。
日常生活で例えるなら、スーパーで商品を1点ずつお会計するようなもの。
それだけ無駄なことを行なっている状態を指します。
え、そんなアホなこと普通はしないって思いますよね?
でもRailsを使用していると往往にしてこの問題が発生してしまうのです。
引用:https://qiita.com/massaaaaan/items/4eb770f20e636f7a1361
なるほど、めちゃ効率悪いですね。
ということでpreloadメソッド
を使って、解決していきます。
@purchase_record_products = current_admin.purchase_record_products.preload(:product)
これだけで問題解決しました。
パラメーターの取得
まずは/admins
のindex.html.erb
の今月の売上の詳細ボタンを変更していきます。
日付操作に関しましては、下記のページを参考にさせていただきました。
https://qiita.com/mmmm/items/efda48f1ac0267c95c29
<a><%=link_to '詳細', admins_sales_record_path, class: "btn btn-success"%></a>
⬇️
<a><%=link_to '詳細', admins_sales_record_path(start_at: Time.current.beginning_of_month, end_at: Time.current.end_of_month), class: "btn btn-success"%></a>
日付がこれで取得できたはずなので、controller
を変更していきます。
パラメーターから渡される値は文字列なので、日付に変更する必要がありますね。
さて、どうやって日付型にした方がいいのか、ググってみます。
見つけました。https://techacademy.jp/magazine/19845
下のようにすれば日付型に変更できるそうです。
Date.parse("日付")
それでは、start_at
とend_at
を日付型に変更して、検索してみます。
class Admins::SalesRecordsController < Admins::ApplicationController
def show
@purchase_record_products = current_admin.products.purchase_record_products
start_at = Date.parse(params[:start_at])
end_at = Date.parse(params[:end_at])
@purchase_record_products = @purchase_record_products.where(created_at: start_at..end_at)
end
end
こんな感じになりますね。しかし、これだと過去全ての売上の場合も実行されてしまい、エラーが起きてしまいますので、if文を追加してあげます。
class Admins::SalesRecordsController < Admins::ApplicationController
def show
@purchase_record_products = current_admin.purchase_record_products
if params[:start_at] && params[:end_at]
start_at = Date.parse(params[:start_at])
end_at = Date.parse(params[:end_at])
@purchase_record_products = @purchase_record_products.where(created_at: start_at..end_at)
end
end
end
こんな感じで完成ですね。確認してみましょう。

無事表示されました。
まとめ
これで売上詳細の表示は完成です!
ProductとPurchaseRecordProductを結び付かせるという発想が思いつくまでが難しかったですね。
ECサイトに関する情報が他の記事でも書いているので、もし良ければみてみてください!
最後まで読んで頂きありがとうございます!