【Rails】deviseのフォームで２つのモデルに同時に値を送る方法(例: UserモデルとProfileモデル)

# はじめに

UserモデルがProfileモデルを1つ持っているという状況を考えます。
（User has_one Profile のような状況）

usersテーブルのカラムにプロフィール情報を追加して管理するより、ユーザー認証のためのロジックとプロフィール情報のデータを分けて管理したいと思ったということですね。

そして、この記事では「１つのフォームで２つのモデルに同時に値を送る（かつ、ProfileがUserにネストしている）」という要件を実装していきます。


# 導入

ますはUserモデルの準備をします。

```bash

$ rails new devise_app             # => アプリ作成
$ rails db:create                  # => データベースの作成
$ rails g devise:install           # => deviseの導入(Gemfileに(gem 'devise')必要)
$ rails g devise:view              # => deviseのビューの作成
$ rails g devise User              # => Userモデルの作成
$ rails g devise:controllers users # => usersコントローラの作成
```
これでUserモデルに関する準備は完了です。
<br>

Profileモデルも作ります。
Userモデルから呼び出せるように、外部キー制約を付けておきます。(user_idカラムを持つ)

```bash

$ rails g model Profile name:string user:references
```

<br>
現在、サインアップ画面(/users/sign_up)は以下のようになっています。

<img width="357" alt="スクリーンショット 2019-03-10 22.39.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/324456/efe75c55-d04d-7432-962a-b128db69f6e8.png">

もちろんまだProfileに関するフォームはありません。
以降の本編で、実装していきます。

# 本編

## UserモデルとProfileモデルの関連付け

Userモデルを以下のようにprofileと関連づけます

```user.rb
class User < ApplicationRecord

  has_one :profile
  accepts_nested_attributes_for :profile

  # ...

end
```

「accepts_nested_attributes_for :profile」によって、Userモデルを通して、Profileの属性の値をDBに保存することが出来ます。
この関連付けの詳しい挙動を知りたい方は以下のアドレスが参考になるかもしれません。

「ActiveRecord::NestedAttributes::ClassMethods」
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

<br>
ProfileモデルはUserモデルにbelongs_toさせましょう。
(今回の場合は、外部キー制約を付けてmigrateしているので、既に定義されています。）

```profile.rb
class Profile < ApplicationRecord
  belongs_to :user
end
```
<br>

## コントローラーでの準備

予め、controllers/users/registration_controller.rbをコマンドで作成していますので、そこに移動しましょう。

まずはインスタンスがサインアップ画面に送られるよう、コードを書きます。
(あとで、/users/sign_upパスへのGETリクエストがここのコントローラーを通るようにルーティングを設定します。)

```controllers/users/registration_controller.rb

def new
  @user = User.new
  @profile = @user.build_profile
end
```

「build_profile」は、Userモデルに定義した「has_one :profile」によって、Userモデルのインスタンスメソッドとして使えるようになっているメソッドです。
「@profile = Profile.new」としても、ビューで@userにネストした@profileを参照することができません。

## ルーティングの準備

先ほど準備したコントローラーを通るように、ルーティングを追加します。

```route.rb
Rails.application.routes.draw do
  devise_for :users, controllers: { registrations: 'users/registrations' }
end
```


## ビューの準備

devise/registations/new.html.erbに「f.fields_for :profile do ~ end」を加えます。
これで、@profileを@userにネストさせながら同じフォームで値を送信できます。

```erb


<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  # ...

  <%= f.fields_for :profile do |f| %>
    <div class="field">
      <%= f.label :name %><br />
      <%= f.text_field :name %>
    </div>
  <% end %>

  # ...
```

### ビューを見てみよう

<img width="398" alt="スクリーンショット 2019-03-10 21.27.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/324456/a45ed168-1628-a9ff-efe5-e7dbd097a967.png">

やりました！　「Name」のフォームが追加されていますね。
これで、@userの情報を送るのと同時に、@profileの情報も送れます。

あとはこの情報を受け取るためのストロングパラメータを設定しましょう。

## ストロングパラメータの設定

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


  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [profile_attributes: [:name] ])
  end
```

Userモデルに定義した「accepts_nested_attributes_for :profile」のおかげで、profile_attributesをキーとして、@profileの属性の中身を受け取ることが出来ます。
（今回だと、nameの値が受け取れます。）
この値はProfileモデルにてバリデーションチェックを受けます。

## ユーザー登録してみよう

<img width="392" alt="スクリーンショット 2019-03-10 22.23.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/324456/c7c71572-1914-4c79-38f2-408eb442bf2c.png">

「sign up」を押すと…。

```bash

Started POST "/users" for 127.0.0.1 at 2019-03-10 22:26:39 +0900
Processing by Users::RegistrationsController#create as HTML
　Parameters: {"utf8"=>"✓", 
"authenticity_token"=>"7KormqpmzDyJF+Yva+T84vho9oZ7OgKNYiXOFEzw9D2sv8m0rX3EtjTChHCR0YJhtugatTH5lLvTdsxh7J8qrg==",
 "user"=>{"email"=>"user@example.com", 
"password"=>"[FILTERED]", 
"password_confirmation"=>"[FILTERED]", 
"profile_attributes"=>{"name"=>"user"}}, 
"commit"=>"Sign up"}

```
パラメータがちゃんと値を受け取り、ユーザー作成が完了します！
下から2行目に、先程ストロングパラメーターで受け取るキーとして許可した"profile_attributes"がありますね。


```bash

  User Create (0.4ms)  INSERT INTO `users` (`email`, `encrypted_password`, `created_at`, `updated_at`) VALUES ('user@example.com', '$2a$11$7siXRQ2mONK2FR4C0fB40uZbs30IZKSCH9/D2/JHx9ICZqRxf7hIK', '2019-03-10 13:26:39', '2019-03-10 13:26:39')
  Profile Create (0.3ms)  INSERT INTO `profiles` (`name`, `user_id`, `created_at`, `updated_at`) VALUES ('user', 2, '2019-03-10 13:26:39', '2019-03-10 13:26:39')
   (1.0ms)  COMMIT
```

もしUserモデルかProfileモデル、どちらかのバリデーションに引っかかったらROLLBACKします。


# おわりに

以上で、deviseで２つのモデルに同時に値を送る方法について解説を終わります。
実装してる内容自体はそこまで複雑じゃないですが、ググっても明確にまとめてある所がなかったり数年前だったりの古い情報が多かったので記事書きました。
ざっくりした内容ですが誰かの助けになればいいな、と思います。