Rails Active Job をとりえず使ってみる (with docker-compose)
まずはキューイングバックエンドなしで Active Job を使ってみます。
TL;DR
- まぁ使い始めるだけなら全然大したことないですね
- Active Job の基礎 | Railsガイド
- docker-compose でコンテナ作るとかのあたりは大体この記事の最初の方と同じです
- devise の model 分割をチャレンジしてみた(with docker-compose) | 北山淳也 | zenn
この記事でやること、やらないこと
- この記事でやること
- docker-compose で Rails コンテナと MySQL コンテナを用意
- 今後、キューイングバックエンドを導入するときに Docker でやる下準備
- キューイングバックエンドなしで Active Job を使ってみる
- キューイングバックエンドとは、Job のキューpushとキューpopをしてくれる処理のこと
- docker-compose で Rails コンテナと MySQL コンテナを用意
- この記事でやらないこと
- Sidekiq などのキューイングバックエンドの導入
Active Job で何をさせるか
こんな画面を用意し、
call job
ボタンを押すと非同期で3秒感覚で何度か status がインクリメントされる処理を作ります。
非同期で status がインクリメントされる様子は、 update
ボタンを押すことで view が再読み込みされるのでそれで確認できます。
docker-compose の準備, bundle init して rails new してよく使うgemも入れておく
前述の過去記事と同じですが手順を追ってサクッとやっていきましょう。
cd myapp
mkdir -p forDocker/mysql/conf.d/
touch forDocker/mysql/conf.d/mysql.cnf
mkdir -p forDocker/rails/
touch forDocker/rails/entrypoint.sh
touch Dockerfile
touch docker-compose.yml
[mysqld]
default_authentication_plugin = mysql_native_password
skip-host-cache
skip-name-resolve
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
init-connect = SET NAMES utf8mb4
skip-character-set-client-handshake
[client]
default-character-set = utf8mb4
[mysqldump]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
#!/bin/bash
set -e
# Reference by https://matsuand.github.io/docs.docker.jp.onthefly/compose/rails/
# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid
# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
FROM ruby:2.7
RUN set -x && curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN set -x && apt-get update -y -qq && apt-get install -yq less lsof vim default-mysql-client
RUN set -x && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list
RUN set -x && apt-get update -y -qq && apt-get install -yq nodejs yarn
RUN mkdir /app
WORKDIR /app
COPY . /app
# Add a script to be executed every time the container starts.
COPY ./forDocker/rails/entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
# Start the main process.
# ※docker-compose up で rails s するときはここのコメントを外す
# CMD ["rails", "server", "-b", "0.0.0.0"]
version: '3.8'
services:
app:
container_name: app
build: .
tty: true
stdin_open: true
volumes:
- .:/app
- bundle_install:/usr/local/bundle
ports:
- "3000:3000"
depends_on:
- db
db:
platform: linux/x86_64
image: mysql:8.0
container_name: db
restart: always
volumes:
- ./forDocker/mysql/conf.d:/etc/mysql/conf.d
- dbvol:/var/lib/mysql
ports:
- "3306:3306"
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 1
TZ: "Asia/Tokyo"
volumes:
bundle_install:
dbvol:
では初期設定をしていきましょう。
この時点で作業ディレクトリを VSCode とかで開いておきながら以下ターミナルの作業を開始すると楽です。
# サービス用のポート(3000)を有効化し、ホスト側に割り当て可能にして起動
docker-compose run --rm --service-ports app bash
# bashログインできてもちょっと待つ(MySQLが立ち上がってくるのを待つ)
# mysql にパスワードなし root で接続できるか確認
mysql -u root -h db -e 'select version();'
# bundle config 確認 BUNDLE_APP_CONFIG: "/usr/local/bundle" なのを確認
bundle config
# Gemfile 作成
bundle init
# rails 追加 (Gemfile を直接編集してから bundle install でもOK)
bundle add rails --version '~> 6.1.3.1'
bundle install
# rails new
# -B:bundle installしない, -S:sprocketsを組み込まない
# -T:test::unitを組み込まない, -J:javascriptを組み込まない
# -d myaql:データベースの種類, --force:ファイルが存在する場合に上書き
rails new . -B -S -T -J -d mysql --force
bundle install
OKです。今回はついでに simpacker を入れちゃいます。
ターミナルはそのままにしておいて、Gemfile に simpacker
を追記して
...(前略)
gem "simpacker"
...(後略)
でまたターミナルで
# simpacker を入れる
bundle install
# simpakcer 初期化
rails simpacker:install
webpack は消します。
ターミナルはそのままにしておいて、 package.json の devDependencies はいったん空に。
{
"private": true,
"devDependencies": {
}
}
rails simpacker:install すると npm で入ってしまうので
不要なファイルを消して yarn しなおします。
でまたターミナルに戻って
rm -rf node_modules/
rm package-lock.json
rm webpack.config.js
yarn install
これで余計な package のない Simpacker がセットアップできます。
git 管理しているときは .gitignore に /node_modules/*
も追記しておきましょう。
ここまで終わったら次は Rails アプリケーションの準備を整えましょう
ターミナルはそのままにしておいて、 config/database.yml を編集します。
...(前略)
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
url: mysql2://root:@db:3306
# username: root
# password:
# host: localhost
...(後略)
config/database.yml の編集を行ったら、データベースの準備をしてrailsを起動してみます。
でまたターミナルに戻って
rails db:create
rails db:migrate
rails s -b "0.0.0.0"
http://localhost:3000/ で "Yay! You’re on Rails!" っていういつものアレが出ましたでしょうか。
OKですね。Ctrl+c でサーバを閉じましょう。また、
exit
docker-compose down
して一度コンテナ全部を落とします。
これで rails s
が通るようになったので
次に Dockerfile と docker-compose.yml を変更して
docker-compose up
で rails s
されるようにしておきます。
Dockerfile の末尾のコメントを外し、
docker-compose.yml に command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
を追記します。
こんな感じですね。
FROM ruby:2.7
RUN set -x && curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN set -x && apt-get update -y -qq && apt-get install -yq less lsof vim default-mysql-client
RUN set -x && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list
RUN set -x && apt-get update -y -qq && apt-get install -yq nodejs yarn
RUN mkdir /app
WORKDIR /app
COPY . /app
# Add a script to be executed every time the container starts.
COPY ./forDocker/rails/entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
version: '3.8'
services:
app:
container_name: app
build: .
tty: true
stdin_open: true
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- .:/app
- bundle_install:/usr/local/bundle
ports:
- "3000:3000"
depends_on:
- db
db:
platform: linux/x86_64
image: mysql:8.0
container_name: db
restart: always
volumes:
- ./forDocker/mysql/conf.d:/etc/mysql/conf.d
- dbvol:/var/lib/mysql
ports:
- "3306:3306"
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 1
TZ: "Asia/Tokyo"
volumes:
bundle_install:
dbvol:
今後は docker-compose up -d
で全体を起動すれば
puma が勝手に立ち上がってくれます。
docker-compose up -d
# 今後、railsコマンド実行などで app の bash にログインするにはこう
docker-compose exec app bash
Active Job で操作するデータを用意する
今回はシンプルに status という情報を持つ model:JobStatus を用意して
Active Job は非同期で status を Increment する処理にします。
model:JobStatus
id | status | 備考 |
---|---|---|
1 | 1 | status は増え続ける |
というわけで今回はサクッと rails g scaffold
で用意しちゃいましょう。
rails g scaffold JobStatus status:integer
rails db:migrate
はい、これで model,controller,view,route が用意できましたね。
こういうところは Rails は本当に便利です。
JobStatus を更新する job を作成する
あまりよくないネーミングですが今回は SampleJob
という job を用意します。
rails g job sample
class SampleJob < ApplicationJob
queue_as :default
def perform(*args)
logger.debug "Sample Job Start."
job_status = JobStatus.last
if job_status.blank?
logger.debug "no status error."
return
end
for i in 0..4
job_status.status += 1
job_status.save!
sleep(3) # 3秒待機
end
logger.debug "Sample Job End."
end
end
特に難しいことはしていません。
JobStatus の最後のレコードを取得して
3秒待機しつつ 5回 status を Increment します。
- 組み込みライブラリ > Kernelモジュール > sleep | Ruby 2.7.0 リファレンスマニュアル
JobStatus を更新する job を実行するエンドポイントを作成する
./home/call_job
で 上記 SampleJob
を実行するエンドポイントを作成します。
ついでなので root を置き換える view として home/index
も作っちゃいましょう。
rails g Home index
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
<p>
<%= link_to 'to /job_statuses', job_statuses_path %>
</p>
home/index
には ./job_statuses/index
へのリンクを貼っておきました。
class HomeController < ApplicationController
def index
end
def call_job
SampleJob.perform_later
end
end
SampleJob
の実行は Active Job の実行に則って SampleJob.perform_later
とします。これで非同期で SampleJob.perform
が実行されます。
今回はキューイングバックエンドなしで Active Job を利用するので、
メモリ上にタスクがキューイングされたのち実行されます。
あとは config/routes.rb に追記しておきます。
Rails.application.routes.draw do
resources :job_statuses
# 以下を追記
get 'home/index'
get 'home/call_job'
root to: 'home#index'
end
JobStatus を 1レコード作っておく
レコードがないと更新する対象がないので
http://localhost:3000/job_statuses
から New Job Status
で
レコードを最低でも1つ作っておきます。
./job_statuses に job を実行する処理を追加する
準備が整ったので、
最後に ./job_statuses に job を実行する処理を追加します。
既に rails g scaffold
で生成されている
app/views/job_statuses/index.html.erb
に以下のように追記します。
<p id="notice"><%= notice %></p>
<h1>Job Statuses</h1>
<%# buttonを2つ追加 %>
<button type="button" id="reload">reload</button>
<button type="button" id="calljob">call job</button>
<table>
<thead>
<tr>
<th>Status</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @job_statuses.each do |job_status| %>
<tr>
<td><%= job_status.status %></td>
<td><%= link_to 'Show', job_status %></td>
<td><%= link_to 'Edit', edit_job_status_path(job_status) %></td>
<td><%= link_to 'Destroy', job_status, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Job Status', new_job_status_path %>
<%# ここから下を追加 %>
<script>
var reload =document.getElementById('reload');
reload.addEventListener('click',function(){
window.location.reload();
});
var reload =document.getElementById('calljob');
reload.addEventListener('click',function(){
fetch('/home/call_job', {
method: 'GET'
});
});
</script>
これも特に難しいことはしていないです。
button を2つ追加し、
それぞれに reload の処理と Fetch API で GET Request を投げる処理を記述してあります。
reload
ボタンは別に必要ないのですが
calljob
ボタンを押した後、3秒ごとに Status が Increment される様子を
確認しやすいように追加しました。
動かしてみる
call job
ボタンを押すと非同期で3秒感覚で5回 status がインクリメントされます。
非同期で status がインクリメントされる様子は、 update
ボタンを押すことで view が再読み込みされるのでそれで確認できます。
おつかれさまでした。
まとめ
キモは rails g job sample
で作った処理を
SampleJob.perform_later
で call してやるだけなので簡単ですね。
今回はキューイングバックエンドなしで Active Job を利用したので、
メモリ上にタスクがキューイングされたのち実行されます。
次は何かしらのキューイングバックエンドを導入した記事を書ければと思います。
今回のリポジトリはこちらです。
Discussion