[セッション実装]ユーザー登録(APIの続編)

##チーム課題を個人でやってみるけん_２
セッション編です
[前のページ：API編はこちら](https://qiita.com/i-to-to-to-mi/items/4a7c239a3d5f3ef8ae99)
[日本語化について](https://qiita.com/i-to-to-to-mi/items/ce8a497a46dfe9641d6a)
[バリデーションについて](https://qiita.com/i-to-to-to-mi/items/6b16bb090c7b4bfe08b8)
[バリデーションのテストコードについて](https://qiita.com/i-to-to-to-mi/items/ce34bbde53bb70593324)



**以下はAPI編で作成すみなので飛ばします**
rails new XXX -d mysql
rails db:create
Gemfile[device]記述後　rails g devise:install
ルーティング設定
rails g controller XXX
app/views/XXX/にindex.html.haml作成
rails g devise XXX
マイグレーションファイル記述後 rails db:migrate
application_controller.rb修正
app/models/user.rb修正
rails g devise:views
views/devise/registrations/new.html.haml一部追記
index修正
etc...

##deviseをカスタマイズしてウィザード形式の新規登録にする

```ruby:ターミナル
$ rails g model address
```
マイグレーションファイルを設定
Userモデルとのリレーションを記述
**※必要に応じてカラムの内容は変更してください**

```ruby:xxxxxxxxxx_create_addresses.rb
#変更前
class CreateAddresses < ActiveRecord::Migration[5.2]
  def change
    create_table :addresses do |t|

      t.timestamps
    end
  end

#変更後
class CreateAddresses < ActiveRecord::Migration[5.2]
  def change
    create_table :addresses do |t|
      t.string :address_firstname, null: false
      t.string :address_lastname, null: false
      t.string :address_kana_firstname, null: false
      t.string :address_kana_lastname, null: false
      t.string :zipcode, null: false
      t.string :prefectures, null: false
      t.string :municipalities, null: false
      t.text :address, null: false
      t.string :building
      t.integer :phone_number
      t.references :user
    end
  end
end

```
 **1/8更新**
zipcodeをintegerにしていましたがstring型に変更しました
理由は１１１−１１１１と言うハイフンの入った形で郵便番号を登録したいからです。
INT型だと数字のみしか通さないのでvalidatesが永遠に通らない現象が起きてしまうためです。
ハイフンなしでいい場合はINT型のままで問題ないと思います。

```ruby:ターミナル
$ rails db:migrate
```
Sequel Proなどで`zipcodeカラム`や`addressカラム`、`user_idカラム`が作られているのを確認。


モデルを編集し、バリデーションおよびアソシエーションを設定
1/14更新
都道府県はenumで記述するのでバリデーションのテストをするときのfactorybotのあたいは数字で入力します。

```ruby:app/models/address.rb
class Address < ApplicationRecord
  belongs_to :user, optional: true
  validates :address_firstname, :address_lastname, :prefectures, :municipalities, :address ,presence: true
  validates :address_kana_firstname, :address_kana_lastname, presence: true, 
            # カナのみ可
            format: { with: /\A([ァ-ン]|ー)+\z/, message: "is must NOT contain any other characters than alphanumerics." }
  validates :postal_code,presence: true,
              # 郵便番号(ハイフンあり7桁)
            format: { with: /\A\d{3}-\d{4}\z/, message: "is must NOT contain any other characters than alphanumerics." }
  enum prefectures:{
    北海道:1,青森県:2,岩手県:3,宮城県:4,秋田県:5,山形県:6,福島県:7,
    茨城県:8,栃木県:9,群馬県:10,埼玉県:11,千葉県:12,東京都:13,神奈川県:14,
    新潟県:15,富山県:16,石川県:17,福井県:18,山梨県:19,長野県:20,
    岐阜県:21,静岡県:22,愛知県:23,三重県:24,
    滋賀県:25,京都府:26,大阪府:27,兵庫県:28,奈良県:29,和歌山県:30,
    鳥取県:31,島根県:32,岡山県:33,広島県:34,山口県:35,
    徳島県:36,香川県:37,愛媛県:38,高知県:39,
    福岡県:40,佐賀県:41,長崎県:42,熊本県:43,大分県:44,宮崎県:45,鹿児島県:46,沖縄県:47
  }
end
```
`optional: true`は外部キーがnullであることを許可するオプション

Userモデルについてもアソシエーションを設定

```ruby: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, :omniauthable, omniauth_providers: [:facebook, :google_oauth2]
  
  validates :nickname, :firstname, :lastname, :birthday, presence: true
  validates :password, presence: true, length: { minimum: 7 }, 
            # 英数字のみ可
            format: { with: /\A[a-z0-9]+\z/i, message: "is must NOT contain any other characters than alphanumerics." }
  validates :email, presence: true, 
            # 重複不可
            uniqueness: { case_sensitive: false }, 
            # 英数字のみ可,@を挟んだemailの形になっているか
            format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i, message: "is must NOT contain any other characters than alphanumerics." }
  validates :kana_firstname, :kana_lastname, presence: true, 
            # カナのみ可
            format: { with: /\A([ァ-ン]|ー)+\z/, message: "is must NOT contain any other characters than alphanumerics." }
            has_many :sns_credentials
            has_one :address #ここを足したよ

```
この記述を忘れるとこんなエラーが出ちゃう。

```
NoMethodError (undefined method `build_address' for #<User:0x00007fba7c11bd20>):

app/controllers/users/registrations_controller.rb:25:in `create'
```

**ルーティングやdeviceの設定はAPIの時に完了しているので飛ばします**

##newアクションと対応するビューを編集

```ruby:app/controllers/users/registrations_controller.rb
#編集前
# GET /resource/sign_up
  # def new
  #   super
  # end

#編集後
  # GET /resource/sign_up
  def new
    super
  end
```
newアクションに対応するフォームの修正

```ruby:app/views/devise/registrations/new.html.erb
#修正前
= f.submit "Sign up"
#修正後
= f.submit "Next"
```

##createアクションを編集
バリデーションチェック
セッション保持
住所情報登録で使用するインスタンスを生成、当該ページへ遷移

```ruby:app/controllers/users/registrations_controller.rb
#変更前
  def create
    if params[:sns_auth] == 'true'
　　　#パスが自動生成される
      pass = Devise.friendly_token
      params[:user][:password] = pass
      params[:user][:password_confirmation] = pass
    end
　　　#親モデルの同名メソッドをそのまま実行(registrations#createアクションが呼ばれる）
    super
  end


#変更後
class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]
#省略
  def create
    if params[:sns_auth] == 'true'
      pass = Devise.friendly_token
      params[:user][:password] = pass
      params[:user][:password_confirmation] = pass
    end
    #バリデーションチェック
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    #sessionに保持させる
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    #インスタンスを生成、当該ページへ遷移
    @address = @user.build_address
    render :new_address
  end

# 省略
  protected

  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  end
end
```


##new_addressアクションと対応するビューを編集
・住所情報を登録させるページを表示するnew_addressアクションのルーティング設定
・住所情報を登録するcreate_addressアクションのルーティングも設定

```ruby:config/routes.rb
#変更前
Rails.application.routes.draw do
  devise_for :users, controllers: {
    sessions: 'users/sessions',
    omniauth_callbacks: 'users/omniauth_callbacks',
    registrations: 'users/registrations'
  }
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  root 'users#index'  # ログインor新規登録を選ぶページ
  resources :users, only: :new  # 新規登録方法を選ぶページ
end
#変更後
Rails.application.routes.draw do
  devise_for :users, controllers: {
    sessions: 'users/sessions',
    omniauth_callbacks: 'users/omniauth_callbacks',
    registrations: 'users/registrations'
  }
  # Devise::RegistrationsControllerを継承した、Users::RegistrationsControllerを作成したイメージ
  devise_scope :user do
    get 'addresses', to: 'users/registrations#new_address'
    post 'addresses', to: 'users/registrations#create_address'
  end
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  root 'users#index'  # ログインor新規登録を選ぶページ
  resources :users, only: :new  # 新規登録方法を選ぶページ
end
```

```ruby:app/views/devise/registrations/new_address.html.haml
%h2 住所情報登録
= form_for @address do |f|
  = render "devise/shared/error_messages", resource: @address
  .field
    = f.label :address_firstname
    %br/
    = f.text_field :address_firstname
  .field
    = f.label :address_lastname
    %br/
    = f.text_field :address_lastname
  .field
    = f.label :address_kana_firstname
    %br/
    = f.text_field :address_kana_firstname
  .field
    = f.label :address_kana_lastname
    %br/
    = f.text_field :address_kana_lastname
  .field
    = f.label :zipcode
    %br/
    = f.text_field :zipcode
  .field
    = f.label :prefectures
    %br/
    = f.text_field :prefectures
  .field
    = f.label :municipalities
    %br/
    = f.text_field :municipalities
  .field
    = f.label :address
    %br/
    = f.text_field :address
  .field
    = f.label :building
    %br/
    = f.text_field :building
  .field
    = f.label :phone_number
    %br/
    = f.text_field :phone_number
  .actions
    = f.submit "Sign up"
= render "devise/shared/links"
```

##create_addressアクションと対応するビューを編集

```ruby:app/controllers/users/registrations_controller.rb
#変更前
    #バリデーションチェック
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    #sessionに保持させる
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    #インスタンスを生成、当該ページへ遷移
    @address = @user.build_address
    render :new_address
  end

# 省略
  protected

  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  end
end

#createアクションの下に追記
    def create_address
    @user = User.new(session["devise.regist_data"]["user"])
    @address = Address.new(address_params)
    unless @address.valid?
      flash.now[:alert] = @address.errors.full_messages
      render :new_address and return
    end
    @user.build_address(@address.attributes)
    @user.save
    sign_in(:user, @user)
  end

#省略
  protected

  def address_params
    params.require(:address).permit(
      :address_firstname, :address_lastname, :address_kana_firstname, :address_kana_lastname,
      :zipcode, :prefectures, :municipalities, :address, :building, :phone_number
    )
  end


  # If you have extra params to permit, append them to the sanitizer.
  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  end
end
```
##address_createに対応するビューを作成

```ruby:app/views/devise/registrations/create_address.html.haml
%h2登録が完了しました
= link_to "トップへ戻る", root_path
```

##参考

- [ウィザード形式の登録フォームでバリデーション](https://qiita.com/ATORA1992/items/c073620be3469bfa1f54)
- [Railsのバリデーションエラーメッセージの取得](https://qiita.com/ATORA1992/items/218f17deb66c82f27fbc)
- [Railsウィザード形式フォームで新規登録〜Devise+session〜](https://qiita.com/ATORA1992/items/40fc543742a6df5a17c1#)
- [【Rails】Sessionの使い方について](https://qiita.com/ozackiee/items/4ee774c81b2a0c571c05)
- [[Rails]ウィザードフォーム実装でfields_forを使って複数のテーブルに保存する](https://qiita.com/NT90957869/items/614842934feff9812203)
- [session管理](https://qiita.com/NT90957869/items/614842934feff9812203)
- [facebook認証](https://qiita.com/keirof1892/items/4d086e5ed0872dee7dac)
- [【Rails】omniauth_callbacks_controllerについて](https://qiita.com/yoshi_4/items/580374b30242a265ec22)

####devise_scopeに関する記事

- [[Rails]Deviseで任意のルーティングだけを行う](https://qiita.com/chamao/items/de00920c343a3e237d20)
- [ほげノート](https://matt-note.hatenadiary.jp/entry/2019/02/24/200435)
- [Deviseのスコープとルーティングについて](https://www.tmp1024.com/articles/devise-scope-and-path)

##終わりに
ビューを整えることなど細々した修正は必要ですがセッションとAPIのサーバーサイド実装完了です。
作り始めの時に
registrations_controller.rbでunless @user.valid?という記述の下にパスワード自動生成のコードを記述していました。
valid?の時点でパスがないので全て弾かれパス生成する箇所が読み込まれていないことに気づくのにひどく時間がかかってしまい苦しみました。。。
（早くbinding.pry使えばよかった(>_<)）

バリデーションなどの記載がまだまだ
不十分＋日本語化していないのでその辺もちょくちょく修正していければと思います。
不備やアドバイス等ございましたらお知らせいただければ幸いです。