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

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

Ruby on Rails 6.0によるWebアプリケーション開発31 追加支払いの詳細入力(Webpackerのインストール)

<<前  [TOP]  次>>


支払い方法はそれぞれ入力する内容に違いがあります。
これを実装するために「Webpacker」というアプリケーションを利用します。
「Webpacker」は作成するJavaScriptファイルを管理するためのツールです。
「Webpacker」をインストールするにはコマンドプロンプトで「bin」フォルダに移動し、「rails webpacker:install:react」と入力いします。

Webpackerのインストール
Webpackerのインストール


「Webpacker」を使用するためには「javascript_pack_tag()」の記述が必要です。
「C:\Rails6\work\shop\app\views\orders」フォルダの「new.html.erb」ファイルに「<%= javascript_pack_tag("hello_react") %>」という記述を追加してみます。
【C:\Rails6\work\shop\app\views\orders\new.html.erb】

<section class="shop_form">
	<h1>お客様情報を入力してください。</h1>
	<%= render 'form', order: @order %>
</section>

<%= javascript_pack_tag("hello_react") %>



ページ下部に「Hello React!」と表示されれば「Webpacker」が使用できているということになります。

WebPaker使用準備完了
WebPaker使用準備完了


既存のドロップダウンをReactとWebpackerが管理するJavascriptを利用したものに置き換えます。
ReactはJavaScriptのビューライブラリです。
ReactはブラウザーでHTMLを動的にレンダリングします。
選択した支払いタイプをページのごく一部にのみ影響するようにし、サーバー全体に再レンダリングしないようにできます。
Webpackを利用してドロップダウンを作成します。
ReactとWebpackの操作方法を理解するためにRalisによってレンダリングされている既存のものをReactによってレンダリングされるものに置き換えます。
それには次の手順で行います。
  1. 実装のルートとなる新しいパック「pay_type」を作成します。
  2. 既存の支払いタイプドロップダウンを置き換えるために使用する「PayTypeSelector」コンポーネントを作成します。
  3. 「javascript_pack_tag()」とReactがコンポーネントをレンダリングできるのを使用して、コンポーネントをチェックアウトビューに入れます。

  4. では最初に新しいパック「pay_type」を作成します。 「C:\Rails6\work\shop\app\javascript\packs」フォルダに「pay_type.jsx」ファイルを新規作成します。
【C:\Rails6\work\shop\app\javascript\packs\pay_type.jsx(新規作成)】

import React           from 'react'
import ReactDOM        from 'react-dom'
import PayTypeSelector from 'PayTypeSelector'

document.addEventListener('turbolinks:load', function() {
  var element = document.getElementById("pay-type-component");
  ReactDOM.render(<PayTypeSelector />, element);
});



「import React from 'react'」の部分はメインのReactライブラリにアクセスする方法です。
インポートはRubyのreuire()に似ていて、他のファイルにあるコードにアクセスできるようにしています。
ブラウザはそれをサポートしていません。
この行を処理するとファイル「react.js」を見つけようとします。


「import ReactDOM from 'react-dom'」の行ではreactDOMオブジェクトが組み込まれます。
このオブジェクトにはReactコンポーネントを使用するために必要なrender()関数があります。


「import PayTypeSelector from 'PayTypeSelector'」の行では次に作成するコンポーネントであるPayTypeSelectorをインポートしています。

「document.addEventListener('turbolinks:load', function() {」の行はドキュメントで使用可能な標準関数「addEventListener()」を使用して、実行しようとしているコードがDOM全体がロードされた後にのみ実行されるようにします。
Turbolinkの仕組みによりページがリロードされるたびにそのイベントが発生するわけではありません。
Turbolinkはページ読み込みイベントを管理し、代わりに「turbolinks:load」イベントを発生させます。
DOMContentLoadedを使用してページから移動して「戻る」ボタンを使用するとページはReactを設定せず何も機能しません。
「turbolinks:load」を使用するとページがレンダリングされるたびにReactがセットアップされます。


「 var element = document.getElementById("pay-type-component");」の行は「pay-type-component」を持つ要素を検索しています。


「ReactDOM.render(, element);」の行は要素をReactコンポーネント「PayTypeSelector」で置き換えています。
JSXファイルでは「PayTypeSelector」を介して行われます。
しかしWebpackがJSXファイルをコンパイルするときにブラウザーで機能するJavaScriptを生成します。
上記のインポート行で「PaTypeSelector」を使用したため機能します。


実際にPayTypeSelectorコンポーネントの作成を行います。
「C:\Rails6\work\shop\app\javascript」フォルダに「PayTypeSelector」フォルダを新規作成し、以下のような「index.jsx」という名前のファイルを作成します。


【C:\Rails6\work\shop\app\javascript\PayTypeSelector/index.jsx】

import React from 'react'

class PayTypeSelector extends React.Component {

  render() {
     return (
        <div className="field">
          <label htmlFor="order_pay_type">支払い方法</label>
          <select id="order_pay_type" onChange={this.onPayTypeSelected} 
            name="order[pay_type]">
            <option value="">支払い方法を選択してください。</option>
            <option value="Check">現金</option>
            <option value="Credit card">クレジットカード</option>
            <option value="Purchase order">着払い</option>
          </select>
        </div>
    );
  }
}
export default PayTypeSelector



PayTypeSelectorコンポーネントをRailsビューに取り込みます。
「C:\Rails6\work\shop\app\views\orders」フォルダにある「new.html.erb」の記述を「<%= javascript_pack_tag("pay_type") %>」に変更します。


【C:\Rails6\work\shop\app\views\orders\new.html.erb】

<section class="shop_form">
	<h1>お客様情報を入力してください。</h1>
	<%= render 'form', order: @order %>
</section>

<%= javascript_pack_tag("pay_type") %>



あわせて「_form.html.erb」ファイルの記述も変更します。

」の記述を追加するだけです。


【C:\Rails6\work\shop\app\views\orders\_form.html.erb】

<%= form_with(model: order, local: true) do |form| %>
  <% if order.errors.any? %>
    <div id="error_explanation2">
      <h2><%= pluralize(order.errors.count, "error") %> この注文の保存ができません。</h2>

      <ul>
        <% order.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :氏名 %>
    <%= form.text_field :name, size: 40 %>
  </div>

  <div class="field">
    <%= form.label :住所 %>
    <%= form.text_area :address, rows: 3, cols: 40 %>
  </div>

  <div class="field">
    <%= form.label :email %>
    <%= form.text_field :email, size: 40 %>
  </div>

  <div id='pay-type-component'></div>

  <div class="actions">
    <%= form.submit '注文を確定する' %>
  </div>
<% end %>



先程作成した「javascript\PayTypeSelector/index.jsx」ファイルに「 onPayTypeSelected(event)」の記述を追加します。


【C:\Rails6\work\shop\app\javascript\PayTypeSelector/index.jsx】

import React from 'react'

class PayTypeSelector extends React.Component {

  onPayTypeSelected(event) {
    this.setState({ selectedPayType: event.target.value });
  }

  render() {
     return (
        <div className="field">
          <label htmlFor="order_pay_type">支払い方法</label>
          <select id="order_pay_type" onChange={this.onPayTypeSelected} 
            name="order[pay_type]">
            <option value="">支払い方法を選択してください。</option>
            <option value="Check">現金</option>
            <option value="Credit card">クレジットカード</option>
            <option value="Purchase order">着払い</option>
          </select>
        </div>
    );
  }
}
export default PayTypeSelector



これで「支払い方法選択フォーム」がReactとWebpackerが管理するJavascriptを利用したものに置き換わりました。
まだ「orders_controller.rb」に「pay_type_params()」メソッドの記述をしていないので注文の保存はできません。

ブラウザの表示
ブラウザの表示


支払い方法を選択した時に詳細入力ができるように記述を変更していきます。
「javascript\PayTypeSelector/index.jsx」ファイルに「constructor()」メソッド、「let PayTypeCustomComponent」の記述、「 」の記述を追加し、このあと新規作成する「NoPayType.jsx」「CheckPayType.jsx」「CreditCardPayType.jsx」「PurchaseOrderPayType.jsx」の4つのファイルのインポートを記述します。


【C:\Rails6\work\shop\app\javascript\PayTypeSelector/index.jsx】

import React from 'react'

import NoPayType            from './NoPayType';
import CreditCardPayType    from './CreditCardPayType';
import CheckPayType         from './CheckPayType';
import PurchaseOrderPayType from './PurchaseOrderPayType';

class PayTypeSelector extends React.Component {
  constructor(props) {
    super(props);
    this.onPayTypeSelected = this.onPayTypeSelected.bind(this);
    this.state = { selectedPayType: null };
  }

  onPayTypeSelected(event) {
    this.setState({ selectedPayType: event.target.value });
  }

  render() {
    let PayTypeCustomComponent = NoPayType;
    if (this.state.selectedPayType == "現金") {
      PayTypeCustomComponent = CreditCardPayType;
    } else if (this.state.selectedPayType == "クレジットカード") {
      PayTypeCustomComponent = CheckPayType;
    } else if (this.state.selectedPayType == "着払い") {
      PayTypeCustomComponent = PurchaseOrderPayType;
    }
    return (
      <div>
        <div className="field">
          <label htmlFor="order_pay_type">支払い方法</label>
          <select id="order_pay_type" onChange={this.onPayTypeSelected} 
            name="order[pay_type]">
            <option value="">支払い方法を選択してください。</option>
            <option value="現金">現金</option>
            <option value="クレジットカード">クレジットカード</option>
            <option value="着払い">着払い</option>
          </select>
        </div>
        <PayTypeCustomComponent />
      </div>
    );
  }
}
export default PayTypeSelector



同じく「C:\Rails6\work\shop\app\javascript\PayTypeSelector/」フォルダに以下のような「NoPayType.jsx」「CheckPayType.jsx」「CreditCardPayType.jsx」「PurchaseOrderPayType.jsx」の4つのファイルを新規作成します。


【C:\Rails6\work\shop\app\javascript\PayTypeSelector/NoPayType.jsx(新規作成)】

import React from 'react'

class NoPayType extends React.Component {
  render() {
    return (<div></div>);
  }
}
export default NoPayType



【C:\Rails6\work\shop\app\javascript\PayTypeSelector/CheckPayType.jsx(新規作成)】

import React from 'react'

class CheckPayType extends React.Component {
  render() {
    return (
      <div>
        <div className="field">
          <label htmlFor="order_routing_number">支払時期</label>
          <select id="order_routing_number" name="order[routing_number]">
            <option value="">支払い時期を選択してください。</option>
            <option value="前払い">前払い</option>
            <option value="後払い">後払い</option>
           </select> 
        </div>
      </div>
    );
  }
}
export default CheckPayType



【C:\Rails6\work\shop\app\javascript\PayTypeSelector/CreditCardPayType.jsx(新規作成)】

import React from 'react'

class CreditCardPayType extends React.Component {
  render() {
    return (
      <div>
        <div className="field">
          <label htmlFor="order_credit_card_number">カード番号</label>
          <input type="password"
                 name="order[credit_card_number]" 
                 id="order_credit_card_number" />
        </div>
        <div className="field">
          <label htmlFor="order_expiration_date">有効期限</label>
          <input type="text"
                 name="order[expiration_date]" 
                 id="order_expiration_date"
                 size="9"
                 placeholder="月/年" />
        </div>
      </div>
    );
  }
}
export default CreditCardPayType



【C:\Rails6\work\shop\app\javascript\PayTypeSelector/PurchaseOrderPayType.jsx(新規作成)】

import React from 'react'

class PurchaseOrderPayType extends React.Component {
  render() {
    return (
      <div>
        <div className="field">
          <label htmlFor="order_po_number">希望宅配業者</label>
          <input type="text"
                 name="order[po_number]" 
                 id="order_po_number" />
        </div>
     </div>
    );
  }
}
export default PurchaseOrderPayType



これで支払い方法を選択した時にそれぞれに対応した入力フォームが表示されます。

ブラウザの表示
ブラウザの表示


最後にコントローラに「pay_type_params」メソッドの記述を行います。


【C:\Rails6\work\shop\app\controllers\orders_controller.rb】

class OrdersController < ApplicationController

  include CurrentCart
  before_action :set_cart, only: [:new, :create]
  before_action :ensure_cart_isnt_empty, only: :new
  before_action :set_order, only: [:show, :edit, :update, :destroy]

  # GET /orders
  # GET /orders.json
  def index
	@orders = Order.all
  end

  # GET /orders/1
  # GET /orders/1.json
  def show
  end

  # GET /orders/new
  def new
    @order = Order.new
  end

  # GET /orders/1/edit
  def edit
  end

  # POST /orders
  # POST /orders.json
  def create
    @order = Order.new(order_params)
    @order.add_line_items_from_cart(@cart)


    respond_to do |format|
      if @order.save
	Cart.destroy(session[:cart_id])
	session[:cart_id] = nil

        format.html { redirect_to market_index_url, notice: 'ご注文ありがとうございました。' }
        format.json { render :show, status: :created, location: @order }
      else
        format.html { render :new }
        format.json { render json: @order.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /orders/1
  # PATCH/PUT /orders/1.json
  def update
    respond_to do |format|
      if @order.update(order_params)
        format.html { redirect_to @order, notice: 'Order was successfully updated.' }
        format.json { render :show, status: :ok, location: @order }
      else
        format.html { render :edit }
        format.json { render json: @order.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /orders/1
  # DELETE /orders/1.json
  def destroy
    @order.destroy
    respond_to do |format|
      format.html { redirect_to orders_url, notice: 'Order was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_order
      @order = Order.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def order_params
      params.require(:order).permit(:name, :address, :email, :pay_type)
    end

    def ensure_cart_isnt_empty
	if @cart.line_items.empty?
		redirect_to market_index_url, notice: 'カートは空です。'
	end
    end

    def pay_type_params
      if order_params[:pay_type] == "クレジットカード"
        params.require(:order).permit(:credit_card_number, :expiration_date)
      elsif order_params[:pay_type] == "現金"
        params.require(:order).permit(:routing_number, :account_number)
      elsif order_params[:pay_type] == "着払い"
        params.require(:order).permit(:po_number)
      else
        {}
      end
    end
end



これで「注文を確定する」ボタンが機能するようになりました。


<<前  [TOP]  次>>