<<前 [TOP] 次>>
商品をセッションカートという一時的な保存場所に格納出来るようなシステムを作っていきます。
Railsでは、アクセスしている人が実行するすべての操作を追跡できるようにするために、sessionと呼ばれるものを使用しています。
sessionはコントローラ内にハッシュに似たような形で保持しています。
この機能を使って、商品を格納するためのカートを作成してみます。
「rails generate scaffold cart
」とコマンドプロンプトで入力します。
途中「CSSを上書きしますか?」と聞かれるので、今回は「n」と入力して上書きしないようにします。
「rake db:migrate
」コマンドでcartsテーブルをデータベースに作成します。
テーブルの内容を見てみましょう。
「id」「created_at」「updated_at」の3つのフィールドがあります。
Rails4.2で自動生成される「id」フィールドは、「int」型になっています。
「app/controllers」フォルダにある「application_controller.rb」ファイルを編集します。
【app/controllers/application_controller.rb】
class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception private def current_cart Cart.find(session[:cart_id]) rescue ActiveRecord::RecordNotFound cart = Cart.create session[:cart_id] = cart.id cart end end
current_cart()は「private」にします。これはこのメソッドをコントローラのアクションとして公開しないという指定をしています。
最初にsessionオブジェクトから「:cart_id」というキーを取得してfind()メソッドで探します。
この「:cart_id」はcartsテーブルのidフィールドを指しています。
カートの商品が見つからない時は新しいカートを作成し、idをセッションに格納してそのカートを返します。
次に「rails generate scaffold line_item good_id:integer cart_id:integer
」とコマンドプロンプトで入力します。
「CSSを上書きしますか?」と聞かれるので、今回も「n」と入力して上書きしないようにします。
「rake db:migrate
」コマンドでline_itemsテーブルをデータベースに作成します。
line_itemsテーブルとgoodsテーブルを外部キーの参照によってリンクさせています
line_itemsテーブルの「good_id」フィールドとgoodsテーブルの「id」フィールドがリンクしています。
goodsテーブルの「id」フィールドは自動で出来たもので「int型」になっていたので、line_itemsテーブルの「good_id」フィールドの型も「int型」にしました。
リンクさせる場合、型を合わせないとエラーが起きます。
「line_items」テーブルを確認してみます。
では「app/models」フォルダに移動して「cart.rb」ファイルを編集します。
【app/models/cart.rb】
class Cart < ActiveRecord::Base has_many :line_items, dependent: :destroy end
has_many()の呼び出しを追加しました。
「has_many :line_items」の部分は、1つのカートに多数の商品が関連付けされるということを示しています。
「dependent: :destroy」の部分は、商品が存在するかどうかはカートが存在するかどうかに依存するという意味になります。
カートを空にしてデータベースから削除した時は、そのカートに関連付けられている商品もすべて削除されるようにするということです。
続いてline_itemsからcarts、goodsテーブルへのリンクを指定します。
「app/models」フォルダにある「line_item.rb」ファイルを以下のように編集します。
【app/models/line_item.rb】
class LineItem < ActiveRecord::Base belongs_to :good belongs_to :cart end
「belongs_to」は line_itemsテーブルはcartsテーブルとgoodsテーブルが親子関係にあるということを示しています。
対応するカートと商品がなければline_itemは存在できません。
さらにgoodモデルにもhas_many()の呼び出しを追加しておきます。
「app/models」フォルダにある「line_item.rb」ファイルを以下のように編集します。
【app/models/good.rb】
class Good < ActiveRecord::Base has_many :line_items before_destroy :referenced_by_line_item validate :price_validate #Railsで標準で用意されている検証メソッド #指定されたフィールドが存在し、その内容が空でないことを確認。 validates_presence_of :title, :image_url, :maker, :category, :message => "が空の状態で保存することは出来ません。" #priceフィールドに数字か入力されているか検証。 validates_numericality_of :price, :message => "が有効な数値ではありません。" #titleフィールドに保存しようとする名称が存在していないかどうかを確認。 validates_uniqueness_of :title, :message => "はすでに存在しています。" #フィールドの値が正規表現に一致するかどうかをチェック。 #.gif,.jpg,.pngのどれかで終わっていることを確認。 validates_format_of :image_url, :with => /\a|\.jpg$|\.png$|\.gif$\z/, :message => "はGIF,JPG,PNG画像でなければなりません。" #商品の価格が正の数であることを確認する。 #価格フィールドが空でないときだけチェックをする。 protected def price_validate errors.add(:price, "は0より大きくなければなりません。") unless price.nil? || price > 0.0 end def self.select_shop where("date <= ?","now()").order(title: "ASC") end private def referenced_by_line_item if line_items.empty? return true else errors.add(:base, '品目が存在します。') retuen false end end end
追加したのは以下の記述です。
has_many :line_items before_destroy :referenced_by_line_item private def referenced_by_line_item if line_items.empty? return true else errors.add(:base, '品目が存在します。') retuen false end end
追加した「referenced_by_line_item()」メソッドは商品を参照している品目(line_item)がないことを確認しています。
商品が多数の品目(line_item)を持つことをhas_many()で宣言してから「referenced_by_line_item」メソッドを定義しています。
「before_destroy」で宣言されるものをフックメソッドといい、Railsに自動で呼び出されるメソッドになります。
今回の場合はRailsがデータベースの行を削除しようとした時に呼び出されます。
このフックメソッドで「false」を返すと行は削除されません。
「errors.add(:base, '品目が存在します。')」はerrorsオブジェクトに直接アクセスし、ベースオブジェクト自体と関連付けているという意味になります。
次に「カートに入れる」ボタンを追加します。
「app/views」フォルダの「market」フォルダにある「index.html.erb」を以下のように編集します。
【app/views/market/index.html.erb】
<% if notice %> <p id="notice"><%= notice %></p> <% end %> <br> <h1>Railsはじめてマート</h1> <h2> Railsで楽しくお買い物♪</h2> <br> <%= link_to 'カートを表示', market_path, class: 'btn' %> <br> <br> <table> <tr> <th></th> <th></th> <th></th> </tr> <% @goods.each do |good| %> <tr> <td text-align:center;> <img height="80" src="<%=h good.image_url %>"/> </td> <td> <p><font size="5">商品名:<%= good.title %></font></p> 商品説明:<%= good.description %><br></font></p> 分類:<%= good.category %><br> メーカー:<%= good.maker %><br> <p><font size="4">価格:<%= good.price %>円</font></p> </td> <td> <%= button_to 'カートに入れる', line_items_path(good_id: good), class: 'fbtn' %> </td> </tr> <% end %> </table>
変更したのは以下の部分です。
<%= button_to 'カートに入れる', line_items_path(good_id: good), class: 'fbtn' %>
POSTで渡したいので「button_to()」メソッドを使います。
コントローラの名前に「_path」と付け加えるとデータの受け渡しをRailsにすべて任せることが出来ます。
「good_id:」オプションを使用すればRailsがidを抽出してくれます。
「app/controllers」フォルダにある「line_items_controller.rb」を編集します。
編集するのは「create()」メソッドです。
【app/controllers/line_items_controller.rb】
class LineItemsController < ApplicationController before_action :set_line_item, only: [:show, :edit, :update, :destroy] # GET /line_items # GET /line_items.json def index @line_items = LineItem.all end # GET /line_items/1 # GET /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 # POST /line_items.json def create @cart = current_cart 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.cart, notice: 'カートに商品が追加されました。' } format.json { render :show, status: :created, location: @line_item } else format.html { render :new } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end # PATCH/PUT /line_items/1 # PATCH/PUT /line_items/1.json def update respond_to do |format| if @line_item.update(line_item_params) format.html { redirect_to @line_item, notice: 'Line item was successfully updated.' } format.json { render :show, status: :ok, location: @line_item } else format.html { render :edit } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end # DELETE /line_items/1 # DELETE /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 # Never trust parameters from the scary internet, only allow the white list through. def line_item_params params.require(:line_item).permit(:good_id, :cart_id) end end
編集したのは以下の部分です。
def create @cart = current_cart 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.cart, notice: 'カートに商品が追加されました。' } format.json { render :show, status: :created, location: @line_item } else format.html { render :new } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end
create()メソッドに変更を加えて、商品idをフォームパラメータとして受け取れるようにしました。
Railsはidフィールドによってオブジェクトやそれに対応するデータベースの行を識別します。
create()メソッドにidを渡すことで、カートに追加する商品を指定しています。
まず、「application_controller.rb」に実装した「current_cart()」メソッドを使ってセッション内のカートを検索、作成しています。
次にparamsを使ってgood_idパラメータを取得しています。今回はビューで使用しないのでローカル変数「good」に格納しています。
その後取得したidの商品を「@cart.line_items.build」に渡しています。これで@cartオブジェクトとgoodの間に新しいline_itemのつながりが出来ます。
このline_itemを「@line_item」に格納します。
残りの部分で、エラー処理とJSONリクエストの処理を行っています。
line_itemオブジェクトからcartオブジェクトを見つけられるように「redirect_to」の記述で「@line_item.cart」として「.cart」を付け加えています。
最後に「app/views/carts」フォルダにある「show.html.erb」を以下のように編集します。
【app/views/carts/show.html.erb】
<% if notice %> <p id="notice"><%= notice %></p> <% end %> <br> <h1>Railsはじめてマート</h1> <h2> カートに追加された商品</h2> <br> <span> </span><%= link_to '買い物を続ける', market_path, :class => 'btn' %><span> <br> <br> <table> <tr> <th></th> <th>商品名</th> <th>価格</th> </tr> <% @cart.line_items.each do |item| %> <tr> <td text-align:center;><img height="80" src="<%=h item.good.image_url %>"/></td> <td text-align:center;><font size="4"><%= item.good.title %></font></td> <td text-align:center;><font size="4"><%= (item.good.price).to_i %>円</font></td> </tr> <% end %> </table>
「http://localhost:3000/」を開いて商品をカートに追加してみましょう。
↓↓クリックして頂けると励みになります。