Ruby on Railsでコミュニティ機能を実装しよう：中間テーブルの作成とdeviseを使ったログイン機能の追加

# 目的

タイトルが不自然に凝ってますが、chatGPTと話してるついでに考えてもらいました。
今回はコミュニティ機能を実装します。
ユーザーが複数のコミュニティに参加できるようにモデルを作成したいと思います。
自分の知っている範囲で考えても答えが出てこなかったので、chatGPTに相談したら中間テーブルなるものを知りました。よく分かっていないのでその理解も含め、頑張ります！


# 今回やること

今回の目標は以下の2つ。
* ユーザーが複数のコミュニティに参加できるようにモデルを作成
* ログイン機能を実装


# 環境
* windows 10
* ruby 3.0.4
* rails 6
* Bootstrap 5.0.2


# いざ出陣

## deviseの導入
まず最終的にやりたいこととしては、以下の要件を満たすモデルを作ることです。

* ユーザーは、複数のコミュニティに参加することができる。
* コミュニティには、複数のユーザーが参加することができる。

これらの関係を表すために、中間テーブルを作成する必要があると言われたので、最終的なモデルは3つです。

* Userモデル
* Communityモデル
* Membershipモデル

できることからやろう。
まずは、ログイン機能のを実装するためのgemとして `device` を導入します。

```sh:gemfile
# 追加
gem 'devise'
```

```sh:cmd
bundle install
rails generate devise:install   # 初期設定
```

`rails generate devise:install` を実行すると、 `config/initializers/devise.rb` と `config/locales/devise.en.yml` が作成され、対応すべきこと？が4つ出てきました。

1. `default_url_options` の設定
1. `root_url` の設定
1. flashメッセージの設定
1. `Devise` のビューをカスタマイズする

1は無視。
2はすでにやっている。
3と4はやります。

### 3. flashメッセージの設定
app > views > layouts > application.html.erbに以下のコードを追加して、flashメッセージを出せるようにします。chatGPTにapplication.html.erbのどこに追加すれば聞いたら、警告とは違うコードを追加してましたが、こっちのがかっこ良いしできそうなのでこれでいきます。
```scss:app\views\layouts\application.html.erb
  <body>
    <%= yield %>
    <% flash.each do |name, msg| %>
      <%= content_tag :div, msg, class: "alert #{name}" %>
    <% end %>
  </body>
```

設定ができたかを確認するために、app > controllers > users_controller.rbでflashメッセージを書いてみます。
```ruby:app\controllers\users_controller.rb
class UsersController < ApplicationController
  def index
    flash[:notice] = "テストメッセージ"
  end
end
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693325/1a5dd2ed-a990-43ab-8377-940a70ade0e4.png)
できてますね。神。


### 4. `Devise` のビューをカスタマイズする
コマンドプロンプトで以下を実行します。
```sh:cmd
rails g devise:views
```
これで、Deviseによって自動生成されたビューをカスタマイズするためのテンプレートファイルが作成されました。後で確認します。


## Userモデルの作成
ログイン機能のあるUserモデルを作成するために以下を実行します。
```sh:cmd
rails generate controller users
rails generate devise User
rails db:migrate
```
すると、config > routes.rbに、以下のように追加されています。
これによってユーザー認証に関わるルーティングが作成されます。
```ruby:config\routes.rb
Rails.application.routes.draw do
  devise_for :users    # これが追加される
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  resources :users
  root   'users#index'
end
```
このまま動作確認をすると、 `http://localhost:3000/users/sign_in` で画面は表示されますが、ログインしていなくても `http://localhost:3000/users` が開けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693325/b9e60904-8d54-8b1d-076f-0e73eaed4f20.png)
ログインしていなければ、 `http://localhost:3000/users/sign_in` に飛ぶようにしたいので、app > controllers > users_controller.rbに以下を追加します。
```ruby:app\controllers\users_controller.rb
class UsersController < ApplicationController
  before_action :authenticate_user!    # これを追加
  
  def index
    flash[:notice] = "テストメッセージ"
  end
end
```
画面がかわいくないのは、さっき用意したテンプレートファイルを編集して後でカスタマイズします。

### Userテーブルにカラムを追加
migrationの使い方に苦しんだ...
とりあえず分かったことを箇条書きにする。

* migrationファイルを `rails db:migrate` したり `rails db:rollback` したりしてモデルを変更する。
* カラムを追加するときのmigrationファイルは、 `rails g migration AddDetailsToUsers` で作成する。


具体的には、以下のように作業しました。

migrationファイルの作成
```sh:cmd
rails g migration AddDetailsToUsers
```

カラムの追加
```ruby:db\migrate\yyyymmddhh24miss_add_details_to_users.rb
class AddDetailsToUsers < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :username, :string, null: false
    add_column :users, :gender, :string
    add_column :users, :origin_prefecture, :string, null: false
    add_column :users, :residence_prefecture, :string, null: false
    add_column :users, :residence_city, :string, null: false
  end
end
```

Viewから飛んできた情報を受け取れるようにする。
```ruby:app\controllers\application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username, :gender, :origin_prefecture, :residence_prefecture, :residence_city])
  end
end
```

あとはhtmlの編集で、設定したカラムを画面から登録できるようにします！
以下のコードを、追加したカラム分追記します。
```html:app\views\devise\registrations\new.html.erb
<div class="field">
  <%= f.label :username %><br />
  <%= f.text_field :username, autofocus: true, autocomplete: "username" %>
</div>
```
出来た。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693325/f27a088a-2845-0da9-5870-20b6fe976247.png)

## Communityモデルの作成
以下を実行。
```sh:cmd
rails g model Community eventname:string
```
eventnameのnullを却下してmigrate
```ruby:cmd
# 変更前
t.string :eventname

# 変更後
t.string :eventname, null: false
```
```sh:cmd
rails db:migrate
```
他にもカラム必要だけど、いったん後で。

## Membershipモデルの作成
ユーザーとコミュニティの多対多の関係を表す中間テーブルを作成するために、Membershipモデルを作成します。
```sh:cmd
rails g model Membership user:references community:references
```

UserモデルとCommunityモデルに、アソシエーションを定義します。
アソシエーションとは、Railsにおいてモデル間の関係を定義する仕組みのことです。
```ruby:app\models\user.rb
class User < ApplicationRecord
  has_many :memberships    # これを追加
  has_many :users, through: :memberships    # これを追加
  
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end
```
```ruby:app\models\community.rb
class Community < ApplicationRecord
  has_many :memberships    # これを追加
  has_many :users, through: :memberships    # これを追加
end
```
そしてマイグレーション。
```sh:cmd
rails db:migrate
```
アソシエーションのメゾットには、以下のようなものがあります。
まぁ、読めはしますよね。書けはしないけど。
| メソッド名 | 説明 |
| --- | --- |
| belongs_to | 他のモデルに属することを示す。 |
| has_one | 他のモデルと1対1の関係を示す。 |
| has_many | 他のモデルと1対多の関係を示す。 |
| has_many through | 間に中間モデルを挟んで他のモデルと多対多の関係を示す。 |


# 余談
右上の検索ボタンのホバーしたときの文字色、ヘッダーの色と合わせよ。