はじめに
Railsの開発をしているとログやターミナルに警告が表示されることがあります。
たとえば以下はRSpecのテストコードを実行したときの実行結果です。
いくつか警告(warning)が表示されています。
$ bundle exec rspec spec/models/user_spec.rb
warning: parser/current is loading parser/ruby31, which recognizes 3.1.4-compliant syntax, but you are running 3.1.3.
Please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from block in <main> at /Users/jnito/dev/some-app/config/environments/test.rb:75)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from block in <main> at /Users/jnito/dev/some-app/config/initializers/devise.rb:15)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from <class:User> at /Users/jnito/dev/some-app/app/models/user.rb:96)
Randomized with seed 44700
.
Finished in 0.22515 seconds (files took 1.91 seconds to load)
1 example, 0 failures
Randomized with seed 44700
警告は文字通り「警告」なので早めに対処した方が良いです。
放置すると将来的に深刻な問題を引き起こす可能性があります。
しかし、「対処した方がよいのは100%承知してるけど、今はちょっと無理なんです!!🥺」というときもあります。
しかも、「いったん無視したい大量の警告」と「早めに対処したい少量の警告」が混在していると、前者がノイズになって後者を見落としてしまう恐れがあります。
たとえば以下のようなイメージです(あくまでイメージですが)。
$ bundle exec rspec spec/models/user_spec.rb
warning: parser/current is loading parser/ruby31, which recognizes 3.1.4-compliant syntax, but you are running 3.1.3.
Please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from block in <main> at /Users/jnito/dev/some-app/config/environments/test.rb:75)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from block in <main> at /Users/jnito/dev/some-app/config/initializers/devise.rb:15)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from <class:User> at /Users/jnito/dev/some-app/app/models/user.rb:96)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from block in <main> at /Users/jnito/dev/some-app/config/environments/test.rb:75)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from block in <main> at /Users/jnito/dev/some-app/config/initializers/devise.rb:15)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from block in <main> at /Users/jnito/dev/some-app/config/environments/test.rb:75)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from block in <main> at /Users/jnito/dev/some-app/config/initializers/devise.rb:15)
DEPRECATION WARNING: Passing the coder as positional argument is deprecated and will be removed in Rails 7.2. (called from <class:User> at /Users/jnito/dev/some-app/app/models/user.rb:78)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from <class:User> at /Users/jnito/dev/some-app/app/models/user.rb:96)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from block in <main> at /Users/jnito/dev/some-app/config/environments/test.rb:75)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from block in <main> at /Users/jnito/dev/some-app/config/initializers/devise.rb:15)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from <class:User> at /Users/jnito/dev/some-app/app/models/user.rb:96)
Randomized with seed 44700
.
Finished in 0.22515 seconds (files took 1.91 seconds to load)
1 example, 0 failures
Randomized with seed 44700
こうした状況を想定して、この記事ではノイズになる警告を一時的に抑制する方法をいくつか紹介します。
警告
ここで紹介する方法はあくまで 一時的に抑制したい場合だけ に利用してください。
抑制したまま警告を放置し続けると、忘れた頃に深刻な問題を引き起こす可能性があります。
動作確認をした実行環境
- Rails 7.1.3.2
- Ruby 3.1.3
- warning 1.3.0
RailsのDEPRECATION WARNINGを抑制する
簡単に制御できるやり方が見つからなかったので、ActiveSupport::Deprecationクラスのbehavior
メソッドをprependでオーバーライドすることにしました。
以下はconfig/application.rb
でオーバーライドする例です。
require_relative "boot"
require "rails/all"
# テストコードの実行時のみ警告を抑制する
if Rails.env.test?
ActiveSupport::Deprecation.prepend(
Module.new do
def behavior
original = super
original.map do |prc|
-> (message, callstack, deprecator) do
# 警告メッセージに以下のどちらかが含まれていたら無視する
# - Rails.application.secrets
# - Passing the coder as positional argument
ignore = ["Rails.application.secrets", "Passing the coder as positional argument"].any? { |m| message.include?(m) }
prc.call(message, callstack, deprecator) unless ignore
end
end
end
end
)
end
Bundler.require(*Rails.groups)
# ...
config/environments/test.rb
やconfig/initializers
に記述することもできますが、なるべく早いタイミングでオーバーライドしないと先に警告が出力されてしまうケースがあるため、今回はconfig/application.rb
に設定を記述しました。
Kernelのwarnで出力される警告を抑制する
Railsが出力するDEPRECATION WARNINGと、それ以外の警告は出力の仕組みが異なるので、対処方法も異なります。
プロジェクト内に書いたコードが原因になっている場合
$VERBOSE
にnil
をセットすると、その間だけ警告を抑制できます。
以下は「やんごとなき理由でgemに設定された定数を再代入しないといけない場合」に発生する警告を抑制する例です。
# ↓この警告を消したい
/Users/jnito/dev/some-app/config/initializers/some_awesome_gem.rb:2: warning: already initialized constant SomeAwesomeGem::IMPORTANT_VALUE
/Users/jnito/.rbenv/versions/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/some_awesome_gem/lib/some_awesome_gem/command.rb:5: warning: previous definition of IMPORTANT_VALUE was here
# SomeAwesomeGemという架空のgemに定義されたIMPORTANT_VALUEという定数をやむを得ず再代入する
module SomeAwesomeGem
old_verbose = $VERBOSE
$VERBOSE = nil # ここから下は警告が出なくなる
IMPORTANT_VALUE = 123
$VERBOSE = old_verbose # 元の値に戻す(警告が出るようになる)
end
$VERBOSE
の役割と設定値については公式リファレンスを参照してください。
gem内のコードが原因になっている場合
自分がプロジェクト内で書いたコードではなく、gemのコードが警告を発する場合もあります。
たとえば、parser gemを使っていると、以下のような警告が表示される場合があります。
warning: parser/current is loading parser/ruby31, which recognizes 3.1.4-compliant syntax, but you are running 3.1.3.
Please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
こうした警告の抑制はWarningモジュールのwarn
メソッドをオーバーライドすると便利です。
以下は config/application.rb
でWarningモジュールを使って上記の警告を抑制する例です。
require_relative "boot"
require "rails/all"
# テストコードの実行時のみ警告を抑制する
if Rails.env.test?
module Warning
def self.warn(message, category: nil)
# 警告メッセージに "parser" が含まれていたら警告を抑制する
ignore = ["parser"].any? { |m| message.include?(m) }
super(message, category: category) unless ignore
end
end
end
Bundler.require(*Rails.groups)
# ...
「プロジェクト内に書いたコードが原因になっている場合」で抑制した警告も、このWarningモジュールで一緒に抑制できます。
require_relative "boot"
require "rails/all"
# テストコードの実行時のみ警告を抑制する
if Rails.env.test?
module Warning
def self.warn(message, category: nil)
# 警告メッセージに "parser" か "IMPORTANT_VALUE" が含まれていたら警告を抑制する
ignore = ["parser", "IMPORTANT_VALUE"].any? { |m| message.include?(m) }
super(message, category: category) unless ignore
end
end
end
Bundler.require(*Rails.groups)
# ...
Warningモジュールのwarn
メソッドの詳しい内容は以下の公式リファレンスを参照してください。
Rubyが出力する非推奨警告を抑制する(?)
以下のように(Railsではなく)Ruby自体が非推奨警告を出力する場合があります。
/Users/jnito/dev/some-app/config/environments/test.rb:4: warning: File.exists? is deprecated; use File.exist? instead
しかし、Rubyの非推奨警告はデフォルトでは出力されません。
Rubyの非推奨警告が出力されるのは RUBYOPT=-W:deprecated
という環境変数が指定されていた場合だけです。
# RUBYOPT=-W:deprecated を指定して意図的にRubyの非推奨警告を出力する
$ RUBYOPT=-W:deprecated bundle exec rspec spec/models/user_spec.rb
/Users/jnito/dev/some-app/config/environments/test.rb:4: warning: File.exists? is deprecated; use File.exist? instead
Randomized with seed 20373
.
Finished in 0.2127 seconds (files took 2.12 seconds to load)
1 example, 0 failures
Randomized with seed 20373
なので、この警告を抑制するのであれば、どこかで設定されているRUBYOPT=-W:deprecated
環境変数を削除してください。
# RUBYOPT=-W:deprecated を付けなければRubyの非推奨警告は出力されない
$ bundle exec rspec spec/models/user_spec.rb
Randomized with seed 20373
.
Finished in 0.2127 seconds (files took 2.12 seconds to load)
1 example, 0 failures
Randomized with seed 20373
$stderr に直接puts/printされている場合
たまに $stderr
に対して直接警告を puts
/ print
するgemがあったりします。
たとえば、MiniMagickを使っていると、以下のような警告が出る場合があります。
convert: profile 'icc': 'RGB ': RGB color space not permitted on grayscale PNG `/var/folders/r1/fsj20_kx4tj5b9why4z15nmw0000gn/T/image_processing20220826-54331-dqrbrb.png' @ warning/png.c/MagickPNGWarningHandler/1750.
MiniMagick gemのコードを見ると、 $stderr
に対して直接 print
していることがわかります。
# https://github.com/minimagick/minimagick/blob/v4.12.0/lib/mini_magick/shell.rb
module MiniMagick
class Shell
def run(command, options = {})
stdout, stderr, status = execute(command, stdin: options[:stdin])
if status != 0 && options.fetch(:whiny, MiniMagick.whiny)
fail MiniMagick::Error, "`#{command.join(" ")}` failed with status: #{status} and error:\n#{stderr}"
end
# $stderr に直接 print している
$stderr.print(stderr) unless options[:stderr] == false
[stdout, stderr, status]
end
こういうケースは簡単に対処できないので、やむを得ずモンキーパッチを当てたりします。
以下は config/initializers/mini_magick.rb
というファイルを作って、そこで元々の処理をオーバーライドする例です。
if Rails.env.test?
module MiniMagick
class Shell
def run(command, options = {})
stdout, stderr, status = execute(command, stdin: options[:stdin])
if status != 0 && options.fetch(:whiny, MiniMagick.whiny)
fail MiniMagick::Error, "`#{command.join(" ")}` failed with error:\n#{stderr}"
end
# "RGB color space not permitted on grayscale" という文言が含まれていたら出力しない
unless stderr.include?('RGB color space not permitted on grayscale')
$stderr.print(stderr) unless options[:stderr] == false
end
[stdout, stderr, status]
end
end
end
end
コラム:「ついうっかり」で放置し続けないように工夫を入れる
警告を抑制するのはあくまで「一時的」であるべきです。
しかし、人間はついうっかり抑制したまま警告を放置してしまう可能性があります。
その「ついうっかり」をなくすための工夫を紹介します。
TODOコメントを入れる
コードレビューを必ず通してからプルリクエストをマージするワークフローになっている場合は、「あとで必ず消す」というTODOコメントを付けておくといいですね。
こうしておけば、万一警告を抑制したままになっていてもレビュアーが「これ、消さなくて大丈夫?」とコメントしてくれるかもしれません。
require_relative "boot"
require "rails/all"
# TODO: あとで必ず消す!!(というTODOコメントを付けておく)
if Rails.env.test?
module Warning
def self.warn(message, category: nil)
ignore = ["parser"].any? { |m| message.include?(m) }
super(message, category: category) unless ignore
end
end
end
Bundler.require(*Rails.groups)
# ...
有効期限を付ける
以下のように有効期限付きで警告を抑制するのも良いかもしれません。
require_relative "boot"
require "rails/all"
# 2024-5-31まで警告を抑制する(2024-6-1以降は警告が出力される)
if Rails.env.test? && Date.today <= Date.new(2024, 5, 31)
module Warning
def self.warn(message, category: nil)
ignore = ["parser"].any? { |m| message.include?(m) }
super(message, category: category) unless ignore
end
end
end
Bundler.require(*Rails.groups)
# ...
まとめ:ご利用は計画的に!!
というわけで、この記事ではノイズになる警告を一時的に抑制する方法をいくつか紹介してみました。
繰り返しになりますが、警告を抑制するのはあくまで「一時的に」ととどめましょう。
警告は無視するのではなく、早めに対処するのが最善であることをお忘れなく。
重要: 警告は無視せず、一日も早い対処を!!