【豆知識】uidとuser_idは別物だった

# はじめに
アプリ制作課題の中で外部認証機能に関するコードを入力しているとき「これって同じ意味なのでは？いったい何が違うのか？」疑問に思った事があったのでアウトプットもかねて記事にまとめてみようと思います。

## 環境
- Windows, WSL
- Docker
- Ruby 3.2.3
- Rails 7.1.3



## **`uid` と `user_id` の違い**

- **`uid`**:
    - `uid` は「ユーザーID」と見なされがちですが、これは**外部認証プロバイダー（例えばGoogleやFacebook）が提供する一意の識別子**です。各プロバイダーが独自にユーザーを識別するためのIDであり、`user_id` とは別物です!!
- **`user_id`**:
    - `user_id` はアプリケーション内の `users` テーブルで管理される**ユーザーの一意の識別子**で、アプリケーション全体でそのユーザーを識別するために使用されます

<br>

## `uid` と `user_id` を使用したコード例3つ
### 1. **ユーザーが認証済みかどうかを確認するコード**

```ruby
# user_id が 1 のユーザーを見つける
user = User.find(1)

# 認証テーブルから、このユーザーの Google の uid を取得
authentication = Authentication.find_by(user_id: user.id, provider: "google")

# その uid が存在するかどうかで、Google 認証済みか確認
if authentication
  puts "ユーザーは Google で認証済みです。"
else
  puts "ユーザーは Google で認証されていません。"
end

```

### **解説**:

- `user_id` はアプリ内でのユーザー識別子。
- `uid` は Google など外部サービスでのユーザー識別子です。このコードは特定のユーザーが Google 認証をしているかどうかを確認します。

### 1の該当ディレクトリとファイルの例
- ユーザーが特定の外部サービスで認証済みかどうかを確認する処理ようなロジックは、コントローラーのアクション内やサービスオブジェクトで使用されることが多いです。
- **コントローラー内**: `app/controllers/sessions_controller.rb`
- **サービスオブジェクト内**: `app/services/authentication_service.rb`

```ruby
# app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def check_authentication
    user = User.find(params[:id])
    authentication = Authentication.find_by(user_id: user.id, provider: "google")

    if authentication
      puts "ユーザーは Google で認証済みです。"
    else
      puts "ユーザーは Google で認証されていません。"
    end
  end
end
```

<br>

### 2. **外部サービスからのログイン処理**

```ruby
# Google から返された uid と provider を受け取る
received_uid = "1234567890"
provider = "google"

# 認証テーブルから該当するユーザーを探す
authentication = Authentication.find_by(uid: received_uid, provider: provider)

if authentication
  user = User.find(authentication.user_id)
  puts "ログイン成功: ユーザー #{user.id} が見つかりました。"
else
  puts "ログイン失敗: ユーザーが見つかりません。"
end

```

### **解説**:

- このコードは、外部サービスから渡された `uid` と `provider` を使って、該当するユーザーをアプリ内で探し、ログイン処理を行います。
- `authentication.user_id` を使って、関連するユーザーを見つけます。

### 2の該当ディレクトリとファイルの例
- 外部サービス（例えばGoogle）からのログイン処理で使用されるこのロジックは、通常、コントローラーのログインアクションや、OAuthのコールバックアクションに記載します。
- **コントローラー内**: `app/controllers/sessions_controller.rb`

```ruby
# app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def create_from_omniauth
    received_uid = params[:uid]
    provider = params[:provider]

    authentication = Authentication.find_by(uid: received_uid, provider: provider)

    if authentication
      user = User.find(authentication.user_id)
      puts "ログイン成功: ユーザー #{user.id} が見つかりました。"
    else
      puts "ログイン失敗: ユーザーが見つかりません。"
    end
  end
end
```

<br>

### 3. **新しい認証情報の保存**

```ruby
# 既存のユーザーに Google 認証を追加
user = User.find(1)

# 新しい認証情報を作成し保存
authentication = Authentication.create(
  user_id: user.id,
  provider: "google",
  uid: "1234567890"
)

if authentication.persisted?
  puts "認証情報が保存されました。"
else
  puts "認証情報の保存に失敗しました。"
end

```

### **解説**:

- このコードは、既存のユーザーに新しい外部サービス（Google）の認証情報を追加する例です。
- `user_id` と `uid` を関連付けて、新しい認証情報を `authentications` テーブルに保存します。

### 3の該当ディレクトリとファイルの例
- このコードは、新しい認証情報をユーザーに関連付けて保存する処理です。通常は、ユーザーが新しい外部サービスでログインした際に、その情報をデータベースに保存するために使用されます。
- **コントローラー内**: `app/controllers/authentications_controller.rb`
- **サービスオブジェクト内**: `app/services/authentication_service.rb`

```ruby
rubyコードをコピーする
# app/controllers/authentications_controller.rb

class AuthenticationsController < ApplicationController
  def create
    user = User.find(params[:user_id])

    authentication = Authentication.create(
      user_id: user.id,
      provider: params[:provider],
      uid: params[:uid]
    )

    if authentication.persisted?
      puts "認証情報が保存されました。"
    else
      puts "認証情報の保存に失敗しました。"
    end
  end
end
```

<br>

## ついでにマイグレーションファイルのコードだともっとわかりやすい
```ruby
class SorceryExternal < ActiveRecord::Migration[7.1]
  def change
    create_table :authentications do |t|
      t.integer :user_id, null: false
      t.string :provider, :uid, null: false

      t.timestamps null: false
    end

    add_index :authentications, [:provider, :uid]
  end
end
```
1:`class SorceryExternal < ActiveRecord::Migration[7.1]`
この行は、新しいマイグレーションを作成していることを示します。SorceryExternal はマイグレーションの名前で、ActiveRecord::Migration[7.1] はこのマイグレーションが Rails 7.1 に対応していることを意味します。

2:`def change`
このメソッド内で、データベースに対する変更を記述します。change メソッドは、マイグレーションを実行するときと元に戻すときの両方を自動的に処理してくれます。

3:`create_table :authentications do |t|`
authentications という新しいテーブルを作成します。do |t| は、このテーブルのカラム（列）を定義するために使用されます。

4:`t.integer :user_id, null: false`
user_id という名前のカラムを整数型で作成します。null: false は、このカラムが NULL 値を持つことが許されない、つまり必須のカラムであることを意味します。このカラムは、User モデルと関連付けるための外部キー（外部テーブルのキー）です。

5:`t.string :provider, :uid, null: false`
provider と uid という2つのカラムを文字列型で作成します。null: false がついているため、これらのカラムも必須です。
provider は認証プロバイダの名前（例: "google", "facebook"）を保存するために使用されます。
uid は、そのプロバイダ内でのユーザーIDを保存します。これにより、同じユーザーが複数のプロバイダで認証を持つことが可能になります。

6:`t.timestamps null: false`
created_at と updated_at という2つのタイムスタンプカラムが自動的に追加されます。これらは、レコードが作成された日時と更新された日時を記録します。null: false により、これらの値も必須になります。

7:`add_index :authentications, [:provider, :uid]`
provider と uid の組み合わせにインデックスを追加します。インデックスは、データベースの検索を高速化するために使用されます。特に、provider と uid の組み合わせが一意（ユニーク）であることを保証したい場合に有効です。

### 流れとしてはこんな感じ
1. ユーザーがGoogleでログインする:
2. authentications テーブルにそのユーザーの user_id、provider（"google"）、および uid を保存
2. 後のログイン時にユーザーを識別

## さいごに改めて

- **`user_id`**: アプリケーション内で一意にユーザーを識別するために使用します。
- **`uid`**: 外部サービスでのユーザー識別子で、`provider` と組み合わせてユーザーを識別します。
- **`SessionsController`**: 主にセッション管理や認証に関連するコードが含まれるコントローラーです。外部サービスからのログイン処理や、ユーザーが認証済みかどうかの確認などのロジックが記載されます。
- **`AuthenticationsController`**: 外部サービスの認証情報を管理するためのコントローラーです。認証情報の保存や更新を行います。
- **サービスオブジェクト**: より複雑なビジネスロジックを管理するために使用されます。特定の機能に関連するロジックをまとめて管理するのに役立ちます。

ユーザーと外部サービスの認証情報を関連付けるために `user_id` と `uid` をどのように使用するかの、少し複雑。にしてもこんな流れで外部認証機能を利用してログインできているんだ、と知ることは面白いと感じました。
今回の記事が何か参考になれば幸いです。