LoginSignup
2
3

More than 5 years have passed since last update.

Rails5とDeviseで複数メールアドレスを持つユーザー認証機能を実装した話

Last updated at Posted at 2016-10-20

複数のメールアドレスを持つユーザーモデルをDeviseでさくさくっと作ろうと思ってたら思ったより手間取ったのでメモ

DeviseのインストールとUserモデルの作成

コマンド
$ rails new demo --skip-bundle
$ cd demo
$ echo "gem 'devise'" >> Gemfile
$ rails g devise:install
config/environments/development.rb
Rails.application.configure do

  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
  #↓追加

  config.action_mailer.default_url_options = { host: 'localhost:3000' }
end

Userモデルの作成

コマンド
$ rails g devise User

Emailモデルの作成

コマンド
$ rails g model Email user:belong_to address:strings

Usersテーブルの編集

db/migrate/20xxxxxxxxxxxx_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      #↓コメントアウトor削除
      #t.string :email,              null: false, default: ""

      #↓追加
      t.belongs_to :main_email, null: true
      #↑ここでハマった、emailカラムはDeviseがメソッドオーバーライドしてるらしく
      #'t.belong_to :email' としてしまうと保存ができない
      #後、'null: false'にしてしまうとデッドロックが発生し保存できない
      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
    end
    #↓コメントアウトor削除
    #add_index :users, :email,                unique: true
  end
end

Emailsテーブルの編集

db/migrate/20xxxxxxxxxxxx_create_emails.rb
class CreateEmails < ActiveRecord::Migration[5.0]
  def change
    create_table :emails do |t|
      #↓行末に', null: false'を追加
      t.belongs_to :user, null: false
      t.string :address

      t.timestamps
    end
    #↓追加
    add_index :emails, :address, unique: true
  end
end

Userモデルの編集

app/model/user.rb
class User < ApplicationRecord
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
  #↓以下追加

  belongs_to :main_email, class_name: Email, optional: true
  accepts_nested_attributes_for :main_email

  has_many :emails, dependent: :destroy, index_errors: true
  accepts_nested_attributes_for :emails

  attr_accessor :email

  after_save :set_main_email
  after_save :add_main_email_to_emails

  #↓deviseにemailのチェックをさせないようにする
  def email_required?
    false
  end

  def email_changed?
    false
  end


  protected
  #↓deviseのユーザー取得メソッドをオーバーライド
  def self.find_first_by_auth_conditions(warden_conditions)
    conditions = warden_conditions.dup
    if email = conditions.delete(:email)
      #↓emailをemailsに持つユーザーを返す
      where(conditions).joins(:emails).find_by('emails.address': email)
      #↓main_emailでしかログインさせたくない場合はこれ
      #where(conditions).joins(:main_email).find_by('main_email.address': email)
    else
      where(conditions).first
    end
  end

  #↓main_emailが設定されていない時にemailsの最初の値を設定する
  def set_main_email
    return false if self.emails.empty?
    if self.main_email.nil?
      self.main_email = self.emails.first
      self.save
    end
  end

  #↓main_emailが変更された時にemailsに追加
  #今回の実装では必要ないが一応
  def add_main_email_to_emails
    return false if self.main_email.nil?
    if self.main_email.changed? && !self.emails.include?(self.main_email)
      self.emails << self.main_email
      self.save
    end
  end
end

Emailモデルの編集

app/model/email.rb
class Email < ApplicationRecord
  has_one :user

  belongs_to :user, optional: true
  #↑Rails5からbelong_toカラムは必須になったが保存してからでないと
  #IDが定まらず保存できないので'optional: true'を指定
end

ApplicationControllerの編集

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  #↓以下追加
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected
  def configure_permitted_parameters
    #Devise4での書き方 古いバージョンでは
    #'devise_parameter_sanitizer.for'を使う
    devise_parameter_sanitizer.permit(:sign_up) do |u|
      u.permit([emails_attributes: [:address]], :password, :password_confirmation, :remember_me)
    end
    devise_parameter_sanitizer.permit(:sign_in) do
      |u| u.permit([emails_attributes: [:address]], :password, :remember_me)
    end
    devise_parameter_sanitizer.permit(:account_update) do
      |u| u.permit([emails_attributes: [:address]], :password, :password_confirmation, :current_password)
    end
  end
end

Viewsの編集

コマンド
$ rails g devise:views user
config/initializers/devise.rb
  #↓223行目付近 コメントアウトのまま放置or削除
  # config.scoped_views = false
  config.scoped_views = true
  #↑追加
app/views/users/registrations/*.html.erb
<h2>Sign up</h2>

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

  #↓追加
  <% @user.emails.new if @user.emails.empty? %>
  <%= f.fields_for :emails, @user.emails || @user.emails.new do |e| %>
  <div class="field">
    <%= e.label :email %><br />
    <%= e.email_field :address %>
  </div>
  <% end %>

  #↓削除
  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true %>
  </div>


  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#

これでおしまい。

2
3
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
2
3