LoginSignup
54
47

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-03-10

はじめに

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

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

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

導入

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


$ 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モデルに関する準備は完了です。

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


$ rails g model Profile name:string user:references



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

スクリーンショット 2019-03-10 22.39.12.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



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

profile.rb
class Profile < ApplicationRecord
  belongs_to :user
end


コントローラーでの準備

予め、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にネストさせながら同じフォームで値を送信できます。



<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 %>

  # ...

ビューを見てみよう

スクリーンショット 2019-03-10 21.27.17.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モデルにてバリデーションチェックを受けます。

ユーザー登録してみよう

スクリーンショット 2019-03-10 22.23.12.png

「sign up」を押すと…。


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"がありますね。


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

54
47
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
54
47