大ちゃんの駆け出し技術ブログ

RUNTEQ受講生のかわいいといわれるアウトプットブログ

【備忘録】deviseの通常ログインは先に実装しましょう

はじめに

こんにちは!大ちゃんの駆け出し技術ブログです。

自分のPFがslackログインのみでしたが、それに通常ログイン機能を加えました。deviseを使用した認証の実装では本来通常ログイン ⇒ 外部認証の順番で実装を行うかと思います。しかし、自分は既にslack認証を実装していたので通常ログインとの互換性に差異があり、少しつまづいてしまったのでこの記事で記録しておきます。

導入前のUserモデルの影響

導入前のUserモデルのバリデーションの設定です。

validates :name,                      presence: true
validates :email,                     presence: true, uniqueness: { case_sensitive: true }
validates :provider,                  presence: true
validates :uid,                       presence: true, uniqueness: { case_sensitive: true }
validates :encrypted_password,        presence: true

belongs_to :team

slack認証を実装していたので、providerとuidがslack認証時に追加されます。そのため、providerとuidは共に必須カラムとしていました。

def self.from_omniauth(auth, user_info)
  user = find_or_initialize_by(provider: auth.provider, uid: auth.uid)
  user.password = Devise.friendly_token[0, 20] # ランダムなパスワードを作成
  user.name = user_info.dig('user', 'name')
  user.email = user_info.dig('user', 'email')
  user.image = user_info.dig('user', 'image_192')
  user.check_team_existence(user_info.dig('team'))
  user.save!
  user
end

また、belongs_to :teamとあるようにslackのワークスペースにユーザーが所属する設定をしています。

後々この3つのprovider, uid, team_id(team)が既に存在していたために通常ログインフローで引っかかりました。

いざ実装

deviseの記事はググればいくらでも出てくるので基本的deviseの導入は割愛します。

ユーザー登録画面は以下のように実装しました。deviseで生成されるerbファイルをslimに変換しています。

h2
  | Sign up
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
  = render "users/shared/error_messages", resource: resource
  .field
    = f.label :name
    br
    = f.text_field :name, autofocus: true, autocomplete: "name"
  .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"
  .field
    = f.label :image
    br
    = f.file_field :image, autofocus: true, autocomplete: "image"
  .actions
    = f.submit "Sign up"
= render "users/shared/links"

さてさて、通常通りにこれを登録しようとしてもエラーになります。表示されるエラーメッセージは元Userモデルのバリデーション が影響したものです。

  • チームを入力してください
  • uidを入力してください
  • providerを入力してください

providerとuidについてはnull: false, presence: trueを取る他に方法がないかなと思いました。これらはslackログインの時に取得できますので、通常ログイン時との互換性を合わせるなら必須カラムを解除するしかないなと。

validates :name,                      presence: true
validates :email,                     presence: true, uniqueness: { case_sensitive: true }                   
validates :encrypted_password,        presence: true

belongs_to :team

問題はteamの方です。belongs_to :teamコメントアウトで消すわけにはいきませんし、、、

問題はuserに属するteamがuser登録時に存在しないことです。なのでゴリ押しで先にデフォルトのチームデータを入れ、userのデフォルトのteam_idをマイグレーションファイルに記述しました。(絶対ベストプラクティスじゃない、、、)

t.references :team, foreign_key: true, :default => "1"

結論

今回のようにゴリ押しする他実装方法がないのはdeviseのログインの実装を外部認証⇒通常ログインの順番で実装したためです。通常ログイン⇒外部認証で実装を行った方が、後々の互換性を合わせる際に楽になるかと思います。結論、普通に通常ログインから実装しようという話でした。