学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

Rails導入編 | カート機能の実装 | 14 | セッションカートの実装

↓↓クリックして頂けると励みになります。




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」フィールドがリンクしています。

line_itemsテーブル
line_itemsテーブル



続いて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」をクリック
「Show this cart」をクリック



「Show this cart」ボタンをクリックすると、カートの内容が表示されます。

カート内容表示
カート内容表示




13 | 買い物ビューの作成】 << 【ホーム】 >> 【15 | セッションカートの改良





↓↓クリックして頂けると励みになります。