パーフェクトRuby on Rails 5章 メモ・雑感

# Rails標準の機能を活用して素早く機能実装する

## 雑感

この章はRailsの組み込みライブラリについての内容でした。初見の機能も多く、個人的にはなかなかヘビィな内容でしたが、非同期処理やメール送信機能など、かなり実務に近い内容になってきている印象です。以下、不具合の解消法なども交えた忘備録です。

## Active Jobによる非同期実行

### ジョブの例外処理

- Active Jobはジョブ実行時に発生した例外をキャッチして対応を変える仕組みが用意されている
- `retry_on`はジョブのリトライを実行。ただし、Sidekiqなどバックエンド側でリトライの仕組みを持っていることがあるので二重実行に注意が必要

```app/jobs/async_log_job.rb
class AsyncLogJob < ApplicationJob
  略
  # ジョブのリトライ(wait: 待ち時間, attempts: リトライ回数)
  retry_on StandardError, wait: 5.seconds, attempts: 3
  # 複数の例外を指定することも可能
  retry_on ZeroDivisionError, TypeError, wait: 10.seconds, attempts: 3
end
```

- `discard_on`はジョブの破棄を実行

```app/jobs/async_log_job.rb
class AsyncLogJob < ApplicationJob
略
  # ジョブの破棄
  discard_on StandardError
  # ブロックを使うことも可能
  # 引数にジョブオブジェクトと例外オブジェクトを受け取れる
  discard on StandardError do |job, error|
    SomeNotifier.push_error(error) # 通知を送る
  end
end
```

## 5-2 Active Storageによるファイルアップロード

### Active Strageのセットアップ方法

- Active StrageはRails組み込みのファイルアップロード機能
- 機能を使うためには以下のコマンドの実行が必要

```
$ bin/rails active_storage:install
```

- モデルの生成時にActive Storage用の属性を`attachment`オプションで用意する。これにより生成されたモデルファイルには属性が追加される。なお、この属性はテーブルのカラムとしては追加されない

```
rails generate model User name:string portrait:attachment
```

```app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
  has_one_attached :portrait # Active Strage用の属性
end
```

- デフォルトだとファイルの保存先はローカルのファイルシステムとなる。S3などのクラウドストレージを利用する際は以下のファイルを編集する

```config/storage.yml
test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

# S3の設定の雛形はデフォルトでコメントアウトされた状態で用意されている
# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
#   service: S3
#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
#   region: us-east-1
#   bucket: your_own_bucket
略

# ミラーリング用の設定
# 一度に複数のストレージにファイルアップロードしたい場合はこちらを利用する
# mirror:
#   service: Mirror
#   primary: local
#   mirrors: [ amazon, google, microsoft ]
```

```config/environments/development.rb
Rails.application.configure do
  # Store uploaded files on the local file system (see config/storage.yml for options).
  # デフォルトはローカルストレージを利用
  config.active_storage.service = :local

  # Active Storageのサービスを上記ファイルのS3に設定変更する場合
  config.active_storage.service = :amazon
end
```

### Active Strageの追加機能

#### サムネイル生成

- アップロードした画像を自動でリサイズし、サムネイルを作成して表示することができる。機能を利用するためには以下のコマンドを実行し、リンク表示箇所を修正する

```ruby:Gemfile
# 以下がコメントアウトされているので解除しておく
# Use Active Storage variant
gem 'image_processing', '~> 1.2'
```

```
$ bundle install
$ brew install imagemagick <-- macOSの場合
```

```erb:app/views/users/show.html.erb
<p>
  <strong>Portrait:</strong>
  <%# <%= link_to @user.portrait.filename, @user.portrait if @user.portrait.attached? %>
  <%= image_tag @user.portrait.variant(resize_to_limit: [100, 100]) %>
</p>
```

- 上記までは`ImageMagick`を利用しているが、画像形式が限定される代わりによりメモリ消費量が少なく実行速度が速い`libvips`というライブラリもサポートしている以下の設定で利用できる

```
$ brew install vips
```

```config/application.rb
module PortraitsUploader
  class Application < Rails::Application
    略
    # vipsを使うための設定
    config.active_storage.variant_processor = :vips
  end
end
```

#### ダイレクトアップロード

- ダイレクトアップロードにも対応しており、サーバーを経由しないことでアップロード時間の短縮やサーバーの負荷軽減につながる。以下の設定で利用可能

```app/javascript/packs/application.js
// Rails6.0以上はデフォルトで有効化
require("@rails/activestorage").start()
```

```erb:app/views/users/_form.html.erb
略
<div class="field">
  <%= form.label :portrait %>
  <%# <%=  form.file_field :portrait %>
  <%#　複数ファイルをアップロードしたい場合は multiple: true に設定 %>
  <%= form.file_field :portrait, direct_upload: true %>
</div>
略
```

### Active Strageの問題点

#### validationヘルパーの不足

例えば、画像のアップローダーに動画がアップされた場合などは、バリデーションエラーとしたいがそのためのヘルパーメソッドが存在しない。自分で実装するか`active_storage_validations`gemを利用する必要がある

#### cacheの不足

画像をアップロードした後にフォームの入力でバリデーションエラーとなった場合、画像をキャッシュできず、フォームの再入力とともに画像も再アップする必要がある

## ActionMailerによるメール送信

### Action Mailerのセットアップ

- Action MailerはRails組み込みのメール送信機能
- Action Mailerで使用するファイルは`mailer`コマンドで生成できる
  - 生成されるファイル
    - メイラークラスの定義: 
    `app/mailers/user_mailer.rb`
    - プレビューの作成: 
    `test/mailers/previews/user_mailer_preview.rb`
    - テスト: 
    `test/mailers/user_mailer_test.rb`  

```
$ bin/rails g mailer UserMailer
```

- 上記コマンドで生成した`app/mailers/user_mailer.rb`ファイルの内容を編集し対応するビューファイルを`app/views/user_mailer/`ディレクトリ配下に実装することでメール送信機能を作ることができる

```diff_ruby:app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
+ # ユーザー登録完了時のメール送信機能
+ def welcome
+   @name = params[:name]
+   # mailメソッドでHTML/textのテンプレートを探してメールを作成
+   mail(to: params[:to], subject: '登録完了') 
+ end
end
```

以下の2種類のフォーマットを作成し、受信側で選んで表示させることができる

```erb:app/views/user_mailer/welcome.html.erb
<p> <%= @name %> 様</p>
<p> ユーザー登録が完了しました。</p>
```

```erb:app/views/user_mailer/welcome.text.erb
<%= @name %> 様
ユーザー登録が完了しました。
```

- デフォルトの送信元メールアドレスはすべてのメイラークラスの継承元の`ApplicationMailer`で設定

```app/mailers/user_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'ここに任意の送信元アドレスを記述する'
  layout 'mailer'
end
```

- 作成したメイラーとビューの動作による文面作成はプレビュー機能で確認できる。`test/mailers/user_mailer_test.rb`を編集し、`localhost:3000/rails/mailers/user_mailer/welcome`へとアクセスすることで確認できる

```diff_ruby:test/mailers/previews/user_mailer_preview.rb
# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview
+  def welcome
+    UserMailer.with(to: "igarashi@example.com", name: "igaiga").welcome
+  end
end
```

- 実際にメールを送信する場合は以下のように`deliver_xxx`メソッドを利用する
  - `deliver_now`: 同期的に送信
  - `delilver_later(wait: x.minutes)`: x分後に送信(`wait until`にすると送信日時を指定)

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  def create
    @user = User.new(user_params)

    respond_to do |format|
      if @user.save
        # ユーザー登録完了時にメールを送信
        UserMailer.with(to: @user.email, name: @user.name).welcome.deliver_now
        format.html { redirect_to @user, notice: "User was successfully created." }
        format.json { render :show, status: :created, location: @user }
      else
略
end
```

- メール送信の動作を開発環境で確認するには以下の環境設定ファイルを編集する。以下の場合は、メール送信を含むアクションをトリガーすると`tmp/mail`配下にファイルが作成されてその末尾に内容が追記される

```config/environments/development.rb
Rails.application.configure do
  略
  config.action_mailer.delivery_method = :file # メールをファイルとして保存
  # ファイル保存先を指定
  # config.action_mailer.file_settings = { location: Rails.root.join('log/mail') } 
  # 指定しない場合は、tmp/mail 以下に保存される
end
```

- 実際にメールを送信するためにはSMTPサーバを用意する必要がある。本書では外部サーバとして[SendGrid](https://sendgrid.com)を利用している

:::note warn
本書の内容通りに進めていくとうまく設定ができなかったので、SendGridの認証設定を最新の推奨方式（APIキー認証）に変更して進めました。設定方法は以下です。

```
手順：
1. SendGridのダッシュボードにログイン
2. Settings → API Keys → Create API Key
3. API Keyを生成（Full Access または Restricted Access で Mail Send の権限を付与）
4. 生成されたAPIキーをコピーして、上記の【SendGridで生成したAPIキー】の部分に貼り付け
```

なお、認証情報の登録に関してもエディタを開くために追加のオプションが必要でした。以下はvscodeでエディタを開く方法です。編集してxで閉じると最下部のメッセージが出力され編集が完了します。

```
$ EDITOR="code --wait" bin/rails credentials:edit
略
File encrypted and saved.
```

認証情報の編集内容は以下です。

```
# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sendgrid:
  api_key: 【SendGridで生成したAPIキー】
```

:::

- SMTPへ接続するためには以下ファイルを編集する

```config/environments/development.rb
Rails.application.configure do
  略
  config.action_mailer.delivery_method = :smtp # SMTPサーバを利用
  config.action_mailer.smtp_settings = {
  user_name: 'apikey',
  password: Rails.application.credentials.dig(:sendgrid, :api_key),
  domain: '送信元に設定したアドレスの@以下の文字列',
  address: 'smtp.sendgrid.net',
  port: 587,
  authentication: :plain,
  enable_starttls_auto: true
}
end
```

- 単体テストは最初に生成した`test/mailers/`配下のファイルで行う

## Action Mailboxによるメール受信

### Action Mailboxのセットアップ

- Action Mailboxはメールを受信した時に処理を行う機能を提供する組み込みライブラリ。各メールサービス、サーバと協調して動作する。対応サービスは[Railsガイド](https://railsguides.jp/action_mailbox_basics.html)を参照
- 機能を使うためには以下のコマンドを使用。受信メールのメタ情報を保存するmigrationファイルも生成されるのでマイグレーションも同時に実行する

```
$ bin/rails action_mailbox:install
$ bin/rails db:migrate
```

- コマンド実行後、環境設定は以下のようにする

```config/environments/production.rb
Rails.application.configure do
  略
  # Sendgridを使う設定
  config.action_mailbox.ingress = :sendgrid
end
```

認証情報は以下のように追加する

```
$ EDITOR="code --wait" bin/rails credentials:edit
```

```
action_mailbox:
  ingress_password: メールサービスのパスワード
```

:::note info
本書の内容だとここからさらにSendGridの設定でDomain Authentication設定を行うように記述されていて、メール受信用のドメインが必要とされています。ですが、本書の内容を進める範囲に限ってはこの設定は行わなくても問題ありませんでした。私はこの辺りの設定で少し詰まってしまったので補足のために追記いたします。
:::

### Action Mailboxの実装

- 受信メールに対しての処理はActive Jobによる非同期処理キューに登録することで行われる。キューのジョブは以下の設定で各処理に振り分けられる

```app/mailboxes/application_mailbox.rb
class ApplicationMailbox < ActionMailbox::Base
  # 判断条件 => 振り分け先
  # 複数マッチする場合は最初にマッチした処理を実行
  routing (/[0-9]+-comment@/i) => :comments # CommentsMailboxクラスへ処理を振り分け
  # routing -> (inbound_email) { inbound_email.mail.to.size > 2 } => :multiple_recipients
  # routing CustomAddress.new => :custom
  routing :all => :backstop
end
```

- 振り分けられたメールを処理するMailboxクラスは以下のように実装する

```
$bin/rails g mailbox comments
```

```diff_ruby:app/mailboxes/comments_mailbox.rb
class CommentsMailbox < ApplicationMailbox
+  before_processing :validate_request
+
+  # メールの内容をコメントとして保存する処理
+  def process
+    board.comments.create!(body: mail.decoded, creator: commenter)
+  end
+
+  # リクエストのバリデーション
+  def validate_request
+    return if commenter && board
+
+    # 引数で受け取ったActionMailerオブジェクトに対してdeliver_laterメソッドを実行し、かつInboundMailの状態をbouncedに変更
+    # bounced状態の時はprocessメソッドが実行されない
+    bounce_with CommentMailer.invalid_request(
+      inbound_email, commenter: commenter, board: board
+    )
+  end

+  # コメント投稿者となるオブジェクトを取得
+  def commenter
+    return @commenter if defined?(@commenter)
+    @commenter = User.find_by(email: mail.from)
+  end

+  # 書き込み先Boardオブジェクトを取得
+  def board
+    return @board if defined?(@board)
+    @board = Board.find_by(id: mail.to.split('-')[0])
+  end
end
```

### メール受信時の処理を確認する開発支援用Web UI

- Action Mailboxには実際にメールを送信しなくてもメール受信時の処理を確認するための開発用のWeb UIが用意されている。下記のURLにアクセスするとメールの送受信をシミュレートできる
http://localhost:3000/rails/conductor/action_mailbox

## Action Textによるリッチテキスト機能

### Action Textのセットアップ

- Action Textはドラッグ&ドロップによるファイルアップロードなどにも対応したリッチテキスト機能
- 機能を利用するためには以下のコマンドを実行する。また、Active Storageの機能を利用するため、同様に`image_processing`や`ImageMagick`などのライブラリもインストールする必要がある(上記参照)

```
$ bin/rails action_text:install
$ bin/rails db:migrate
```

### Action Textの実装

- 実装したいモデルファイルにリッチテキストを属性として定義する

```diff_ruby:app/models/message.rb
class Message < ApplicationRecord
+  has_rich_text :content # contentという名前で属性を定義
end
```

ビューテンプレートで`rich_text_helper`を利用すると`content`編集用のフィールドが表示される

```diff_erb:app/views/messages/_form.html.erb
<%= form_with(model: message, local: true) do |form| %>
  略
+  <div class="field">
+    <%= form.label :content %>
+    <%= form.rich_text_area :content %>
+  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
```

コントローラでは送信されるリッチテキストを許可するように設定

```diff_ruby:app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  略
  def message_params
+    params.require(:message).permit(:content)
  end
end
```

- 保存したリッチテキストはモデルの属性として利用できる

```diff_erb:app/views/messages/show.html.erb
<p id="notice"><%= notice %></p>
+ <%= @message.content %>
<%= link_to 'Edit', edit_message_path(@message) %> |
<%= link_to 'Back', messages_path %>
```

### Action TextにおけるN+1問題の対処

- `meesage.content`のようにリッチテキスト属性にアクセスする場合、一見モデルのカラムを参照しているように見えるが実際には`ActionText::RichText`というモデルのテーブルへアクセスしている。つまり、その都度クエリが発行されるのでN+1問題が発生することになる。専用のeager loadメソッドが用意されているのでそれで対処できる

```diff_ruby:app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  # GET /messages or /messages.json
  def index
-    @messages = Message.all # N+1問題が発生する
+    @messages = Message.with_rich_text_content # eager_loadを使ってN+1問題を解消
  end
end
```

## Action Cableによるリアルタイム通信

Action CableはWebSocketを使用したチャット機能などのリアルタイム処理を提供する組み込みライブラリ

### Action Cableのセットアップ

:::note info
Action CableのセットアップにはJSファイルの修正も含まれるので`rails s`に加えて`bin/webpack-dev-server`も起動しておくとスムーズに開発が進められる
:::

- 以下の`channel`サブコマンドで必要ファイルを生成できる
  - 生成されるファイル
`app/channels/room_channel.rb`: サーバーサイド
`app/javascript/channels/room_channel.js`: クライアントサイド

```
$ bin/rails g channel room speak
```

- ファイルを生成した状態で`rails s`してページにアクセスして以下のメッセージがサーバログに出力すればWebSocketの接続が確認できたことになる

```
$ bin/rails s
略
RoomChannel is streaming from room_channel
```

### ビューの実装

```erb:app/views/rooms/show.html.erb
<h1>Chat room</h1>
<div id="messages">
  <!-- 部分テンプレートを呼び出してインスタンス変数の内容をレンダリング -->
  <%= render @messages %>
  <!-- 以下と同等 -->
  <%# <%= render partial: "message", collection: @messages %>
</div>

<form>
  <label>Say something:</label><br>
  <input type="text" data-behavior="room_speaker">
</form>
```

```erb:app/views/messages/_message.html.erb
<div class="message">
  <p><%= message.content %></p>
</div>
```

### サーバサイドの実装

```diff_ruby:app/channels/room_channel.rb
class RoomChannel < ApplicationCable::Channel
  # 購読後に呼ばれる
  def subscribed
+    stream_from "room_channel" # ブロードキャスト用のチャンネル名を指定
  end

  # 購読解除後に呼ばれる
  def unsubscribed
  end

  # クライアントがチャットメッセージを送信したときに実行される
  def speak(data)
+    message = Message.create!(content: data["message"])
+    # 生成したメッセージを部分テンプレートでHTMLに変換して、room_channelにブロードキャスト
+    ActionCable.server.broadcast(
+      "room_channel", { message: render_message(message) }
+    )
  end

+  private
+
+  # 部分テンプレートでメッセージのHTMLを生成
+  def render_message(message)
+    # ApplicationController.renderでコントローラーを介さずに部分テンプレ+ートをレンダリング
+    ApplicationController.render(
+      partial: "messages/message",
+      locals: { message: message }
+    )
+  end
end
```

### クライアントサイドの実装

```diff_javascript:app/javascript/channels/room_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("RoomChannel", {
  connected() { // 接続時
+    // ユーザーが入力フィールドでEnterキーを押すと、その内容がサーバーに送信される
+    document.
+      querySelector('input[data-behavior=room_speaker]').
+      addEventListener('keypress', (event) => {
+        if (event.key === 'Enter') {
+          this.speak(event.target.value);
+          event.target.value = '';
+          return event.preventDefault();
+        }
+      });
  },

  disconnected() { // 切断時
  },

  received(data) { // サーバーからのメッセージ受信時
+    const element = document.querySelector('#messages');
+    // DOM操作でサーバーからのメッセージをHTMLに追加
+    element.insertAdjacentHTML('beforeend', data['message']); 
  },

  speak: function(message) {
+    // サーバーに speak メソッドを実行するように伝える
+    return this.perform('speak', {message: message}); 
  }
});
```

### 本番で運用する場合のAction Cableの設定

#### アダプタを設定

以下のファイルでWebsocketの接続管理を行うアダプターとアダプターへの接続先を指定する

```config/cable.yml
development:
  adapter: async # インメモリで接続情報を管理

test:
  adapter: test

production:
  adapter: redis # 本番では複数の接続情報をを管理するためにredisを使用する
  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  channel_prefix: actioncable_sample_production
```

#### Action Cableサーバをスタンドアローンで立ち上げる

- デフォルトだとAction Cable用サーバは`/cable`へマウントされ、一つのプロセスにWebサーバとWebSocketサーバが混在している
- RackアプリケーションとしてAction Cable用のサーバプロセスを立ち上げることでスタンドアローンで動作させることができる。以下のファイルを作成して`puma`コマンドでサーバを起動させる

```cable/config.ru
require_relative "../config/environment"
Rails.application.eager_load!

run ActionCable.server
```

```
$ bundle exec puma cable/config.ru
```

さらに以下の設定をアンコメントしてWebサーバにAction Cableがマウントされているのを解除する

```config/environments/production.rb
config.action_cable.mount_path = nil # アンコメントする
```

- Action Cable用のサーバを立ち上げてもデフォルトではAction Cableへの接続は`localhost`が指定されている。サブドメインを設定するには以下の設定をアンコメントする

```config/environments/production.rb
# アンコメントする
config.action_cable.url = 'wss://example.com/cable' 
# 以下の設定で接続可能なオリジンを制限することも可能
config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
  
```

さらに設定をクライアントサイド全体に反映させるためにビューを修正する。`javascript_pack_tag`が書かれている行より前に`action_cable_meta_tag`を配置する

```erb:app/views/layouts/application.html.erb
<%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%# 以下で生成されたタグを見て接続先を判別する %>
    <%= action_cable_meta_tag %>　

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
```

#### Action Cableに認証処理を実装する

コネクションを利用するとWebSocketの認証認可処理を行うことができる。`User`モデルでログイン処理を実装した後に以下のファイルに処理を追加する

```app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user # current_userメソッドが使えるようになる

    # クライアントが接続した時に呼ばれる
    def connect
      self.current_user = find_verified_user
    end

    private

    def find_verified_user
      if verified_user = User.find_by(id: cookies.signed[:user_id])
        verified_user
      else
        reject_unauthorized_connection # ユーザーが見つからない場合は接続を拒否
      end
    end
  end
end
```

#### Action Cableのワーカ数を変更する

WebSocket経由で受け取ったメッセージはAction Cable用のワーカスレッドで処理される。以下の設定でワーカ数を変更できる

```config/environments/production.rb
# デフォルトは4つ
# WebとWebSocketを同一プロセスとしている場合は二つのスレッドを掛け合わせた値が必要
# サーバを分離している場合は、Webサーバ用のスレッドを設定
config.action_cable.worker_pool_size = 10
```
サーバを分離している場合は以下ファイルでWebSocketのワーカ数を設定

```config/cable.yml
development:
  adapter: redis
  url: redis://localhost:6379/1
  worker_pool_size: 10 # WebSocketのワーカ数

test:
  adapter: test

production:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  worker_pool_size: 10
  ```

#### Action Cableのテスト

チャネルとコネクション、二つのテストを用意する

```test/channels/room_channel_test.rb
require "test_helper"

class RoomChannelTest < ActionCable::Channel::TestCase
  test "subscribes" do
    subscribe # 購読作成をシミュレート
    # subscriptionメソッドでRoomChannelオブジェクトを取得し、購読ができたか確認
    assert subscription.confirmed?
    assert_has_stream "room_channel" # ストリームがroom_channelであることを確認
  end

  test 'broadcast' do
    subscribe
    text = "hello"
    # メッセージをHTMLに変換
    broadcast_text = ApplicationController.render(
      partial: "messages/message",
      locals: { message: Message.new(content: text) }
    )
    # performメソッドでクライアントからspeakアクションを呼び出して、ブロードキャストされるか確認
    assert_broadcast_on("room_channel", message: broadcast_text) do
      perform :speak, message: text
    end
  end
end
```

```test/channels/application_cable/connection_test.rb
require "test_helper"

class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
  test "connects with accepts" do
    user = User.first
    cookies.signed[:user_id] = user.id
    connect # Connection#connectメソッドを呼び出して、接続を確立
    assert_equal connection.current_user.id, user.id
  end

  test "connection rejects" do
    cookies.signed[:user_id] = nil # ユーザーIDがnilの場合
    assert_reject_connection { connect } # 接続が拒否されることを確認
  end
end
```