LoginSignup
2
1

More than 3 years have passed since last update.

『2ページ遷移して会員登録』できる最低限のRailsアプリを丁寧に作る(deviseをウィザード形式に拡張)

Last updated at Posted at 2020-03-14

この記事の基本的な方針

deviseで実装したログイン機能は特にカスタマイズしなければ、1ページでEmailとパスワードの2項目を入力して登録完了です。
これを2ページに拡張し、1ページ目にEmailとパスワードに加えニックネームを、2ページ目でケータイ番号の入力を求めます。

この記事は、以下の「登録画面」「ログイン画面」「TOP画面」の3画面の簡単なアプリを元に拡張していきます。

【TOP画面(ログイン前)】     【TOP画面(ログイン後)】
a0.png a9.png
【登録画面】
a1.png
【ログイン画面】
a2.png

手を動かしながら読みたいようでしたら、以下でこの3画面アプリを手に入れてください。

Terminal
$ git clone -b 超最低限のRailsアプリ(messageコントローラVer)  https://github.com/annaPanda8170/minimum_rails_application.git
$ bundle install
$ bundle exec rake db:create
$ bundle exec rake db:migrate

これ自体の作り方はこちら

最終的に以下のように1ページ目にニックネームの項目が加わり、ケータイ番号の登録をする2ページ目が新たに作られます。

222a.png 333a.png

想定する読み手

既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。

具体的なコーディング手順

完成品GitHub(masterではなく一つのブランチなので注意して下さい)

①取り急ぎ1ページ目のみでニックネームカラムを登録項目として増やす

まず1ページのままでニックネーム入力項目を追加してみましょう。
通常、最初にテーブルを作成するときはrails g model <モデル名>としたときに一緒にマイグレーションファイルも作られますが、今回はすでにあるテーブルにカラムを増やすだけなのでマイグレーションファイルのみを作ります。

Terminal
$ rails g migration <クラス名>

を打ちます。
クラス名はなんでもいいです。rails g migration Aaaでも問題ないですが、rails g migration AddColumn<テーブル名>とするのが一般的です。今回はrails g migration AddColumnUsersとしましょう。
そして、作られたマイグレーションファイルを編集します。

db/migrate/20200xxxxxxxxxxx_add_column_users.rb
class AddColumnUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :nickname, :string 
  end
end

カラムを加えるadd_columnメソッドを使います。第一引数がテーブル名、第二引数がカラム名、第三引数がデータ型です。「ユーザテーブルニックネームカラム文字列型で加えます」ってことですね。極めて直感的です。余談ですが、コードを読むときは悶々とコードのまま黙読で理解しようとせずに、きっちりと日本語(自然言語)に直して読んでみることをオススメいたします。

そして

Terminal
$ rails db:migrate

して、テーブルは完成です。一応データベースを見に行ってカラムが増えているかを確認しましょう。

続いてコントローラで、追加したカラムを受け入れられるようにします。ここはapp/controllers/messages_controller.rbではなく、app/controllers/application_controller.rbを編集します。

app/controllers/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: [:<追加したカラム名>])
  end
end

難しいですが、それぞれのメソッドを完全に理解できなくでも大丈夫です。deviseのGitHubにそうしろと書いてあるのでそれに従います。掃除機を正しく使うのに掃除機を作れるようになる必要はありません。説明書をきちんと読みましょう。
今回はdevise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])ですね。

今回はnicknameなしでは登録できないようにしようと思うので、モデルを編集します。以下を追記します。

app/models/user.rb
〜省略〜
  validates :nickname ,presence: true
〜省略〜

あとはビューにニックネームを入力する欄を加えれば完了ですね。
あれ?ビューファイルはどこにありますか?
ありません。deviseをデフォルトのまま使うにはビューファイルとコントローラファイルの編集が必要ないので見えないところに隠されています。これを編集するには表に出させる必要があります。

Terminal
$ rails g devise:views

すると、隠れていたビューファイル達が表に出てきます。当たり前ですが、ただ表に出てきただけなので編集しなければ何も変わりません。
以下を追記します。

app/views/devise/registrations/new.html.erb
<div class="field">
  <%= f.label :nickname %><br />
  <%= f.text_field :nickname %>
</div>

autofocus: trueに関しては適切な場所に一箇所置きます。詳細は省きます。

これで登録してみます。
大丈夫そうですね。

②2ページ目用モデル

2ページ目で入力されるケータイ番号をデータベースに保存するには、usersテーブルにカラムを追加するのではなく新たにテーブルを作ります。新たにテーブルを作るには、

Terminal
$ rails g model cellphone

して、userテーブルに従属するので、

db/migrate/2020xxxxxxxxxx_create_cellphones.rb
class CreateCellphones < ActiveRecord::Migration[5.2]
  def change
    create_table :cellphones do |t|
      t.integer :cellphone
      t.references :user
      t.timestamps
    end
  end
end

と編集して、

Terminal
$ rails db:migrate

です。慣れたもんです。

ここからモデルにバリデーションとアソシエーションを記述します。
まずapp/models/cellphone.rbvalidates :cellphone ,presence: trueを加えます。バリデーションです。これは簡単ですね。
続いて、アソシエーションです。cellphaneモデルとuserモデルを同時に見てください。

app/models/cellphone.rb
class Cellphone < ApplicationRecord
  validates :cellphone ,presence: true
  belongs_to :user, optional: true
end
app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :name ,presence: true
  has_one :cellphone
end

belongs_toメソッドとhas_oneメソッドが使われています。テーブル同士の関係が1対1の場合に使われます。1対1でどちらにどちらのメソッドを使うかというと、外部キーを置いていない側にbelongs_toを置きます。今回は後から作ったcellphoneの側のテーブルにuser_idカラムがあります。だから、cellphoneテーブルがuserテーブルに従属する(belongs)というイメージですね。そして逆側ににhas_oneを置くことになります。
optional: trueが付いていますが、これはウィザード形式を実装するには必須です。(コントローラでCellphoneモデルのみでインスタンスを生成する場面があるからのようです)

③2ページ目用コントローラ

隠れていたビューファイルとコントローラファイルのうち前者は①で表に出していますが、ここでは後者のコントローラファイルを表に出して編集していく必要があります。 表に出してみましょう。

Terminal
$ rails g devise:controllers <コントローラのディレクトリ名>

コントローラのディレクトリ名はなんでも大丈夫ですが、通常モデル名の複数形なのでrails g devise:controllers usersにします。

さてここで話を整理しましょう。普通、登録画面に関するバックグラウンドの流れは、コントローラのnewアクションを通り、newのビューが表示され、そこのformにクライアントが情報が入力してsubmitが押されると、createアクションに来て、バリデーションを通過すればデータベースに登録するというものであります。図示するとこんな感じです。

newアクション → 登録画面 → createアクション → データベース

ここで2ページ目の登録画面を挟まなくてはならないので、

①newアクション → ①登録画面 → ①createと②newのアクション → ②登録画面 → ②createアクション → データベース

となります。
この①createと②newのアクション②createアクションが難しいです。丁寧に行きましょう。

①createと②newのアクションは、registrations_controller.rbにもとからあるcreateアクション(コメントアウトされています)を使います。②createアクションは新たに適当にアクションを作ります。

まず①createと②newのアクションについてです。ここは①登録画面と②登録画面の橋渡しです。①登録画面の情報を受け取って変数(session変数という特別な変数)に格納して②登録画面に遷移します。

まず、最終的なcreateアクションの全体を掲載します。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  # POST /resource
  def create
    user = User.new(sign_up_params)
    unless user.valid?
      render :new and return
    end
    session[:registration] = {user: user.attributes}
    session[:registration][:user][:password] = params[:user][:password]
    @cellphone = user.build_cellphone
    render :new_cellphone
  end
〜省略〜
end

これを構築してゆきます。
まず、表に出したapp/controllers/users/registrations_controller.rbのコメントアウトしているcreateアクションを復活させ、superを消してbinding.pryをかませてみましょう。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  # POST /resource
  def create
    binding.pry
  end
〜省略〜
end

こんな感じです。http://localhost:3000/users/sign_upにアクセスして適当に値を埋めてSign upボタンを押すと停止するので、ターミナルを見ます。

まず①登録画面で入力された情報は

Terminal
> sign_up_params

で取れます。私の場合、

Output
=> {"email"=>"aaa@aaa", "password"=>"123123123", "password_confirmation"=>"123123123", "name"=>"annaPanda"}

こんな感じです。sign_up_paramsとはdeviseが準備しているメソッドでしょう。
さてこのsign_up_paramsを代入してUserモデルのインスタンスを生成してみましょう。

Terminal
> aaa = User.new(sign_up_params)
Output
=> #<User id: nil, email: "aaa@aaa", created_at: nil, updated_at: nil, nickname: "annaPanda">

これでバリデーションを通過できるかどうかはvalid?メソッドを使います。

Terminal
> aaa.valid?
Output
  User Exists (0.8ms)  SELECT  1 AS one FROM `users` WHERE `users`.`email` = BINARY 'aaa@aaa' LIMIT 1
  ↳ (pry):3
=> true

trueって出ました。OKなようです。
続いて、②登録画面に引き継ぐべき値はattributesメソッドで取れます。

Terminal
> aaa.attributes
Output
=> {"id"=>nil,
 "email"=>"aaa@aaa",
 "encrypted_password"=>"$2a$11$W/lFietoiCkM6Lj6bsuY2eTsvGSg7nakpLCisng73tpv5bp.8OGFu",
 "reset_password_token"=>nil,
 "reset_password_sent_at"=>nil,
 "remember_created_at"=>nil,
 "created_at"=>nil,
 "updated_at"=>nil,
 "nickname"=>"annaPanda"}

パスワードがencrypted_passwordで暗号化されていますね。ですが引き継ぐ段階では元のパスワードが必要なのでそれを引き出します。params変数から、

Terminal
> params[:user][:password]
Output
=> "123123123"

このように取れるのはわかりますね。
さてここでsession変数を使います。普通変数などの値は、ブラウザが読み込まれるたびに一度クリアになります。これは"ステートレス"というHTTP通信の性質です。これに抗って値を保持できるのがsession変数です。
格納するsessionに付随する[:registration]や[:user]や[:password]の名前はなんでもOKですが今回はこうします。
あとはcellphone用のインスタンスを生成できればOKです。これにはnewメソッド出なく特別なbuild_<テーブル名>メソッドを使います。

Terminal
> aaa.build_cellphone
Output
=> #<Cellphone:0x00007fc4c5d1db20 id: nil, cellphone: nil, user_id: nil, created_at: nil, updated_at: nil>

これらの情報を元にcreateアクションを構築すると上のようになるわけです。
再掲します。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  # POST /resource
  def create
    user = User.new(sign_up_params)
    unless user.valid?
      render :new and return
    end
    session[:registration] = {user: user.attributes}
    session[:registration][:user][:password] = params[:user][:password]
    @cellphone = user.build_cellphone
    render :new_cellphone
  end
〜省略〜
end

そしてこのコントローラが使用されるにはルーティングを変更する必要があります。

config/routes.rb
Rails.application.routes.draw do
  get 'messages/index'
  devise_for :users, controllers: {
    registrations: 'users/registrations',
  }
  devise_scope :user do
    get 'cellphones', to: 'users/registrations#new_cellphone'
    post 'cellphones', to: 'users/registrations#create_cellphone'
  end
  root "messages#index"
end

devise_for :usersの先に加えられているのがcreateアクションへのルーティングです。これがないと上で書いたcreateアクションが使われません。
2ページ目用はdevise_scopeメソッドを使います。その先は普通にURIを設定しているだけですね。

②登録画面のビューは以下です。ファイル自体もないので作ります。

app/views/devise/registrations/new_cellphone.html.erb
<%= form_for @cellphone do |f| %>
  <%= render "devise/shared/error_messages", resource: @cellphone %>

  <div class="field">
    <%= f.label :cellphone %><br />
    <%= f.text_field :cellphone %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

続いて、②createアクションです。名前はcreate_cellphoneです。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  def create_cellphone
    @user = User.new(session[:registration]["user"])
    @cellphone = Cellphone.new(cellphone_params)
    unless @cellphone.valid?
      flash.now[:alert] = @cellphone.errors.full_messages
      render :new_cellphone and return
    end
    @user.build_cellphone(@cellphone.attributes)
    @user.save
    sign_in(:user, @user)
    redirect_to root_path
  end

  protected

  def cellphone_params
    params.require(:cellphone).permit(:cellphone)
  end
〜省略〜
end

最終的にこうなります。これを構築してゆきます。

createアクションと同じようにbinding.pryをかませて見てみましょう。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  def create_cellphone
    binding.pry
  end
〜省略〜
end

②createアクションのアクション名はcreate_<テーブル名>でないとならないみたいですね。

まず、session変数に格納されている値を引き出してみましょう。

Terminal
> session[:registration][:user]
Output
=> nil

あれれ?なんででしょう。仕方ないので[:user]を抜いて見てみましょう。

Terminal
> session[:registration]
Output
=> {"user"=>
  {"id"=>nil,
   "email"=>"bbb@bbb",
   "encrypted_password"=>"$2a$11$Gp6J1spbcfu4EiS6EfqJsuGjZV1GB9LZXpWTzTBNDTY0GBmsIDDgm",
   "reset_password_token"=>nil,
   "reset_password_sent_at"=>nil,
   "remember_created_at"=>nil,
   "created_at"=>nil,
   "updated_at"=>nil,
   "nickname"=>"annaPanda",
   "password"=>"321321321"}}

取れました。"user"になっているみたいなんでそれに変えてみましょう。

Terminal
> session[:registration]["user"]
Output
=> {"id"=>nil,
 "email"=>"bbb@bbb",
 "encrypted_password"=>"$2a$11$Gp6J1spbcfu4EiS6EfqJsuGjZV1GB9LZXpWTzTBNDTY0GBmsIDDgm",
 "reset_password_token"=>nil,
 "reset_password_sent_at"=>nil,
 "remember_created_at"=>nil,
 "created_at"=>nil,
 "updated_at"=>nil,
 "nickname"=>"annaPanda",
 "password"=>"321321321"}

OKです。
次に②登録画面で入力されたケータイ番号を引き出してみましょう。
通常のストロングパラメータと同じですね。

Terminal
> params.require(:cellphone).permit(:cellphone)
Output
=> <ActionController::Parameters {"cellphone"=>"09011111111"} permitted: true>

さてコントローラ全体はこんな感じになります。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
〜省略〜
  def create
    user = User.new(sign_up_params)
    unless user.valid?
      render :new and return
    end
    session[:registration] = {user: user.attributes}
    session[:registration][:user][:password] = params[:user][:password]
    @cellphone = user.build_cellphone
    render :new_cellphone
  end

  def create_cellphone
    @user = User.new(session[:registration]["user"])
    @cellphone = Cellphone.new(cellphone_params)
    unless @cellphone.valid?
      flash.now[:alert] = @cellphone.errors.full_messages
      render :new_cellphone and return
    end
    @user.build_cellphone(@cellphone.attributes)
    @user.save
    sign_in(:user, @user)
    redirect_to root_path
  end

  protected

  def cellphone_params
    params.require(:cellphone).permit(:cellphone)
  end
〜省略〜
end

これで完成です。
まだケータイ番号用に桁数を制限などはしていません。余裕があったら挑戦してみてください。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1