sk

開発で得たこと

予約機能の作成

この記事で行うこと

ユーザーがチケットを予約できるようにする。

使うもの

Ruby 2.4.2 Rails 5.1.6

前提

ユーザーの作成 - さがえもんを完了していること。

実装手順

① チケットの表示

チケット一覧をトップページに表示します。 チケットはログインしているユーザーもログインしていないユーザーも見ることができます。

welcome_controller.rb

class WelcomeController < ApplicationController
  def index
    @tickets = Ticket.all
  end
end

app/views/welcome/index.html.haml

.col-md-12
  %h3.text-center チケット一覧
  %table.table
    %thead
      %tr
        %th
        %th チケット名
        %th 内容
        %th.text-center チケット販売可能枚数
        %th.text-center 締切日
    %tbody
      - @tickets.each.with_index(1) do |ticket, index|
        %tr
          %td= index
          %td= ticket.title
          %td= ticket.body
          %td.text-center= ticket.number
          %td.text-center= ticket.expired_at

f:id:sksksksksk:20180727202507p:plain

② 予約とユーザーの関連付け

ユーザーはチケットを複数予約することができます。 ユーザーとチケットの中間モデルを予約が行うのでモデルの関係性は以下のようになります。

f:id:sksksksksk:20180727204522p:plain ER図の書き方

まず予約(Delivery)を作成します。

$ rails g model Delivery
Running via Spring preloader in process 35173
      invoke  active_record
      create    db/migrate/20180727113318_create_deliveries.rb
      create    app/models/delivery.rb
      invoke    test_unit
      create      test/models/delivery_test.rb
      create      test/fixtures/deliveries.yml

ユーザー・予約・チケットを関連付けします。

app/models/user.rb

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  # -------------------------------------------------------------------------------
  # Relations
  # -------------------------------------------------------------------------------
  has_many :deliveries, dependent: :destroy
end

app/models/ticket.rb

class Ticket < ApplicationRecord
  # -------------------------------------------------------------------------------
  # Relations
  # -------------------------------------------------------------------------------
  has_many :deliveries, dependent: :destroy
end

app/models/delivery.rb

class Delivery < ApplicationRecord
  # -------------------------------------------------------------------------------
  # Relations
  # -------------------------------------------------------------------------------
  belongs_to :user
  belongs_to :ticket  
end

db/migrate/20180727113318_create_deliveries.rb

class CreateDeliveries < ActiveRecord::Migration[5.1]
  def change
    create_table :deliveries do |t|
      t.belongs_to :user, foreign_key: true
      t.belongs_to :ticket, foreign_key: true
      t.timestamps
    end
  end
end

マイグレートコマンドを実行しましょう。

$ rails db:migrate
== 20180727113318 CreateDeliveries: migrating =================================
-- create_table(:deliveries)
   -> 0.0059s
== 20180727113318 CreateDeliveries: migrated (0.0059s) ========================

これで関連付けは終了です。

③ 確認画面

ユーザーがチケットを予約ボタンまで辿りつけるようにします。

チケット一覧画面に「予約する」ボタンを設置しましょう。 この「予約する」ボタンを押すと確認画面に遷移します。

Deliveryコントローラーを作成します。 Userスコープで作成しましょう。

$ rails g controller Users::Deliveries
Running via Spring preloader in process 35321
      create  app/controllers/users/deliveries_controller.rb
      invoke  haml
      create    app/views/users/deliveries
      invoke  test_unit
      create    test/controllers/users/deliveries_controller_test.rb
      invoke  helper
      create    app/helpers/users/deliveries_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users/deliveries.coffee
      invoke    scss
      create      app/assets/stylesheets/users/deliveries.scss

予約はログインユーザーしかできない仕様ですので、未登録・未ログインユーザーにはログインを強制します。 ログインリダイレクトのコールバックを作成しますが、今後予約以外でもログインしてほしい場面が出てくると思うので共通化しておきます。

BaseコントローラーをUserスコープに作成します。

$ rails g controller Users::Base
Running via Spring preloader in process 35374
      create  app/controllers/users/base_controller.rb
      invoke  haml
      create    app/views/users/base
      invoke  test_unit
      create    test/controllers/users/base_controller_test.rb
      invoke  helper
      create    app/helpers/users/base_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users/base.coffee
      invoke    scss
      create      app/assets/stylesheets/users/base.scss

baseコントローラーのコールバックにdeviseのauthenticate_user!を置きます。 app/controllers/users/base_controller.rb

class Users::BaseController < ApplicationController
  before_action :authenticate_user!
end

DeliveryコントローラーはこのBaseコントローラーを継承することで、 before_action :authenticate_user! を使用できるようになります。(AppricationController -> Users::BaseController

app/controllers/users/deliveries_controller.rb

class Users::DeliveriesController < Users::BaseController
end

これで、未登録・未ログインユーザーにはログインを強制することができるようになりました。

続いて、確認画面を作成していきましょう。

app/controllers/users/deliveries_controller.rb

class Users::DeliveriesController < Users::BaseController
  def new
    @ticket = Ticket.find(params[:ticket_id]) #チケット情報を確認画面で表示するため
  end
end

app/views/users/deliveries/new.html.haml

.col-md-12
  %h3.text-center 予約確認画面
  %table.table
    %thead
      %tr
        %th チケット名
        %th 内容
        %th.text-center チケット販売可能枚数
        %th.text-center 締切日
    %tbody
      %tr
        %td= @ticket.title
        %td= @ticket.body
        %td.text-center= @ticket.number
        %td.text-center= @ticket.expired_at

次にルーティングですが、リソースをネストさせます。

config/routes.rb

Rails.application.routes.draw do
  root 'welcome#index'
  devise_for :users, path: 'users', controllers: {
    registrations: 'users/registrations', # 登録
    confirmations: 'users/confirmations', # 確認
    sessions: 'users/sessions'            # ログイン
  }
  resources :tickets, only: %i(new create index)

  #
  # ユーザー
  #
  namespace :users do
    resources :tickets do
      resources :deliveries, only: %i(new) do
        get :confirm, to: 'deliveries#new', on: :collection
      end
    end
  end
end

詳しく知りたい方は routing(ルーティング)まとめ - さがえもん をご覧ください。

最後にトップページから、確認画面に遷移できるようにリンクを貼りましょう。

app/views/welcome/index.html.haml

.col-md-12
  %h3.text-center チケット一覧
  %table.table
    %thead
      %tr
        %th
        %th チケット名
        %th 内容
        %th.text-center チケット販売可能枚数
        %th.text-center 締切日
        %th  ## 追加
    %tbody
      - @tickets.each.with_index(1) do |ticket, index|
        %tr
          %td= index
          %td= ticket.title
          %td= ticket.body
          %td.text-center= ticket.number
          %td.text-center= ticket.expired_at
          %td.text-center  ## 追加
            = link_to '予約する', confirm_users_ticket_deliveries_path(ticket), class: 'btn btn-primary'  ## 追加

動作確認

  • トップページ f:id:sksksksksk:20180727213516p:plain

  • 確認画面 f:id:sksksksksk:20180727212343p:plain

④ 予約作成

予約作成の処理を実装します。 確認画面に「予約する」ボタンを置きます。その「予約する」ボタンをクリックすると、予約が作成されて予約完了画面に遷移します。

Deliveryコントローラーに予約を作成するcreateアクションを定義し、処理をかきます。 app/controllers/users/deliveries_controller.rb

class Users::DeliveriesController < Users::BaseController
  before_action :set_ticket, only: %i(new create)
  def new
    @delivery = Delivery.new
  end

  def create
    @delivery = current_user.deliveries.new(ticket: @ticket)
    if @delivery.save
      redirect_to # 予約完了画面のパス, notice: '予約しました'
    else
      render :new
    end
  end

  private

  def set_ticket
    @ticket = Ticket.find(params[:ticket_id])
  end
end

@ticket はnewとcreateの両方に使用しますのでコールバックでまとめます。 Deliveryを初期化しますが、current_user.deliveriesと書くことで Delivery.new(user: current_user, ticket: @ticket)よりも短く書くことができます。

create をルーティングに追加します。 config/routes.rb

・・・
   resources :deliveries, only: %i(new create) do
・・・

確認画面に「予約する」リンクを追加します。

app/views/users/deliveries/new.html.haml

.col-md-12
  %h3.text-center 予約確認画面
  %table.table
    %thead
      %tr
        ・・・・
        %th.text-center 締切日
        %th # 追加
    %tbody
      %tr
        ・・・・
        %td.text-center= @ticket.expired_at
        %td.text-center= link_to '予約する', [:users, @ticket, @delivery], method: :post, class: 'btn btn-primary' # 追加

f:id:sksksksksk:20180727220800p:plain

そのまま予約完了画面を実装しましょう。

app/views/users/deliveries/complete.html.haml

.col-md-12
  .text-center= link_to 'ホームに戻る', :root

app/controllers/users/deliveries_controller.rb

・・・
  def create
    ・・・・
    if @delivery.save
      redirect_to :users_complete, notice: '予約しました'
      ・・・
    end
  end

  def complete
  end
・・・

config/routes.rb

  #
  # ユーザー
  #
  namespace :users do
    resources :tickets do
    ・・・
    end
    get :complete, to: 'deliveries#complete' #追加
  end

f:id:sksksksksk:20180727221514p:plain

以上で予約作成の実装は完了です。 最後に動作確認をしましょう。

動作確認

  • トップページトップページの「予約する」ボタンをクリックすると、ログイン画面にリダイレクトされる。 f:id:sksksksksk:20180727222336p:plain

  • トップページからナビゲーションバーのサインアップリンクをクリックしてサインアップする。 f:id:sksksksksk:20180727222144p:plain f:id:sksksksksk:20180727222449p:plain

  • トップページから「予約する」ボタンを押すと、予約確認画面に遷移する。 f:id:sksksksksk:20180727222643p:plain

  • 「予約する」ボタンを押すと、予約が作成され予約が作成される。 f:id:sksksksksk:20180727222815p:plain

ソースコード

GitHub - sagaekeiga/ticket-reservation

質疑応答

コメントしてください。

ProgateやRailsチュートリアル、プログラミングスクールを通い終えたが現場のコードはかけない、

一体どうやって書くの?と思っているエンジニアのみなさんのためのチュートリアルを公開しています。

チュートリアル