LoginSignup
12
6

More than 5 years have passed since last update.

Railsでサーバ立ち上げのときのみ実行するコードの配置 - Initialization Codes Executed Only in Server Mode (Rails)

Posted at

TL;DR

config.rurun Rails.application Statement の直前に書く。

# This file is used by Rack-based servers to start the application.

require_relative 'config/environment'

p "Initialization codes should come here."
p "In config.ru file, #{Rails.env}, models can also be handled #{Article.name}"
run Rails.application

経緯

rails で HTTP アプリケーションを立ち上げつつ、同時に discordrb gem を使って discord bot も立ち上げたかった。

要件

  1. RailsでHTTPアプリのServe
  2. RailsのHTTPアプリで利用するModelやJobをdiscordrbボットでも流用する
  3. rails sコマンドやrails unicorn:startコマンドの実行時に自動的にdiscord bot立ち上げる
  4. rails cコマンドやrails task起動時にはdiscord botが立ち上がってほしくない(重複するため)

初めは config/initializers/discord.rb にbot起動コードを書いていたが、これだと要件4が満たされない
かといって、単独のrubyプログラムとしてdiscord botを記述してしまうと、要件2が満たされない
さらに、下手くそなのでrails s時に自動的にbotが立ち上がらないと、忘れてしまう。(要件3)

ネット上で散見される解決策

  1. Rails::Server#initialize をオーバーライドしろ
  2. Rails::Server定数の定義の有無をチェックしてif分岐しろ

    if defined?(Rails::Server)
    # do something
    end
    

しかし、1. は、ブラックボックス触りたくない
2. はRackハンドラ (e.g. unicorn, WEBrickなど) が変わると通用しない

ので、他の方法を模索しました。

(いきなり)解決策

config.ru に書け。
これはrails appが読み込まれ、initializersの処理等も終わったタイミングで呼び出される。
しかも、rails cなど実行時には呼ばれない。

実験

準備

  1. rails new hoge で空のRails app作成
  2. rails g model Article title:string content:text published_at:datetime でそれっぽく作る
  3. rails g job ArticlePublishJob でそれっぽく(対照実験用)
  4. rails taskもそれっぽく作る
  5. config/initializers/sample.rbもそれっぽく
  6. 肝の部分はconfig.ru
  7. rails db:migrate して準備完了
# app/job/article_publish_job.rb

class ArticlePublishJobJob < ApplicationJob
  queue_as :default

  def perform(article)
    article.update(published_at: Time.zone.now)
  end
end
# lib/tasks/articles.rake
namespace :articles do
  desc "publish all articles"
  task :publish => :environment do
    Article.all.each do |article|
      ArticlePublishJob.perform_later article
    end
  end
end
# config/initializer/sample.rb
p "In config/initializers/sample.rb file, #{Rails.env}, #{Article.name}"
Article.create(title: "Sample article", content: "This is a sample article...")
# config.ru

# This file is used by Rack-based servers to start the application.

require_relative 'config/environment'

p "Initialization codes should come here."
p "In config.ru file, #{Rails.env}, models can also be handled #{Article.name}"
run Rails.application

いざ、試験

まず, rails s
/tmp/hoge% rails s
=> Booting Puma
=> Rails 5.0.7 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
"In config/initializers/sample.rb file, development, Article"
"Initialization codes should come here."
"In config.ru file, development, models can also be handled Article"
Puma starting in single mode...
* Version 3.11.4 (ruby 2.5.1-p57), codename: Love Song
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
^C- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2018-05-28 19:03:26 +0900 ===
- Goodbye!
Exiting

ちゃんとconfig.ru内のコードが実行されている

次はrails c
/tmp/hoge% rails c
Running via Spring preloader in process 16334
Loading development environment (Rails 5.0.7)
irb(main):001:0> Article.last
  Article Load (0.6ms)  SELECT  "articles".* FROM "articles" ORDER BY "articles"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<Article id: 1, title: "Sample article", content: "This is a sample article...", published_at: nil, created_at: "2018-05-28 10:12:06", updated_at: "2018-05-28 10:12:06">
irb(main):002:0>

config/initializers/* なファイルだけ実行される。
想定通り。

さいごに rails articles:publish
/tmp/hoge% rails articles:publish
"In config/initializers/sample.rb file, development, Article"

これもOK

さいごに

いろいろ調べたけど, config.ru はRackのエントリーポイントっぽく、RackはHTTPアプリケーションフレームワークということで、HTTP鯖upするときだけ呼ばれるという挙動は納得です。
今回の実装がベクとプラクティスかどうかはわかりませんが、いろいろググってもしっくり来る実装がなくてこの対処に落ち着きました。
他にこんな良い方法あるよ!っていうのがあれば教えて頂けると幸いです。

Rackアプリケーションの概要については @higuma さんの下記記事が大局観をつかみやすく、わかりやすかったです。ありがとうございました。
Rack解説 - Rackの構造とRack DSL

12
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6