実装概要
商品をクレジットカード決済で購入する機能を実装から単体テストまで行う
事前準備 Turbo機能をオフにする
# 中略
~data: { turbo: false }~%>
# 中略
PAY.JPを使用するためには、Turboの機能をオフにする必要があるため上記コードを追記
Purchasesコントローラーの作成
ターミナル
% rails g controller purchases
indexアクションの定義
def index
end
ルーティングの設定
# 中略
resources :purchases, only: :index
購入、送付先住所についてのモデルとテーブルの作成
ターミナル
% rails g model purchese
% rails g model shipping_address
マイグレーションファイルを編集
class CreatePurchases < ActiveRecord::Migration[7.0]
def change
create_table :purchases do |t|
t.references :user, null: false, foreign_key: true
t.references :item, null: false, foreign_key: true
t.timestamps
end
end
end
class CreateShippingAddresses < ActiveRecord::Migration[7.0]
def change
create_table :shipping_addresses do |t|
t.string :postal_code, null: false
t.integer :prefecture_id, null: false
t.string :city, null: false
t.string :address, null: false
t.string :building_name
t.string :phone_number, null: false
t.references :purchase, null: false, foreign_key: true
t.timestamps
end
end
end
マイグレーションファイルを実行
ターミナル
% rails db:migrate
アソシエーションの追加
# 中略
has_many :purchases
# 中略
has_one :purchase
belongs_to :user
belongs_to :item
has_one :shipping_address
belongs_to :purchase
Formオブジェクトの作成
modelsディレクトリ直下にpurchase_form.rbファイルを作成
併せてバリデーションも設定
include ActiveModel::Model
attr_accessor :user_id, :item_id, :postal_code, :prefecture_id, :city, :address, :building_name, :phone_number
with_options presence: true do
validates :user_id
validates :item_id
validates :postal_code, format: { with: /\A[0-9]{3}-[0-9]{4}\z/, message: 'is invalid. Include hyphen(-)' }
validates :city
validates :address
validates :phone_number, format: { with: /\A[0-9]{10,11}\z/, message: 'must be 10 or 11 digits long' }
end
validates :prefecture_id, numericality: { other_than: 0, message: "can't be blank" }
attr_accessor
は、インスタンス変数の読み書き用メソッドを自動生成し、コードをシンプルにするために使われる。これにより、メンテナンスが容易になり、コードの可読性も向上する。また、attr_reader
やattr_writer
で読み取り専用・書き込み専用の設定も可能。
with_options
は、共通のバリデーションや設定をまとめて適用するために使用する。これにより、重複コードを減らし、可読性が向上される。Railsでのモデルのバリデーションやオプションの指定を簡潔に書く際に便利
データベースに購入情報(Purchase)と配送先情報(ShippingAddress)を保存するための処理を記述。
# 中略
def save
purchase = Purchase.create(user_id:, item_id:)
ShippingAddress.create(
postal_code:,
prefecture_id:,
city:,
address:,
building_name:,
phone_number:,
purchase_id: purchase.id
)
end
end
購入機能に必要なnewアクションとcreateアクションの定義
# 中略
def new
@purchase_form = PurchaseForm.new
end
def create
@purchase_form = PurchaseForm.new(purchase_params)
if @purchase_form.valid?
@purchase_form.save
redirect_to root_path
else
render :new, status: :unprocessable_entity
end
end
private
def purchase_params
params.require(:purchase_form).permit(:postal_code, :prefecture_id, :city, :address, :building_name,
:phone_number).merge(user_id: current_user.id, item_id: params[:item_id])
end
end
ルーティングの修正
購入機能は、商品情報にネストされたルーティングの設定がされていなかったため修正。ネストすることで、items
コントローラーで定義した変数を利用できるようになる。
# 中略
resources :items do
resources :purchases, only: [:index, :new, :create]
end
indexアクション内のコード編集
def index
@item = Item.find(params[:item_id])
@purchase_form = PurchaseForm.new
end
1.商品の取得:
@item = Item.find(params[:item_id])
の部分は、特定の商品情報をデータベースから取得する。params[:item_id]
は、URLから取得した商品ID。このIDを使用して、該当する商品をデータベースから検索し、その情報を@item
に格納する。
商品の詳細情報を表示するためには、正しい商品データを取得することが必須。この情報がないと、ユーザーに対して適切な商品情報を表示することができない。
2.購入フォームの初期化:
@purchase_form = PurchaseForm.new
の部分では、購入に必要な情報を入力するためのフォームオブジェクトを初期化している。これにより、購入に必要な情報(住所、電話番号など)を収集するためのフォームが準備される。
フォームオブジェクトを使用することで、入力内容の検証やデータの保存を簡単に行えるようになる。
購入のためのフォームを実装
# 中略
<%= form_with (model: @purchase_form, url: item_purchases_path(@item.id), id: 'charge-form', class: 'transaction-form-wrap',local: true) do |f| %>
# 中略
-
url: item_purchases_path(@item.id):
ここで@item.id
を渡すことで、現在の商品に紐づいた購入が正しく処理される -
model: @purchase_form: @purchase_form
はフォームオブジェクトのインスタンスであり、モデルの役割を果たす。これにより、バリデーションエラーメッセージが自動的に関連フィールドに表示される
この設定で、購入データが送信される際に指定した create アクションにリクエストが届き、正しいフォーム送信ができるようになる。
現時点までの実装でフォームの情報が保存されるのか確認
Template is missing
エラーが発生
該当箇所を確認
# 中略
render :new, status: :unprocessable_entity
# 中略
new.html.erb
ファイルが存在していないのにrender :new
を指定してしまっていたためエラーが発生していた。
下記に修正
render :index, status: :unprocessable_entity
続いてNoMethodError
が発生。
<%= image_tag @item.image, class: 'buy-item-img' %>
部分が該当していた。
最初に商品購入ページへ遷移した際は、問題なく表示がされていたので、再表示される際に何かしらの問題が発生していると予想し、create
アクション内を確認。
すると、@item
が再設定されていないことが確認できた。
下記にコード修正
# 中略
def create
@item = Item.find(params[:item_id])
@purchase_form = PurchaseForm.new(purchase_params)
if @purchase_form.valid?
@purchase_form.save
redirect_to root_path
else
render :index, status: :unprocessable_entity
end
end
ここでnew
アクションとcreate
アクションで@item = Item.find(params[:item_id])
が共通して使用されていたため、private
メソッド内にset_item
メソッドを定義し、before_action
で呼び出せるように修正
※コード記載は省略。
テストコードを実行する
ここまでの実装が正しいかをテストコードを用いて確認
次回はPAY.JPを利用してクレジットカード決済ができるように実装を進めていきます!