TL;DR
config.ru
の run 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 も立ち上げたかった。
要件
- RailsでHTTPアプリのServe
- RailsのHTTPアプリで利用するModelやJobをdiscordrbボットでも流用する
-
rails s
コマンドやrails unicorn:start
コマンドの実行時に自動的にdiscord bot立ち上げる -
rails c
コマンドやrails task起動時にはdiscord botが立ち上がってほしくない(重複するため)
初めは config/initializers/discord.rb
にbot起動コードを書いていたが、これだと要件4が満たされない
かといって、単独のrubyプログラムとしてdiscord botを記述してしまうと、要件2が満たされない
さらに、下手くそなのでrails s
時に自動的にbotが立ち上がらないと、忘れてしまう。(要件3)
ネット上で散見される解決策
-
Rails::Server#initialize をオーバーライドしろ
-
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` して準備完了
```ruby
# 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