Railsのproduction環境でrunnerがエラーになる問題
はじめに
前回の記事で、WheneverによるRailsタスクのcron化について書きました。
これはこれで良かったのですが、アプリを本番環境にデプロイしたあと、作成したタスクを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に移行した途端にオートロードされなくなってしまうというわけでした。