↓↓クリックして頂けると励みになります。
【13 | 買い物ビューの作成】 << 【ホーム】 >> 【15 | セッションカートの改良】
商品をセッションカートという一時的な保存場所に格納出来るようなシステムを作っていきます。
Railsではアクセスしている人が実行するすべての操作を追跡できるようにするためにsessionと呼ばれるものを使用しています。
sessionはコントローラ内にハッシュに似たような形で保持しています。
この機能を使って商品を格納するためのカートを作成してみます。
ターミナルでrails generate scaffold cart
と入力します。
~/Desktop/Rails7_1/SampleCart $ rails generate scaffold cart invoke active_record create db/migrate/20240115064827_create_carts.rb create app/models/cart.rb invoke test_unit create test/models/cart_test.rb create test/fixtures/carts.yml invoke resource_route route resources :carts invoke scaffold_controller create app/controllers/carts_controller.rb invoke erb create app/views/carts create app/views/carts/index.html.erb create app/views/carts/edit.html.erb create app/views/carts/show.html.erb create app/views/carts/new.html.erb create app/views/carts/_form.html.erb create app/views/carts/_cart.html.erb invoke resource_route invoke test_unit create test/controllers/carts_controller_test.rb create test/system/carts_test.rb invoke helper create app/helpers/carts_helper.rb invoke test_unit invoke jbuilder create app/views/carts/index.json.jbuilder create app/views/carts/show.json.jbuilder create app/views/carts/_cart.json.jbuilder
次にrails db:migrate
コマンドでマイグレーションを適用させ、cartsテーブルをデータベースに作成します。
~/Desktop/Rails7_1/SampleCart $ rails db:migrate == 20240115064827 CreateCarts: migrating ====================================== -- create_table(:carts) -> 0.0278s == 20240115064827 CreateCarts: migrated (0.0279s) =============================
「SampleCart/app/controllers」フォルダにある「concerns」フォルダに「current_cart.rb」ファイルを新規作成して保存します。
作成した「current_cart.rb」ファイルを以下のように編集します。
記述編集 【SampleCart/app/controllers/concerns/current_cart.rb】
module CurrentCart private def set_cart @cart = Cart.find(session[:cart_id]) rescue ActiveRecord::RecordNotFound @cart = Cart.create session[:cart_id] = @cart.id end end
「set_cart()」メソッドの最初の部分では「sessionオブジェクト」から「:cart_id」を取得し、そのidに対応するカートを探しています。
カートのレコードが見つからない場合このメソッドは新しいCartを作成してidをセッションに格納し、そのカートを返します。
「set_cart()」メソッドは「module CurrentCart」としてモジュールに配置して「private」にしています。
この処理によりコントローラー間で共通のコードを共有できます。
商品とカートの関連付けを行うためのLineItemモデルを作成します。
ターミナルでrails generate scaffold LineItem good:references cart:belongs_to
と入力します。
~/Desktop/Rails7_1/SampleCart $ rails generate scaffold LineItem good:references cart:belongs_to invoke active_record create db/migrate/20240115070617_create_line_items.rb create app/models/line_item.rb invoke test_unit create test/models/line_item_test.rb create test/fixtures/line_items.yml invoke resource_route route resources :line_items invoke scaffold_controller create app/controllers/line_items_controller.rb invoke erb create app/views/line_items create app/views/line_items/index.html.erb create app/views/line_items/edit.html.erb create app/views/line_items/show.html.erb create app/views/line_items/new.html.erb create app/views/line_items/_form.html.erb create app/views/line_items/_line_item.html.erb invoke resource_route invoke test_unit create test/controllers/line_items_controller_test.rb create test/system/line_items_test.rb invoke helper create app/helpers/line_items_helper.rb invoke test_unit invoke jbuilder create app/views/line_items/index.json.jbuilder create app/views/line_items/show.json.jbuilder create app/views/line_items/_line_item.json.jbuilder
マイグレーションを適用させます。
コマンド
rails db:migrate
モデル
Posticoで「line_items」テーブルを確認してみます。
Windowsの場合はHeidiSQLで確認して下さい。
line_itemsテーブルとgoodsテーブルを外部キーの参照によってリンクさせています。
line_itemsテーブルの「good_id」フィールドとgoodsテーブルの「id」フィールドがリンクしています。
続いてLineItemsからcartsおよびgoodsテーブルへの逆方向リンクを指定します。
この指定には「SampleCart/app/models」フォルダにある「line_item.rb」ファイル内で「belong_to()」宣言を2回使います。
「belong_to」はRailsに「line_itemsテーブル」の行はcartsテーブルとgoodsテーブル内にある行の子であることを通知します。
この記述は自動で生成されているので編集する必要はありません。
確認してみます。
記述確認 【SampleCart/app/models/line_item.rb】
class LineItem < ApplicationRecord belongs_to :good belongs_to :cart end
「SampleCart/app/models」フォルダ内に新規作成された「cart.rb」ファイルを開いて以下のように「has_many()」の呼び出しを追加します。
「has_many :line_items」という部分は1つのカートに多数の品目が関連付けられることを意味しています。
「dependent: :destroy」はカートを破棄してデータベースから削除したときにはそのカートに関連付けられている品目も全て削除されるようにしたいということです。
記述追加 【SampleCart/app/models/cart.rb】
class Cart < ApplicationRecord has_many :line_items, dependent: :destroy end
Goodモデルに「has_many」の記述を追加します。
また、before_destroy メソッドを使ってlineItemから参照されている商品が削除されないようにします。
「good.rb」ファイルを以下のように編集してください。
記述編集 【SampleCart/app/models/good.rb】
class Good < ApplicationRecord has_many :line_items before_destroy :ensure_not_referenced_by_any_lin_item validates :title, :description, :image_url, presence: true validates :price, numericality: {greater_than_or_equal_to: 1} validates :title, uniqueness: true validates :image_url, allow_blank: true, format: { with: %r{\.(gif|jpg|png)\z}i, message: 'はGIF、JPG、PNG画像のURLでなければなりません。' } private # この商品を参照しているLineItemがないことを確認する def ensure_not_referenced_by_any_line_items unless line_items.empty? errors.add(:base, '商品が存在します。') throw :abort end end end
ここでは多数の商品を持つことを「has_many」で宣言してから「ensure_not_referenced_by_any_lin_item」というフックメソッドを定義しています。
フックメソッドとはRailsから自動的に呼び出されるメソッドです。
この場合はRailsがデータベースの行を削除しようとしたときに呼び出されます。
このフックメソッドでfalseが返されると行は削除されません。
「errors」オブジェクトはvalidates()でエラーメッセージが格納されるのと同じ場所にエラーを返します。
エラーは個別の属性と関連付けることもできますが、この場合はベースオブジェクト自体と関連付けています。
コントローラー
「LineItemsコントローラ」を修正します。
現在のセッションのショッピングカートを見つけて(ショッピングカートがまだ存在しない場合は新たに作成)選択された商品をカートに追加したうえでカートの内容を表示するようにします。
「SampleCart/app/controllers」フォルダにある「line_items_controller.rb」ファイルを以下のように修正します。
最初の「include CurrentCart」「before_action :set_cart, only: [:create]」の2行を追加したのと「create()」メソッドの部分を修正しています。
記述編集 【SampleCart/app/controllers/line_items_controller.rb】
class LineItemsController < ApplicationController include CurrentCart before_action :set_cart, only: [:create] before_action :set_line_item, only: %i[ show edit update destroy ] # GET /line_items or /line_items.json def index @line_items = LineItem.all end # GET /line_items/1 or /line_items/1.json def show end # GET /line_items/new def new @line_item = LineItem.new end # GET /line_items/1/edit def edit end # POST /line_items or /line_items.json def create good = Good.find(params[:good_id]) @line_item = @cart.line_items.build(good: good) respond_to do |format| if @line_item.save format.html { redirect_to line_item_url(@line_item), notice: "商品をカートに追加しました。" } format.json { render :show, status: :created, location: @line_item } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end # PATCH/PUT /line_items/1 or /line_items/1.json def update respond_to do |format| if @line_item.update(line_item_params) format.html { redirect_to line_item_url(@line_item), notice: "Line item was successfully updated." } format.json { render :show, status: :ok, location: @line_item } else format.html { render :edit, status: :unprocessable_entity } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end # DELETE /line_items/1 or /line_items/1.json def destroy @line_item.destroy! respond_to do |format| format.html { redirect_to line_items_url, notice: "Line item was successfully destroyed." } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_line_item @line_item = LineItem.find(params[:id]) end # Only allow a list of trusted parameters through. def line_item_params params.require(:line_item).permit(:good_id, :cart_id) end end
ビュー
次に「カートに入れるボタン」を追加します。
これには「button()」メソッドを使います。
「SampleCart/app/views/markets」フォルダにある「index.html.erb」ファイルの29行目に以下の記述を追加しています。
<div class="mt-4"> <%= button_to 'カートに入れる', line_items_path(good_id: good ), class: "btn btn-warning" %> </div>
記述編集 【SampleCart/app/views/markets/index.html.erb】29行目
<div class="container mt-4"> <div class="row"> <!-- 右側(カート)--> <div class="col-md-4"> </div> <!-- 左側(商品リスト) --> <div class="col-md-8"> <div class="row"> <% @goods.each do |good| %> <div class="col-md-4"> <div class="card"> <img src="<%= good.image_url %>" class="card-img-top"> <div class="card-body"> <h6 class="card-title"><strong><%= good.title %></strong></h6> <div class="badge bg-danger fs-6"> <%= good.price %>円 </div> <br/> <div class="badge bg-secondary"> <%= good.maker %> </div> <div class="badge bg-primary"> <%= good.category %> </div> <div class="mt-4"> <%= link_to "この商品の詳細", good, class: "btn btn-success" %> </div> <div class="mt-4"> <%= button_to 'カートに入れる', line_items_path(good_id: good ), class: "btn btn-warning" %> </div> </div> </div> </div> <% end %> </div> </div> </div> </div>
ブラウザで「http://localhost:3000/」にアクセスして表示を確認してみます。
実際に「カートに入れる」ボタンを押してみます。
カートに商品が追加される動作を確認することができます。
カートの表示を変更します。
「SampleCart/app/views/carts」フォルダにある「show.html.erb」ファイルを以下のように変更します。
記述編集 【SampleCart/app/views/carts/show.html.erb】
<p style="color: green"><%= notice %></p> <div class="container"> <div class="card"> <div class="card-body"> <div class="card-title h5"><strong>現在のカート</strong></div> <% @cart.line_items.each do |item| %> <div> <%= item.good.title %> × 1 </div> <% end %> <div class="mt-4"> <%= link_to "カートの編集", edit_cart_path(@cart), class: "btn btn-warning" %> <%= link_to "戻る", carts_path, class: "btn btn-secondary" %> </div> <div class="mt-4"> <%= button_to "カートを削除", @cart, method: :delete, class: "btn btn-danger" %> </div> </div> </div> </div>
ブラウザを確認します。
カートに商品を追加した状態で以下のURLにアクセスして下さい。
http://localhost:3000/carts
「Show this cart」ボタンをクリックすると、カートの内容が表示されます。
【13 | 買い物ビューの作成】 << 【ホーム】 >> 【15 | セッションカートの改良】
↓↓クリックして頂けると励みになります。