LoginSignup
31

More than 3 years have passed since last update.

deviseのユーザー登録機能を拡張する

Last updated at Posted at 2019-09-27

はじめに

deviseのデフォルトでは、emailとパスワードしかuserテーブルに保存されません。
今回は、ユーザー登録をする際に

  • userテーブルのカラムの追加
  • userテーブルに対して一対一のテーブルへの保存
  • userテーブルに対して1対多のテーブルへの保存

のやり方を紹介します。

環境

  • Rails 5.2.2.1
  • Ruby 2.5.1
  • devise 4.7.1

ユーザー登録できるようにする

  • テストアプリの作成
$ rails _5.2.2.1_ new devise_test -d mysql
  • gemの追加
Gemfile
gem 'pry-rails'
gem 'compass-rails', '3.1.0'
gem 'sprockets', '3.7.2'

gem 'haml-rails'
gem 'devise'
  • Gemfileの中身を変更
Gemfile
gem 'mysql2', '0.5.2'
gem 'sass-rails', '5.0.7'
  • gemの反映
$ bundle install
  • データベース作る
$ rake db:create
  • deviseのインストール
$ rails g devise:install
  • erbファイルをhamlに変換
$ rails haml:erb2haml
  • userモデルの作成
$ rails g devise user
  • userコントローラの作成
$ rails g controller users
  • userコントローラを編集
app/controllers/user_controller.rb
class UsersController < ApplicationController
  def index
  end
end
  • ビューファイルの作成

app/views/users/にindex.html.hamlを作成

app/views/users/index.html.haml
- if user_signed_in?
  %p
    ログインしています
  =link_to "ログアウト", destroy_user_session_path, method: :delete
- else
  %p
    ログインしていません
    %li
      =link_to "新規会員登録", new_user_registration_path, method: :get
    %li
      =link_to "ログイン", new_user_session_path, method: :get
  • ルーティングの設定
config/route.rb
Rails.application.routes.draw do
  devise_for :users
  root 'users#index'
end
  • マイグレーションの実行
$ rake db:migrate

基本的なユーザー登録はここまで。
これでdeviseデフォルトのemail、パスワード保存ができるようになりました。

deviseのデフォルト以外のカラムを追加する

例)登録時、userテーブルにnicknameを追加する

  • nicknameを追加するマイグレーションファイルを生成
$ rails g migration AddNicknameToUsers
  • マイグレーションファイルを編集
db/migrate/2019*********_add_nickname_to_users.rb
class AddNicknameToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :nickname, :string
  end
end
  • マイグレーションの実施
$ rake db:migrate
  • application controllerを編集
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
  end
end
  • deviseのビューを編集可能にする
$ rails g devise:views
  • deviceのビューをhamlに変換する
$ rails haml:erb2haml
  • 新規登録画面にnicknameの入力欄を追加する

ついでにform_forをRails5.1以降推奨のform_withに変える

app/views/devise/registrations/new.html.haml
%h2 Sign up
= form_with model: @user, url: user_registration_path, id: 'new_user', class: 'new_user',local: true do |f|
  = render "devise/shared/error_messages", resource: resource
  .field
    = f.label :nickname
    %br/
    = f.text_field :nickname, autofocus: true, autocomplete: "nickname"
  .field
    = f.label :email
    %br/
    = f.email_field :email, autofocus: true, autocomplete: "email"
  .field
    = f.label :password
    - if @minimum_password_length
      %em
        (#{@minimum_password_length} characters minimum)
    %br/
    = f.password_field :password, autocomplete: "new-password"
  .field
    = f.label :password_confirmation
    %br/
    = f.password_field :password_confirmation, autocomplete: "new-password"
  .actions
    = f.submit "Sign up"
= render "devise/shared/links"

これで完了。
deviseのデフォルトのカラムに加え、nicknameも一緒に保存できるようになりました。

1:1のテーブルの情報を一緒に保存する

例)userテーブルは、1対1のprofileテーブルを持つものとする。
profileテーブルには以下のカラムがあるとする。

  • 住んでいる国(country)
  • ユーザーid(user_id)

ユーザー登録時に、住んでいる国も同時に登録する。

  • profileモデルの生成
$ rails g model Profiles
  • マイグレーションファイルの編集
db/migrate/2019**********_create_profiles.rb
class CreateProfiles < ActiveRecord::Migration[5.2]
  def change
    create_table :profiles do |t|
      t.string :country
      t.references :user, null: false, foreign_key: true
      t.timestamps
    end
  end
end
  • マイグレーションの実行
$rake  db:migrate
  • userモデルを編集
app/models/user.rb
class User < ApplicationRecord
  has_one :profile, dependent: :destroy
  accepts_nested_attributes_for :profile

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end
  • profileモデルを編集
app/models/profile.rb
class Profile < ApplicationRecord
  belongs_to :user
end
  • application controllerを編集
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, profile_attributes: [:country]])
  end
end
  • deviceのコントローラーを表示
$ rails g devise:controllers users
  • registration_controllerを編集

必要なところのコメントアウトを外し、createアクション内に追記
一番下に、privateとprofile_paramsを追加

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]
  before_action :configure_account_update_params, only: [:update]

  # GET /resource/sign_up
  def new
    super
  end

  # POST /resource
  def create
    super
    @user = User.new(profile_params)
    @user.build_profile
    @user.save
  end

  # GET /resource/edit
  def edit
    super
  end

  # PUT /resource
  def update
    super
  end

  # DELETE /resource
  def destroy
    super
  end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  def cancel
    super
  end

  # protected

  # 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

  # If you have extra params to permit, append them to the sanitizer.
  def configure_account_update_params
    devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  end

  # The path used after sign up.
  def after_sign_up_path_for(resource)
    super(resource)
  end

  # The path used after sign up for inactive accounts.
  def after_inactive_sign_up_path_for(resource)
    super(resource)
  end

  private

  def profile_params
    params.permit(:sign_up, keys: [:nickname, profile_attributes: [:country]])
  end
end
  • ビューにcountryの入力フォームを作る

fields_forを使う

app/views/devise/registrations/new.html.haml
%h2 Sign up
= form_with model: @user, url: user_registration_path, id: 'new_user', class: 'new_user',local: true do |f|
  = render "devise/shared/error_messages", resource: resource
  .field
    = f.label :nickname
    %br/
    = f.text_field :nickname, autofocus: true, autocomplete: "nickname"
  .field
    = f.label :email
    %br/
    = f.email_field :email, autofocus: true, autocomplete: "email"
  .field
    = f.label :password
    - if @minimum_password_length
      %em
        (#{@minimum_password_length} characters minimum)
    %br/
    = f.password_field :password, autocomplete: "new-password"
  .field
    = f.label :password_confirmation
    %br/
    = f.password_field :password_confirmation, autocomplete: "new-password"
  = f.fields_for :profile, Profile.new do |i|
    .field
      = i.label :country
      %br/
      = i.text_field :country
  .actions
    = f.submit "Sign up"
= render "devise/shared/links"

これで1対1のテーブル情報であるcountryを保存できる。

1:多のテーブルの情報を一緒に保存する

例)userテーブルは、1対多のcreditcardテーブルを持つものとする。
creditcardテーブルには以下のカラムがあるとする。

  • カードナンバー(credit_number)
  • ユーザーid(user_id)

ユーザー登録時に、クレジットカードも同時に一枚分登録する。
基本的には1対1とほぼ変わらない。

  • クレジットカードモデルを作る
$ rails g model Creditcards
  • マイグレーションファイルを編集
db/migrate/2019**********_create_creditcards.rb
class CreateCreditcards < ActiveRecord::Migration[5.2]
  def change
    create_table :creditcards do |t|
      t.string :credit_number, null: false
      t.references :user, null: false, foreign_key: true
      t.timestamps
    end
  end
end
  • マイグレーションの実行
$ rake db:migrate
  • userモデルを編集
app/models/user.rb
class User < ApplicationRecord
  has_one :profile, dependent: :destroy
  accepts_nested_attributes_for :profile

  has_many :creditcards, dependent: :destroy
  accepts_nested_attributes_for :creditcards

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

end
  • Creditcardモデルを編集
app/models/creditcard.rb
class Creditcard < ApplicationRecord
  belongs_to :user
end
  • applicatuib_controllerを編集
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, profile_attributes: [:country], creditcards_attributes: [:credit_number]])
  end
end
  • registration_controllerを編集
app/controllers/users/registrations_controller.rb
createアクションとprofile_paramsに、追加の記述を行う

 def create
    super
    @user = User.new(profile_params)
    @user.build_profile
    @user.creditcards.build
    @user.save
  end

def profile_params
    params.permit(:sign_up, keys: [:nickname, profile_attributes: [:country], creditcards_attributes: [:credit_number]])
end
  • ビューにcountryの入力フォームを作る
app/views/devise/registrations/new.html.haml
%h2 Sign up
= form_with model: @user, url: user_registration_path, id: 'new_user', class: 'new_user',local: true do |f|
  = render "devise/shared/error_messages", resource: resource
  .field
    = f.label :nickname
    %br/
    = f.text_field :nickname, autofocus: true, autocomplete: "nickname"
  .field
    = f.label :email
    %br/
    = f.email_field :email, autofocus: true, autocomplete: "email"
  .field
    = f.label :password
    - if @minimum_password_length
      %em
        (#{@minimum_password_length} characters minimum)
    %br/
    = f.password_field :password, autocomplete: "new-password"
  .field
    = f.label :password_confirmation
    %br/
    = f.password_field :password_confirmation, autocomplete: "new-password"
  = f.fields_for :profile, Profile.new do |i|
    .field
      = i.label :country
      %br/
      = i.text_field :country
  = f.fields_for :creditcards, Creditcard.new do |j|
    .field
      = j.label :credit_number
      %br/
      = j.text_field :credit_number
  .actions
    = f.submit "Sign up"
= render "devise/shared/links"

以上で1対多のテーブル情報を保存できるようになります。

fields_for周りでエラーが出ている人へ

以下のようなエラーが出ている場合の対処

LoadError in Devise::RegistrationsController#new
Unable to autoload constant モデル名?, expected ファイルのディレクトリ to define it

もしくは

NameError in Devise::Registrations#new
howing ファイルのディレクトリ where line #2 raised:
uninitialized constant ActionView::CompiledTemplates::モデル名?

理由は単純で、両方ともfield_forの中に入れているモデル名が間違っています。
私が実際に遭遇した時は、アンダーバー「_」が入っているパターンでした。
具体的には、

  • テーブル名は「delivary_address」
  • モデル名は「DeliveryAdderss」

したがって、field_forの記述の仕方は

f.fields_for :delivery_address, DeliveryAddress.new do |i|

となります。
モデル名が分からない!という方は、Railsの構造が下記のようになっているので参照してください。

app/models/テーブル名
class モデル名 < ApplicationRecord
  処理色々
end

参考

https://qiita.com/params_bird/items/96922cae0c7a82ff157c
https://qiita.com/hiroweb/items/74867433ab5091713521#_reference-c32bb385189fe7807ff2
https://qiita.com/tmzkysk/items/6349d8d860c5982771ff

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
31