Rails, Sidekiq, RedisをDockerでセットアップし、指定した時間にジョブを実行する

## はじめに
この記事では、Dockerを使用してRails、Sidekiq、Redisをセットアップし、指定した時間にジョブを実行する方法を紹介する。今回は、ユーザーにリマインダーメールを送信する機能を例とする。

## Sidekiqとは
SidekiqはRubyのバックグラウンドジョブ処理ライブラリで、非同期処理を行うために使用される。Redisをバックエンドとして作成したジョブを管理する。

#### 非同期処理とは
プログラムが複数のタスクを並行して実行すること。重い処理や時間のかかる処理をバックグラウンドで行うことでパフォーマンスを向上させることができる。
メールの送信やデータのバッチ処理など、時間のかかるタスクを非同期に行うことが一般的。

## DockerとDocker Composeの設定
まず、DockerとDocker Composeを使用して、Rails、MySQL、Redis、Sidekiqの各サービスをセットアップをする。以下のDockerfileとdocker-compose.ymlをプロジェクトのルートディレクトリに作成する。

```Dockerfile
FROM ruby:3.1
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
apt-get install -y nodejs
ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN mkdir /VehicleMinders
WORKDIR /VehicleMinders
COPY Gemfile /VehicleMinders/Gemfile
COPY Gemfile.lock /VehicleMinders/Gemfile.lock
RUN bundle install
COPY . /VehicleMinders

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
```
```docker-compose.yml
version: '3'
services:
  db:
    platform: linux/amd64
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - db
      - redis
    stdin_open: true
    tty: true
  sidekiq:
    build: .
    command: bundle exec sidekiq
    volumes:
      - .:/app
    depends_on:
      - db
      - redis
  redis:
    image: redis
volumes:
  mysql-data:
    driver: local
```


## Sidekiqの設定
Sidekiqを設定する。Gemfileに`gem 'sidekiq'`を追加し、`bundle install`を実行する。
```title:Gemfile
gem 'sidekiq'
```
Sidekiqのダッシュボード機能を利用できるように、routes.rbに以下のコードを追加する。
`http://localhost:3000/sidekiq`にアクセスすることで実行中のジョブや実行待ちのジョブを確認することができる。
```routes.rb
Rails.application.routes.draw do
  require 'sidekiq/web'
  mount Sidekiq::Web => '/sidekiq'
end
```
Sidekiqの設定ファイルsidekiq.rbを作成する。このファイルでは、SidekiqがRedisサーバーに接続するための設定を行う。

```sidekiq.rb
redis_config = { url: 'redis://redis:6379/0' }

Sidekiq.configure_server do |config|
  config.redis = redis_config
end

Sidekiq.configure_client do |config|
  config.redis = redis_config
end
```

## メール送信機能の作成
メールを送信するためのUserMailerを作成する。

```user_mailer.rb
class UserMailer < ApplicationMailer
  def reminder_email(user)
    @user = notification.user
    mail(to: @user.email, subject: 'Your reminder')
  end
end
```

## メール送信ジョブの作成
指定した時間にメールを送信するジョブを作成する。`app/workers`ディレクトリに作成する。
ジョブは特定のタスクを実行するためのクラスで、`Sidekiq::Worker`モジュールを`include`し、`perform`メソッドを定義する。`perform`メソッドは、ジョブが実行されるときに呼び出される。

```reminder_mailer_worker.rb
class ReminderMailerWorker
  include Sidekiq::Worker

  def perform(notification_id)
    notification = Notification.find(notification_id)
    UserMailer.reminder_email(notification).deliver_now
  end
end
```

## リマインダーのスケジューリング
リマインダーをスケジュールする。今回は、Notificationモデルに`after_create`と`after_update`のコールバックを追加し、リマインダーメールの送信をスケジュールする。
ジョブをキューに追加するには、`perform_async`メソッドを使用する。このメソッドは、ジョブをRedisに保存し、その後バックグラウンドで実行される。
下記では`perform_at`メソッドを使用して、第一引数に`perform`メソッドを実行する時間を指定している。

```notification.rb
class Notification < ApplicationRecord
  belongs_to :user
  after_save :schedule_reminder_mailer, if: -> { self.datetime.present? }

  private
  def schedule_reminder_mailer
    ReminderMailerWorker.perform_at(self.datetime, self.id)
  end
end
```

## まとめ
今回はSidekiqを使って指定した時間にジョブを実行する方法について紹介した。
sidekiq-cronというgemもあり、こちらは定期的に実行する必要があるジョブを管理することができるよう。

## 参考文献・URL
[sidekiqの使い方](https://qiita.com/nysalor/items/94ecd53c2141d1c27d1f)
[公式GitHub](https://github.com/sidekiq/sidekiq/wiki)