ウィザード形式でユーザ登録のフォームを作成してみた②-2　〜viewとcontroller〜

## 【この記事の説明】
現在、私はプログラミングスクールでプログラミング（主にRuby）の勉強をしています。
そして、その一環でチーム開発（メルカリもどきのサイト作成）を行っており、私はその中で記事のタイトルにあるように、ウィザード形式でユーザの登録ができるようにする作業を担当致しました。
又、スクールからの指示で、この登録フォームにはdeviseを使用するようにとのことでした。

この記事は、その担当作業についてまとめたものになります。

## 【作業】
ここから私が行った作業（そこで困ったこと）を記事として記載していこうと思います
※この記事では、controllerについて記載したいと思います

#### controller
```ruby:/app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController

  # GET /resource/sign_up
  def new
    super
  end
  
  def create
    @user = User.new(session["devise.regist_data"]["user"])
    @profileaddress = Profileaddress.new(session["profileaddress"])
    @deliveryaddress = Deliveryaddress.new(deliveryaddresses_params)
    unless @deliveryaddress.valid?
      flash.now[:alert] = @deliveryaddress.errors.full_messages
      render :new_deliveryaddresses and return
    end
    @user.build_profileaddress(@profileaddress.attributes)
    @user.build_deliveryaddress(@deliveryaddress.attributes)
    if @user.save
      session["devise.regist_data"]["user"].clear
      session["profileaddress"].clear
      sign_in(:user,@user)
      redirect_to root_path
    else
      render :new
    end
  end

  def new_profileaddresses
    @user = User.new(birthday_save)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @profileaddress = @user.build_profileaddress
  end

  def new_deliveryaddresses
    user = User.new(session["devise.regist_data"]["user"])
    @profileaddress = Profileaddress.new(profileaddress_params)
    unless @profileaddress.valid?
      flash.now[:alert] = @profileaddress.errors.full_messages
      render :new_profileaddresses and return
    end
    user.build_profileaddress(@profileaddress.attributes)
    session["profileaddress"] = @profileaddress.attributes
    @deliveryaddress = user.build_deliveryaddress
  end

  private
    def birthday_save
      date = user_params
      year = date[:"birthday(1i)"]
      month = date[:"birthday(2i)"]
      day = date[:"birthday(3i)"]
      if year.empty? || month.empty? || day.empty?
        return
      end
      # 年月日別々できたものを結合して新しいDate型変数を作り,paramsに数字化したDataをくっつけました
      birthday = Date.new(year.to_i,month.to_i,day.to_i)
      params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge(birthday: birthday)
    end
    def user_params
      params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge("birthday(1i)": params[:birthday][:"birthday(1i)"]).merge("birthday(2i)": params[:birthday][:"birthday(2i)"]).merge("birthday(3i)": params[:birthday][:"birthday(3i)"])
      # paramsの中のカラム→:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana,:birthday(1i),:birthday(2i),:birthday(3i)
    end
    def profileaddress_params
      params.require(:profileaddress).permit(:postal_code, :prefectures,:city,:address,:building)
    end
    def deliveryaddresses_params
      params.require(:deliveryaddress).permit(:family_name,:given_name,:family_name_kana,:given_name_kana,:postal_code,:prefectures,:city,:address,:building,:phone_number)
    end
end

```
ここで、ウィザード形式を行う上で必要なsessionについて説明します

#### session
普通、ページ遷移するたびにフォームの内容はリセットされてしまいます
sessionは、一時的にデータを保存し、最終的に保存できるようにするメソッドです
このsessionを使用することで、ウィザード形式は3ページ目でデータを保存することができます

簡単にいうと、
1ページ目データ入力
　=>sessionを使って一時保存
　　=>2ページ目データ入力
　　　=>sessionを使って一時保存
　　　　=>3ページ目データ入力
　　　　　=>sessionでデータを呼び出して全てのデータを保存（ユーザ登録）
このような形になります

**sessionとattributes**
今回はsessionにハッシュオブジェクトの形で情報を保持させなくてはいけないので、attributesメソッドを用いてデータを整形しています
ただし、パスワードの情報はその中に含まれないので、再度、パスワードだけsessionに代入する必要があります

####｟アソシエーション｠
次にcontrollerといきたいのですが、その前にモデルでアソシエーションを組まないとcontrollerでコード記述ができないので、モデルの記述を行います
（バリデーションに関しては別記事に記載しますので、ここでは省きます）

```ruby:/app/models/user.rb
class User < ApplicationRecord
  has_one :profileaddress, dependent: :destroy
  has_one :deliveryaddress, dependent: :destroy
end
```

```ruby:/app/models/profileaddress.rb
class Profileaddress < ApplicationRecord
  belongs_to :user, optional: true
  # optional: true は外部キーがnullであることを許可するオプション
end
```

```ruby:/app/models/deliveryaddress.rb
class Deliveryaddress < ApplicationRecord
  belongs_to :user,optional: true
end
```
#### has_oneとは
belongs_to => 他方のモデルとの間に「1対1」を示したもの
has_one => 他方のモデルとの間に1対1の関連付け示したものだが、belongs_toとの違いは、他方のモデルのインスタンスをまるごと含む形になるというところである

今回で言えば、
Userモデル　=> has_one
Profileaddressモデル => belongs_to
Deliveryaddressモデル　=> belongs_to
という形であり、UserモデルのインスタンスがProfileaddressモデルとDeliveryaddressモデルのインスタンスをまるごと含む形です
私の感覚的に言えば、
Userが親だからhas_one
他二つのインスタンスは子だからbelongs_to
という感覚になります

ちなみに、has_oneを使用する時は、belongs_toとセットだという認識で良いそうです

**build_association**
build_associationメソッドは、has_one,belongs_toをモデルに記述された時に使用できるメソッドです
このメソッドは関連付けられた型の新しいオブジェクトを返します
又、このオブジェクトは渡された属性でインスタンス化され、そのオブジェクトの外部キーを介してリンクが設定されます
今回を例に取って見ます

```ruby
@user.build_profileaddress(@profileaddress.attributes)
# @profileaddressにはProfileaddressモデルのインスタンス（データ入り）が代入されている
# @user = User.new(session["devise.regist_data"]["user"])が前提となっている
```
この場合、Userインスタンスに関連づけられたProfileaddressモデルのインスタンスが生成されているということになります

#### ｟アクション｠
では、アクションの中身を記述していきたいと思います

まずは**new_deliveryaddressesアクション**です

```ruby:必要な部分のみ抜粋
  def new_profileaddresses
    @user = User.new(birthday_save)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @profileaddress = @user.build_profileaddress
  end
  private
    def birthday_save
      date = user_params
      year = date[:"birthday(1i)"]
      month = date[:"birthday(2i)"]
      day = date[:"birthday(3i)"]
      if year.empty? || month.empty? || day.empty?
        return
      end
      # 年月日別々できたものを結合して新しいDate型変数を作り,paramsに数字化したDataをくっつけました
      birthday = Date.new(year.to_i,month.to_i,day.to_i)
      params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge(birthday: birthday)
    end
    def user_params
      params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge("birthday(1i)": params[:birthday][:"birthday(1i)"]).merge("birthday(2i)": params[:birthday][:"birthday(2i)"]).merge("birthday(3i)": params[:birthday][:"birthday(3i)"])
      # paramsの中のカラム→:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana,:birthday(1i),:birthday(2i),:birthday(3i)
    end
```
上記にsessionは一時保存用のメソッドであることは記載いたしました。
そして、コードを見てもらえればわかりますが、session[]にはUserモデルのインスタンスがそのまま入る形となっています。
つまり、sessinonメソッドに入る前にはUserモデルのインスタンスの形として成り立っていないといけません
（sessionにこの形で入らなければいけない制約はありませんが、最終的保存を考えるとここでこの形で一時保存したかったという意味です）

ここで、私が大変だっところは生年月日のデータ保存でした
**生年月日のデータ保存**
viewの入力フォームは、下記のようになっています

```ruby
.form-group
            生年月日
            %span.required
              必須
            %br
            .field
              != sprintf(f.date_select(:birthday, prefix:'birthday',with_css_classes:'XXXXX', prompt:"--",use_month_numbers:true, start_year:Time.now.year, end_year:1900, date_separator:'%s'),'年','月')+'日'             
```
<img width="379" alt="スクリーンショット 2020-06-07 21.37.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573113/fe39d9c0-88bd-b043-41f8-773e277bafc7.png">

困ったのは、このフォームの形をとると取得するデータの形を
```birthda=>{birthday(1i)=>"0000",birthday(2i)=>"0",birthday(3i)=>"0"}```
の形で取ってしまうことです
Userテーブルのbirthdayカラムは１つなので、普通に下記のようなストロングパラメータでは弾かれてしまうのです

```ruby
def user_params
  params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana,:birthday)
end
```
ですので、下記のようにパラメータを作成しました

```ruby
 def user_params
      params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge("birthday(1i)": params[:birthday][:"birthday(1i)"]).merge("birthday(2i)": params[:birthday][:"birthday(2i)"]).merge("birthday(3i)": params[:birthday][:"birthday(3i)"])
      # paramsの中のカラム→:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana,:birthday(1i),:birthday(2i),:birthday(3i)
end
```

そして、これでは、User.newでUserインスタンスを生成する際もデータが入らないのです
ですので、入るようにデータの形を変更するメソッドを作成しないといけませんでした
それが下記のメソッドになります

```ruby
def birthday_save
      date = user_params
      #user_paramsはストロングパラメータのことです
      year = date[:"birthday(1i)"]
      month = date[:"birthday(2i)"]
      day = date[:"birthday(3i)"]
      if year.empty? || month.empty? || day.empty?
        return
      end
      #↑empty?は配列の中身が空か、文字列の中身が空の場合にtrueを返す。つまり、空だったら、やり直し
      #↓年月日別々できたものを結合して新しいDate型変数を作り,paramsに数字化したDataをくっつけました
      birthday = Date.new(year.to_i,month.to_i,day.to_i)
      params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge(birthday: birthday)
end
```
```ruby
def new_profileaddresses
    @user = User.new(birthday_save)
                     #↑ここでparamsを呼び出している
```
このメソッドで作成したparamsを```new_profileaddressesアクション```で呼び出すことによってUserモデルのインスタンスにデータを代入することができるようになりました

###【次の記事】
次の記事はバリデーションについて記載したいと思います

**次の記事**　→　[ウィザード形式でユーザ登録のフォームを作成してみた③-1　〜バリデーション〜](https://qiita.com/niisan3211/items/1871a9d9bf336856424e)

###【参考】
[ウィザードフォーム形式フォームの実装 -devise×session×データを複数テーブルに分けて保存 ver.- (rails)](https://qiita.com/hrwtrhnk/items/657375db8b6b79bc35e0)
[Rails の session を完全に理解した](https://qiita.com/zettaittenani/items/a75f0da8f44cfe0f85c0#session%E3%81%AE%E5%AE%9F%E4%BD%93%E3%81%AF%E3%81%A9%E3%81%93%E3%81%AB%E3%81%82%E3%82%8B%E3%81%8B)
[[Rails] delegateってなに。　solidus](https://qiita.com/ozipi/items/3a5d39ff693c7c051a74)
[Active Record の関連付け](https://railsguides.jp/association_basics.html#has-one%E9%96%A2%E9%80%A3%E4%BB%98%E3%81%91)
[【Rails】date_selectタグの使い方メモ](https://qiita.com/ozackiee/items/3c027d07cdeb61df6029)
[nil? empty? blank? present? の使い分け](https://qiita.com/somewhatgood@github/items/b74107480ee3821784e6)