はじめに
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の追加
gem 'pry-rails'
gem 'compass-rails', '3.1.0'
gem 'sprockets', '3.7.2'
gem 'haml-rails'
gem 'devise'
- 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コントローラを編集
class UsersController < ApplicationController
def index
end
end
- ビューファイルの作成
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
- ルーティングの設定
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
- マイグレーションファイルを編集
class AddNicknameToUsers < ActiveRecord::Migration[5.2]
def change
add_column :users, :nickname, :string
end
end
- マイグレーションの実施
$ rake db:migrate
- application controllerを編集
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に変える
%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
- マイグレーションファイルの編集
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モデルを編集
class User < ApplicationRecord
has_one :profile, dependent: :destroy
accepts_nested_attributes_for :profile
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
end
- profileモデルを編集
class Profile < ApplicationRecord
belongs_to :user
end
- application controllerを編集
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を追加
# 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を使う
%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
- マイグレーションファイルを編集
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モデルを編集
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モデルを編集
class Creditcard < ApplicationRecord
belongs_to :user
end
- applicatuib_controllerを編集
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を編集
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の入力フォームを作る
%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の構造が下記のようになっているので参照してください。
class モデル名 < ApplicationRecord
処理色々
end
参考
https://qiita.com/params_bird/items/96922cae0c7a82ff157c
https://qiita.com/hiroweb/items/74867433ab5091713521#_reference-c32bb385189fe7807ff2
https://qiita.com/tmzkysk/items/6349d8d860c5982771ff