とりとめも

藻類が学んだり感じたりしたことを未来の自分のために書き留めるところ

Railsのproduction環境でrunnerがエラーになる問題

はじめに

前回の記事で、WheneverによるRailsタスクのcron化について書きました。

marimomemo.hatenablog.jp

これはこれで良かったのですが、アプリを本番環境にデプロイしたあと、作成したタスクをrunnerで実行するとよくわからないエラーが出る問題に直面しました。 (前回の記事では問題解消したコードを載せています)

恥ずかしながら3時間くらい苦しんだので、原因と対応を書き留めます。

状況

前回の記事の要領でlib/tasksにタスクファイルを作りました。
例として、前回と同じlib/tasks/marimotask.rbを作ったものとします。

require "#{Rails.root}/app/models/marimodel"
require "#{Rails.root}/app/lib/malib"
class Tasks::Marimotask
  def self.marimo
   #modelやlibのメソッド呼んだり
    Marimodel.hoge
    Malib::Marimo.nyanya 
    #適当な処理書いたり
    foo
    #putsしてみたり
    puts "成功しました!"
  end
end

本番サーバーで、以下のコマンドを打ってみます。

$ bundle exec rails runner Tasks::Marimotask.marimo
成功しました!

成功したように見えますが、これはdevelopment環境で実行されてしまっています。
実際、私が問題に直面した時は、developmentデータベースを操作しようとエラーになっていました。

つまり、productionにしないといけない。
それならこれでどうだ!

$ bundle exec rails runner Tasks::Marimotask.marimo -e production
Please specify a valid ruby command or the path of a script to run.
Run 'bin/rails runner -h' for help.

uninitialized constant Rails::Command::RunnerCommand::Tasks
Did you mean?  Rails::Command::RunnerCommand::Task

よくわからないエラーがでてきました。
このエラーに3時間近く苦しめられたのでした。

よくよく考えればクラスがrequireできていないっぽく見えますし、それも考えなかったわけではないのですが、runnerを使ったのも初めてだったり、環境指定が「-e production」で良いのか自信がなかったり(rakeだとRAILS_ENVじゃないですか!)で、まともに問題の切り分けもできず迷子になっていました。

というわけで、原因と解決策です。

解決策:production用のロードパスに設定追記する

こちらの記事に辿り着き、解決することができました。

ます、タスクを作成するにあたっては、lib以下をロード対象とするためにapplication.rbにオートロードの設定を記述する必要がありました。
ここで前回書いた通り、私はconfig.autoload_pathsしか書いていませんでした。これが問題でした。
config.application.rbに以下を書き足してあげれば解決です。

config.autoload_paths += Dir["#{config.root}/lib/**/"]
#↓これを書き足す!!
config.eager_load_paths += Dir["#{config.root}/lib/**/"]
$ bundle exec rails runner Tasks::Marimotask.marimo -e production
成功しました!

原因について少し詳しく

前掲の記事に書いてある通りですが、原因を整理しておきます。

今回のエラーの根本原因は、config/environmentsの環境別ファイル(produciton.rb等)で定義される「Rails.application.config.cache_classes」の値でした。

Rails.application.config.cache_classesが、

falseの場合 → Rails.application.config.auto_load_pathのパスを、リクエストのたびにロードする

trueの場合 → Rails.application.config.eager_load_pathsのパスを、rails起動の際にロードする

という動きらしいです。要するにキャッシュです。

productionだとtrueで、developmentだとfalseがデフォルトらしいです。falseだとパフォーマンスは低下しますが、編集するたびにrails再起動しなくてよいのでdevelopment環境向きなんですね。
そんなわけで、auto_load_pathしか定義していないと、productionに移行した途端にオートロードされなくなってしまうというわけでした。