【Ruby on Rails】deviseを使ってUserモデルに一対一のリレーションシップでユーザー情報を紐付ける

こんにちは、nishi_talk(@nishi_talk)です。

今回は、deviseを使ってUserモデルに一対一のリレーションシップでユーザー情報を紐づけ方をご紹介します。




今回やりたかったこと

認証情報とユーザー情報を切り分けたかった。
セキュリティー上の観点とユーザー情報が増えれば増えるほど処理が重くなるかなーッと思って今回は以下の内容で対応。(あんまり意味ないのかなー)
ユーザー認証は「devise」
ユーザー情報は別テーブルの「userinfo」に格納する

前提条件

各種バージョン

  • ruby 2.5.1
  • Rails 5.2.0
$ bundle install

「devise」を記述済みで上記コマンドを実行済み

認証の設定

/app/controllers/application_controller.rb

class ApplicationController < ActionController::Base

  # CSRF保護をオンにする以下の1行を有効にします。
  protect_from_forgery with: :exception
  # ログイン済ユーザーのみにアクセスを許可する
  before_action :configure_permitted_paramaters, if: :devise_controller?

  private
  def configure_permitted_paramaters
#devise_parameter_sanitizer = 許可するパラメータを追加(railsのバージョンによって書き方が異なるので注意)
    devise_parameter_sanitizer.permit(:sign_up, keys: [:userinfo_attributes => [:user_id, :name, :body]])
    devise_parameter_sanitizer.permit(:account_update, keys: [:userinfo_attributes => [:user_id, :name, :body]])
  end

end

/app/controllers/users/registrations_controller.rb

class Users::RegistrationsController < Devise::RegistrationsController
#
# 省略
#

#以下を追記

  protected

  # If you have extra params to permit, append them to the sanitizer.
  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up) do |params|
      params.permit(:email, :password, :password_confirmation, :current_password,
                    userinfo_attributes: [:user_id,:name, :body])
    end
  end

  # If you have extra params to permit, append them to the sanitizer.
  def configure_account_update_params
    devise_parameter_sanitizer.permit(:account_update) do |params|
      params.permit(:email, :password, :password_confirmation, :current_password,
                    userinfo_attributes: [:user_id, :name, :body])
    end
  end
end

/app/models/user.rb

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :confirmable
  #has_one = 関連付け  inverse_of = 双方の関連付け
  has_one :userinfo, dependent: :destroy, inverse_of: :user

  accepts_nested_attributes_for :userinfo, update_only: true
end

userとuserinfoのリレーションを作成

/db/migrate/xxxxxxx_create_userinfos.rb

class CreateUserinfos < ActiveRecord::Migration[5.2]
  def change
    create_table :userinfos do |t|
      t.references :user, null: false
      t.string :name
      t.text :body

      t.timestamps
    end
  end
end

マイグレートを実行

$ rails db:migrate

userモデルとの関連付け

/app/models/userinfo.rb

class Userinfo < ApplicationRecord
 belongs_to :user, inverse_of: :userinfo, optional: true
end



各view側の設定

rails

<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "off" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "off" %>
  </div>

  <%= f.fields_for :userinfo, resource.build_userinfo || Userinfo.new do |info| %>
  <%= info.label :name %>
  <%= info.text_field :name%>
  <% end %>

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

<%= render "users/shared/links" %>
<h2>Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <%= devise_error_messages! %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
    <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
  <% end %>

  <div class="field">
    <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
    <%= f.password_field :password, autocomplete: "off" %>
    <% if @minimum_password_length %>
      <br />
      <em><%= @minimum_password_length %> characters minimum</em>
    <% end %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "off" %>
  </div>

  <div class="field">
    <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
    <%= f.password_field :current_password, autocomplete: "off" %>
  </div>

  <%= f.fields_for :userinfo do |info| %>
  <%= info.label :name %>
  <%= info.text_field :name%>
  <% end %>

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

<h3>Cancel my account</h3>

<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>

<%= link_to "Back", :back %>