【Rails】Twitterクローンを作ってみた 〜GitHubログイン(OmniAuth)〜

## はじめに
[【Rails】Twitterクローンを作ってみた 〜画像アップロード(ActiveRecord)〜](https://qiita.com/bloom__fu/items/761d53d644b6a016ed0d)の続きです。こちらを前提に進めていきますので、まだご覧になっていない方はそちらからご覧ください！※こちらはdockerでの環境構築を省きます。
## サービス環境
- ruby 3.0.0
- Rails 6.0.4
- docker
- mysql 8.0.2
- Slim, SCSS

## OmniAuth
#### 条件
- Git Hubアカウントでログインできる
DeviseのモジュールOmniauthableを使用しGit Hubログインをできるようにしてください。
#### 実装流れ(簡略)
1. https://fuga-ch85.hatenablog.com/entry/2021/04/04/164302 
こちらを参考にGitHubのClient IDとClient secretsを取得してください。
2. Client IDとClient secretsを.envファイルに保存。.gitignoreに.envファイル追記。
(.env.exampleファイルを作り、どの環境変数を設定すれば良いかわかるようにすると親切)
3. Gemファイルに```gem 'omniauth-github'```
```gem 'omniauth-rails_csrf_protection'```を追記し、```bundle install```
4. マイグレーションファイル作成
5. ```db:migarate```→失敗→```db:reset```→```db:migrate```
6. config/initializers/devise.rbでomniauthの設定をする
7. ルーティング作成
8. models/user.rbでユーザー登録のメソッド作成
9. controllers/users/omniauth_callbacks_controller.rbでGitHubログインのメソッド作成
10. controllers/users/registrations_controller.rbでパスワードなしでのアカウント更新を可能にするメソッド作成
11. ルーティング作成
12. サインアップ画面(html, css)の修正
 

#### サインアップ画面
![スクリーンショット 2022-11-23 20.20.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1961802/695e2e84-542d-d2b3-b844-cb78b4069377.png)

#### GitHubサインイン後の画面
![スクリーンショット 2022-11-23 20.22.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1961802/50c1c059-d946-3cfa-3d69-d48150a08ac4.png)



## 解説
#### マイグレーションファイルの作成
```ruby
class AddOmniauthToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :uid, :string
    add_column :users, :provider, :string, null: false, default: ""
    
    add_index :users, [:uid, :provider], unique: true
  end
end
```
uidとproviderカラムを作成します。uid は oAuth を使用してログインしたユーザーの一意の ID で構成されます。uidに```null: false```を追記していない理由は通常ログインの際にuidが無くともログインできるようにするためです。エラー原因の１つになる、とても重要なものになります。そしてuserモデルにバリデーションを加えるのを忘れずにしましょう！
#### models/user.rb
```ruby
class User < ApplicationRecord
  ...
  validates :uid, uniqueness: { scope: :provider }, if: -> { uid.present? }
  
  # authの中身はGitHubから送られてくる大きなハッシュ。この中に名前やメアドなどが入っている。
  # providerカラムとuidカラムが送られてきたデータと一致するユーザーを探す。
  # もしユーザーが見つからない場合は新規作成する。
  def self.find_for_github_oauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create! do |user|
      # 名前を取得するときはこのように書く（今回はUserモデルにname属性がないのでエラーなる） 
      # user.name = auth.info.name
      user.email = auth.info.email
      # 任意の20文字の文字列を作成する
      user.password = Devise.friendly_token[0, 20]
    end
  end
  ...
end
```
ここで重要になるものがif条件です。uidが存在していないときにこのバリデーションが作用するとまたしても通常ログインの際エラーが出てログインできなくなります。私はここのif条件を忘れてしまっていたため、エラー地獄のひどい目に遭いました笑
さらにGitHubからもらったデータでユーザー登録するメソッドを作ります。しかしながらこの状態だとGitHubログインした際、プロフィール編集ができません。なぜならプロフィール編集に現在のパスワードが必要になっています。しかし現在のパスワードはGitHubのパスワードではなくこのメソッドで作ったランダムのパスワードです。つまりGitHubでログインしたユーザーはプロフィール編集できない仕様になっています。この問題を後ほどのcontrollers/users/registrations_controller.rbで解決していきます！
#### db:migrateでのエラー
userテーブルにデータが入っているとエラーが出ます。なぜこのようなことが起きたのかというと、先程のマイグレーションファイルでuidに対して```null: false```を追記してしまったままであったからです。これは現データのuser.uidに対してNOT NULL制約がかかってしまったためでした。その場では```rails db:reset```をして現データを削除してから```rails db:migrate```することにより解決できました。
#### ルーティング(routes.rb)
```ruby
Rails.application.routes.draw do
  
  devise_for :users, controllers: { 
    registrations: "users/registrations",
    omniauth_callbacks: "users/omniauth_callbacks"
  }
  ...
end
```
ハッシュロケットは可能な限り使わないようにしましょう！
#### controllers/users/omniauth_callbacks_controller.rb
```ruby
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  # You should configure your model like this:
  # devise :omniauthable, omniauth_providers: [:github]

  # You should also create an action method in this controller like this:
  def github
    # request.env["omniauth.auth"]にGitHubから送られてきたデータが入っている
    @user = User.find_for_github_oauth(request.env["omniauth.auth"])

    unless @user.persisted? # ログイン失敗
      session["devise.github_data"] = request.env["omniauth.auth"]
      return redirect_to new_user_registration_url
    end
    
    if @user.profile.blank? # プロフィール登録画面への誘導
      sign_in @user, event: :authentication
      return redirect_to new_profile_path
    end
    
    sign_in_and_redirect @user, event: :authentication #プロフィールを作成していれば、ログイン
    set_flash_message(:notice, :success, kind: "Github") if is_navigational_format?
  end

  # More info at:
  # https://github.com/heartcombo/devise#omniauth

  # GET|POST /resource/auth/github
  # def passthru
  #   super
  # end

  # GET|POST /users/auth/github/callback
  # def failure
  #   super
  # end

  # protected

  # The path used when OmniAuth fails
  # def after_omniauth_failure_path_for(scope)
  #   super(scope)
  # end
end
```
コメントアウトしてあるsuperに関してメソッドを追記しない場合はコメントアウトのままで大丈夫です。

#### controllers/users/registrations_controller.rb
```ruby
class Users::RegistrationsController < Devise::RegistrationsController
  ...
  protected
    def update_resource(resource, params)
      return super if params["password"]&.present?
      # 現在のパスワードなしでアカウントの更新をする
      resource.update_without_password(params.except("current_password"))
    end
end
```
上記コードによりランダムなパスワードが振り分けされても(GitHubのパスワードで無くとも)プロフィール詳細を編集できるようになりました！
#### .env.exampleファイル
```
GITHUB_ID = 
GITHUB_SECRET = 
```
最後に、どの環境変数を設定すれば良いかわかるようにしておくと親切です。※秘匿情報は書かない

## 実装を通して学んだこと
#### 1. uidとproviderカラム
omniauthを使ったログインではこの二つのカラムをセットにして考えるということを学びました。uid は oAuth を使用してログインしたユーザーの一意の ID で構成されることから、通常ログインでは絶対に絡めないようにすること。validationで必ずif条件によりuidが存在するかどうか判断してproviderカラムにユニーク制約をかけること(if条件をつけないと通常ログインでもユニーク制約がかかり、エラーが発生する)。
#### 2. omniauthでのパスワード問題
GitHubログインをした際は、その時点で適当なパスワードを振るように設定することと、パスワードが必要な操作をする際にパスワードなしで、操作できるようにコントローラの編集が必要なことを学びました。
#### 3. db:migarate失敗
```db:reset```からの```db:migrate```で解決。



## 感想
今回は非常に時間がかかり、苦労しました。まずはomniauth?oAuth?というとこから始まりました笑
GitHubログインができた！と喜んでいると、通常のログインが機能しなくなっていたり、てんてこまいになりましたが解説の通り、マイグレーションファイルやモデルのバリデーションをいじることによりなんとか解決できました。実際にこの章を実装してみるとよくわかりますがとても得られるものが多いなと感じました。特にマイグレーションファイルのNOT NULL制約であったり、モデルのバリデーション機能に関してはとても良い復習になりました。是非実装してみてください！
次回は**ツイートのいいね機能**についてアウトプットしていこうと思います！読んでいただきありがとうございました。
## 参考記事
- https://qiita.com/lamp7800/items/84ac4f9f7f5d4e171f1d
- https://fuga-ch85.hatenablog.com/entry/2021/04/10/075536
- https://fuga-ch85.hatenablog.com/entry/2021/04/04/164302