【第三章】フリマアプリ開発完全ロードマップ【ユーザ登録/ログイン機能】

はじめに

本記事は第二章 マークアップ作業のつづきです。
Railsでフリマアプリの根幹機能となる商品出品・購入に必要なユーザ登録/ログイン機能を実装します。
gem"devise"を使って実装します。また、ウィザード形式という手法を用います。

制作するアプリ

ユーザ登録、商品出品・購入の機能を備えたフリマアプリです。
メルカリ風、というかほぼメルカリです。メルカリを作ります。

こんな感じのWebサイトを作っていきます。
イラスト.png

ちなみに、この規模のアプリは単価50万円ほどで取引されることが多いそうです。
通常納期は30日ほどだと思いますが、慣れてくれば1週間くらいで作れたりします。
この記事を使って、Web開発を極める一助にしていただければ幸いです。

開発環境
OS   : macOS Catalina 10.15.3
DB   : MySQL
Ruby : 2.5.1
Rails: 5.2.4.2

フリマアプリ開発完全ロードマップ

内容 難易度 所要時間 主要技術
序 章 AWS自動デプロイ ★☆☆☆☆ ★★★☆☆ capistrano
第一章 データベース設計 ★☆☆☆☆ ★☆☆☆☆
第二章 マークアップ作業 ★★☆☆☆ ★★★★★
第三章 ユーザ登録/ログイン機能 ★★★★☆ ★★☆☆☆ devise
第四章 商品出品/編集/詳細表示/削除 ★★★★☆ ★★★☆☆ carrierwave
第五章 商品カテゴリ機能 ★★★★★ ★★★☆☆ ancestry
第六章 商品購入機能 ★★★★★ ★★★★☆ Payjp

記事を読むにあたっての前提条件

この記事には私が作成した大量のコードが載っていますが、
コードレビューのための記事ではありませんので、「コードが汚い!」などのコメントはご遠慮願います。
リファクタリングについては、編集リクエストで私に直接お知らせ頂ければ幸いです。
ご理解のほど、何卒よろしくお願いいたします。

1.devise関連のビューを作成

以下のコマンドで一気に作成可能です。早速実行しましょう。
初期では全てerbファイルなのでhamlへの変換もついでにやっておきます。

ターミナル(ローカル)
rails g devise:views
rails haml:erb2haml

2.deviseコントローラの作成

まずはdeviseコントローラの作成です。以下のコマンドをターミナルで実行。

ターミナル(ローカル)
rails g devise:controllers users

このコマンドでapp/controllers/usersというディレクトリに、何個かコントローラが作成されます。

このコントローラを使用する目的は、ウィザード形式の実装に必要だからです。
住所登録の機能をユーザ登録と並行して行うために、
new_addressやcreate_addressといった自作アクションをこのコントローラに定義する必要が出てきます。

早速、作成したregistrations_controller.rbのnewアクションを編集しましょう。

registrations_controller.rb
  # GET /resource/sign_up
  def new
    @user = User.new
  end

元々書いてあるsuperという記述でも同じ動作をしますが、
理解を深めるために上記のように@userを使うよ、と明記しましょう。

続いて、routes.rbを編集して上記のコントローラを使用することを明示します。
また、先ほどビューの作成を行った事で、user関連の仮ルーティングが必要なくなったので、ついでに削除します。

routes.rb
Rails.application.routes.draw do
  # ↓編集箇所ここから
  devise_for :users, controllers: {
    registrations: 'users/registrations',
  }
  # ↑編集箇所ここまで
  $date = Time.now.in_time_zone('Tokyo').to_s
  root "products#index"
  resources :products do
    collection do
      get "product_create" ,to: 'products#create'
      get "product_update" ,to: 'products#update'
      get "product_destroy",to: 'products#destroy'
    end
  end
  # ↓編集箇所ここから
  resources :users, only: :show
  # ↑編集箇所ここまで
end

以上がコントローラ作成です。続いて、ビューの設定をしていきます。

3.deviseビューの設定

上記で仮ルーティングを修正したので、変更をビューに反映します。
修正するのは、_header.html.hamlnew_session.html.hamlです。

_header.html.haml
#request{action: request.path}
.header
  .header__top
    = link_to root_path do
      %i{class: "fab fa-pagelines"}
      %span{class: "header__top__logo"}
        Frema
    %input{class: "header__top__input", id: "serch-box", placeholder: "何かお探しですか?"}
  .header__bottom
    %ul
      %li
        %i{class: "fas fa-list"}
        カテゴリーから探す
      %li
        %i{class: "fas fa-tags"}
        ブランドから探す
      %li
        %i{class: "fas fa-bell"}
        お知らせ
      %li
        %i{class: "fas fa-check"}
        やることリスト
      %li
        // ↓修正箇所ここから
        = link_to new_user_session_path do
          %i{class: "fas fa-smile"}
          ログイン/新規会員登録
        // ↑修正箇所ここまで
new_session.html.haml
.new_session-wrapper
  .new_session-wrapper__logo
    = link_to root_path do
      %i{class: "fab fa-pagelines"}
      %span{class: "new_session-wrapper__logo__text"}
        Frema
  .new_session-wrapper__main
    .new_session-wrapper__main__title.text-align-center
      アカウントをお持ちでない方はこちら
    // ↓修正箇所ここから
    = link_to new_user_registration_path, class: "new_session-wrapper__main__btn" do
      新規会員登録
    // ↑修正箇所ここまで
  .new_session-wrapper__main
    %input{class: "new_session-wrapper__main__text",placeholder: "メールアドレス"}
    %input{class: "new_session-wrapper__main__text",placeholder: "パスワード"}
    %input{type:"submit", class: "new_session-wrapper__main__btn", value: "ログイン"}

次に、deviseビューの中身を自作のビューに置き換えます。

自作ビュー(views/users/new_session.html.haml)のコードを全てコピーし、
deviseビュー(views/devise/sessions/new.html.haml)に貼り付けます。

上記の作業が終わったら、自作ビューのファイルは不要になるので、削除してOK。
58755f9986b3a833360fc08ecbe3f660.gif

上記と同様に、以下のファイルも中身の入れ替えをしてください。

コピー元ファイル 貼り付け先ファイル 備考
users/new_session.html.haml devise/sessions/new.html.haml 上記で作業済み
users/new_user.html.haml devise/registrations/new.html.haml
users/new_address.html.haml devise/registrations/new_address.html.haml フォルダ移動でOK
users/create_address.html.haml devise/registrations/create_address.html.haml フォルダ移動でOK

ビューの設定はここまで。続けて、form_withなどを使って機能の実装に移ります。

4.とりあえずユーザ登録できるようにする

ウィザード形式の前に、まずは普通にユーザ登録をできるようにしておきます。
前章で以下のような画面を作りましたが、まずはこの画面だけでユーザ登録を成立させます。
スクリーンショット 2020-04-28 22.50.32.png
ちょっと変ですが、必須事項を入力して次へ進むをクリックしたらユーザ登録が完了するようにしましょう。
何を実装するにしても、ひとつひとつ段階を踏むのがオススメです。

以下のようにregistrations/new.html.hamlをform_withで書き換えます。

registrations/new.html.haml
= form_with model: @user,url: user_registration_path do |f|
  .new_session-wrapper
    .new_session-wrapper__logo
      = link_to root_path do
        %i{class: "fab fa-pagelines"}
        %span{class: "new_session-wrapper__logo__text"}
          Frema
      %span{class: "new_session-wrapper__logo__title title-active after-active"}
        会員情報
      %span{class: "new_session-wrapper__logo__title"}
        住所情報
      %span{class: "new_session-wrapper__logo__title-done"}
        登録完了
    .new_session-wrapper__main
      .new_session-wrapper__main__head
        会員情報
    .new_session-wrapper__main
      = render "devise/shared/error_messages", resource: @user
      .new_session-wrapper__main__title
        ニックネーム
        %span{class: "require"} 必須
      = f.text_field :nickname, {class: "new_session-wrapper__main__text", placeholder: "例)フリマ太郎"}
      .new_session-wrapper__main__title
        メールアドレス
        %span{class: "require"} 必須
      = f.text_field :email, {class: "new_session-wrapper__main__text", placeholder: "PC・携帯どちらでも可"}
      .new_session-wrapper__main__title
        パスワード
        %span{class: "require"} 必須
      = f.password_field :password, {class: "new_session-wrapper__main__text", placeholder: "7文字以上、英字数字両方を含むこと"}
      .new_session-wrapper__main__title
        パスワード(確認)
        %span{class: "require"} 必須
      = f.password_field :password_confirmation, {class: "new_session-wrapper__main__text", placeholder: "7文字以上、英字数字両方を含むこと"}
      .new_session-wrapper__main__title
        お名前(全角)
        %span{class: "require"} 必須
        .flexbox
          = f.text_field :firstname, {class: "new_session-wrapper__main__text text-half", placeholder: "例)山田"}
          = f.text_field :lastname , {class: "new_session-wrapper__main__text text-half", placeholder: "例)彩"}
      .new_session-wrapper__main__title
        お名前カナ(全角)
        %span{class: "require"} 必須
        .flexbox
          = f.text_field :firstname_kana, {class: "new_session-wrapper__main__text text-half", placeholder: "例)ヤマダ"}
          = f.text_field :lastname_kana , {class: "new_session-wrapper__main__text text-half", placeholder: "例)アヤ"}
      .new_session-wrapper__main__title
        生年月日
        %span{class: "require"} 必須
        .flexbox
          - year = ["--"]
          - for num in 0..120
            - year << 2020-num
          = f.select :birth_year, year, {}, class: "new_session-wrapper__main__select three-select"- month = ["--"]
          - for num in 1..12
            - month << num
          = f.select :birth_month, month, {}, class: "new_session-wrapper__main__select three-select"- day = ["--"]
          - for num in 1..31
            - day << num
          = f.select :birth_day, day, {}, class: "new_session-wrapper__main__select three-select".new_session-wrapper__main__title
        電話番号
        %span{class: "require"} 必須
      = f.text_field :tel_number, {class: "new_session-wrapper__main__text", placeholder: "ハイフンなし10~11桁"}
      .new_session-wrapper__main__title
      .new_session-wrapper__main__caution
        ※本人情報は正しく入力してください。会員登録後、お時間を頂く場合があります。
      = f.submit "次へ進む", class: "new_session-wrapper__main__btn"

selectの中身は、配列を定義して記述してみました。
ビューでやるのはちょっと...という方はコントローラで@year@monthといった
配列オブジェクトを定義し、ビューに渡すのもアリだと思います。お好みでどうぞ。

続いてapplication_controller.rbを以下のように編集します。

application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:firstname, :lastname, :firstname_kana, :lastname_kana, :nickname, :birth_year, :birth_month, :birth_day, :tel_number])
  end
end

deviseが受け取るパラメータは初期状態でnameemailだけなので、
今回追加したnicknamefirstnameなどを受け取れるようにするための編集です。

これでユーザを新規登録できるようになります。
適当なダミーデータを入力して、新規登録してみてください。
ログイン後、ヘッダーのログイン/新規登録ボタンがマイページへボタンに変わっていればログインできてます。
054988ff634754f41ab33e61f27c16ab.gif
この後もユーザ登録機能を引き続きデバッグするので、
マイページ左下のログアウトボタンで一旦ログアウトしておきます。
スクリーンショット 2020-04-29 14.34.25.png

5.ウィザード形式の実装

お待ちかね、ウィザード形式の実装です。
以下のような手順で進めます。

 1. 次へ進むをクリックした後にnew_addressに遷移させる
 2. 遷移しても@userの中身を保持できるようにする
 3. 遷移先で@addressを新たに生成する
 4. バリデーションをチェックし、問題がなければ完了画面に遷移させる

それでは、実際に手を動かして実装作業を進めます。

1. 次へ進むをクリックした後にnew_addressに遷移させる

new_addressへのルーティングが必要になります。
routes.rbを以下のように編集しましょう。

routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations',
  }
  # 編集箇所ここから
  devise_scope :user do
    get  'addresses', to: 'users/registrations#new_address'
    post 'addresses', to: 'users/registrations#create_address'
  end
  # 編集箇所ここまで
  $date = Time.now.in_time_zone('Tokyo').to_s
  root "products#index"
  resources :products do
    collection do
      get "product_create" ,to: 'products#create'
      get "product_update" ,to: 'products#update'
      get "product_destroy",to: 'products#destroy'
    end
  end
  resources :users, only: :show
end

続けて、registrations_controller.rbのcreateアクションを編集します。

registrations_controller.rb
  # POST /resource
  def create
    @user = User.new(sign_up_params)
    if @user.valid?
      redirect_to addresses_path
    else
      render :new
    end
  end

これで遷移自体は可能になりますが、このままだと遷移後に@userの情報が消滅します。
なので、sessionという機能を使って@userを保持できるようにします。

2. 遷移しても@userの中身を保持できるようにする

registrations_controller.rbのcreateアクションを編集します。

registrations_controller.rb
  # POST /resource
  def create
    @user = User.new(sign_up_params)
    if @user.valid?
      session["devise.regist_data"] = {user: @user.attributes}
      session["devise.regist_data"][:user]["password"] = params[:user][:password]
      redirect_to addresses_path
    else
      render :new
    end
  end

session["devise.regist_data"]という記述を用いる事で、遷移後も@userの中身を保持できます。

{user: @user.attributes}という記述については、少々複雑な話になるので、あまり気にしなくてもOKです。
一応要点だけ説明すると、パスワードの情報を保持するのに必要な記述です。
単にsession["devise.regist_data"] = @userと記述してしまうと、
パスワードの情報の保持が面倒になります。これを回避しています。
また、2つめのsessionの記述も同様にパスワードの保持に関わる記述です。

3. 遷移先で@addressを新たに生成する

registrations_controller.rbnew_addressという自作アクションを定義します。

registrations_controller.rb
  def new_address
    @user = User.new(session["devise.regist_data"]["user"])
    @address = Address.new
  end

これで遷移先のnew_addressビューでform_withが使えるようになったので、書き換えていきます。

new_address.html.haml
= form_with model: @address, local: true do |f|
  .new_session-wrapper
    .new_session-wrapper__logo
      = link_to root_path do
        %i{class: "fab fa-pagelines"}
        %span{class: "new_session-wrapper__logo__text"}
          Frema
      %span{class: "new_session-wrapper__logo__title before-active after-active"}
        会員情報
      %span{class: "new_session-wrapper__logo__title title-active after-active"}
        住所情報
      %span{class: "new_session-wrapper__logo__title-done"}
        登録完了
    .new_session-wrapper__main
      .new_session-wrapper__main__head
        住所情報
    .new_session-wrapper__main
      .new_session-wrapper__main__title
        郵便番号
        %span{class: "require"} 必須
      = f.text_field :post_number, {class: "new_session-wrapper__main__text", placeholder: "例)123-4567"}
      .new_session-wrapper__main__title
        都道府県
        %span{class: "require"} 必須
        = f.select :prefecture, ["北海道","青森県","岩手県","宮城県","秋田県","山形県","福島県","茨城県","栃木県","群馬県","埼玉県","千葉県","東京都","神奈川県","新潟県","富山県","石川県","福井県","山梨県","長野県","岐阜県","静岡県","愛知県","三重県","滋賀県","京都府","大阪府","兵庫県","奈良県","和歌山県","鳥取県","島根県","岡山県","広島県","山口県","徳島県","香川県","愛媛県","高知県","福岡県","佐賀県","長崎県","熊本県","大分県","宮崎県","鹿児島県","沖縄県"], {}, class: "new_session-wrapper__main__select"
      .new_session-wrapper__main__title
        市区町村
        %span{class: "require"} 必須
      = f.text_field :city, {class: "new_session-wrapper__main__text", placeholder: "例)渋谷区"}
      .new_session-wrapper__main__title
        番地
        %span{class: "require"} 必須
      = f.text_field :address, {class: "new_session-wrapper__main__text", placeholder: "例)道玄坂3-2"}
      .new_session-wrapper__main__title
        建物名
        %span{class: "any"} 任意
      = f.text_field :apartment, {class: "new_session-wrapper__main__text", placeholder: "例)フォンティスビル7F"}
      .new_session-wrapper__main__caution
        ※本人情報は正しく入力してください。会員登録後、お時間を頂く場合があります。
      %input{type: "submit",class: "new_session-wrapper__main__btn", value: "次へ進む"}

4. バリデーションをチェックし、問題がなければ完了画面に遷移させる

registrations_controller.rbcreate_addressという自作アクションを定義します。
また、前述のcreateアクション(userの情報を保存するほう)のストロングパラメータは、
deviseに用意されているものを使いましたが、自作アクションにおいては
自分でストロングパラメータを定義する必要があります。

registrations_controller.rb
  def create_address
    @user = User.new(session["devise.regist_data"]["user"])
    @address = Address.new(address_params)
    if @address.valid?
      @user.save
      @address = Address.new(address_params.merge(user_id: @user.id))
      @address.save
      session["devise.regist_data"]["user"].clear
      sign_in(:user, @user)
    else
      render :new_address
    end
  end

  protected

  def address_params
    params.require(:address).permit(:post_number, :prefecture, :city, :address, :apartment)
  end

これでウィザード形式を使ったユーザ登録が可能になりました!!

6.ログイン機能の実装

メールアドレスとパスワードの入力でログインを可能にします。すぐ終わります。
sessions/new.html.hamlを以下のように編集します。

sessions/new.html.haml
.new_session-wrapper
  .new_session-wrapper__logo
    = link_to root_path do
      %i{class: "fab fa-pagelines"}
      %span{class: "new_session-wrapper__logo__text"}
        Frema
  .new_session-wrapper__main
    .new_session-wrapper__main__title.text-align-center
      アカウントをお持ちでない方はこちら
    = link_to new_user_registration_path, class: "new_session-wrapper__main__btn" do
      新規会員登録
  .new_session-wrapper__main
    = form_with model:resource, url: session_path(resource_name) do |f|
      = f.text_field :email, {class: "new_session-wrapper__main__text",placeholder: "メールアドレス"}
      = f.password_field :password, {class: "new_session-wrapper__main__text",placeholder: "パスワード"}
      = f.submit "ログイン", {class: "new_session-wrapper__main__btn"}

form_withを使用してmodelresource
urlsession_path(resource_name)とすればログイン機能になります。
これは暗記でいいかなと思います。もしくはメモ帳にメモです。

お疲れ様でした!!

以上がdeviseによるウィザード形式を用いたユーザ登録/ログイン機能の実装でした。

第四章 商品出品/編集/詳細表示/削除に続きます。

内容 難易度 所要時間 主要技術
序 章 AWS自動デプロイ ★☆☆☆☆ ★★★☆☆ capistrano
第一章 データベース設計 ★☆☆☆☆ ★☆☆☆☆
第二章 マークアップ作業 ★★☆☆☆ ★★★★★
第三章 ユーザ登録/ログイン機能 ★★★★☆ ★★☆☆☆ devise
第四章 商品出品/編集/詳細表示/削除 ★★★★☆ ★★★☆☆ carrierwave
第五章 商品カテゴリ機能 ★★★★★ ★★★☆☆ ancestry
第六章 商品購入機能 ★★★★★ ★★★★☆ Payjp

あとがき

この記事のここをもっと解説してほしい!などありましたら、コメント欄でお伝えください。
また、コメント欄での暴言や誹謗中傷の書き込み等はご遠慮ください。マジで凹むので。

いつも記事を読んで頂いている皆さま、LGTMを押していただいている皆さま、本当にありがとうございます!!
有益な記事を執筆するモチベーションに繋がりますので、この記事が役に立ったなと思って頂けたら、
是非LGTMボタンのクリックSNSでのシェアをよろしくお願いします。


添野文哉(@enjoy_omame)
https://twitter.com/enjoy_omame

enjoy_omame
TECH CAMPというスクール出身です。 Rails系男子。hamlとjQueryが好きです。
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした