💭

[Feature #21219] Object#inspect に含まれるインスタンス変数を制御できるようにする提案

に公開

[Feature #21219] Object#inspect accept a list of instance variables to display

  • Object#inspect はオブジェクトのインスタンス変数も含まれるような形になっている
    • #inspectp 等で内部から呼ばれるメソッド
class User
  def initialize(name)
    @name = name
  end
end

user = User.new("homu")
p user.inspect
# => #<User:0x0000743db53f7bd8 @name="homu">
  • これはデバッグ等で便利なんですがログ出力などで使用する場合に機密情報まで出力されてしまう弊害があります
require 'logger'
logger = Logger.new(STDOUT)

class DatabaseConfig
  def initialize(host, user, password)
    @host = host
    @user = user
    @password = password
  end
end


env = {db_config: DatabaseConfig.new("localhost", "root", "hunter2")}

# 以下のようなログ出力をするときに内部で #inspect が呼ばれる
# パスワードのような秘匿したい情報まで出力されてしまう
logger.info("something happened, env: #{env}")
# => I, [2025-06-04T05:49:03.289106 #2350376]  INFO -- : something happened, env: {db_config: #<DatabaseConfig:0x00007851931555d8 @host="localhost", @user="root", @password="hunter2">}
  • このようなケースで #inspect で参照するインスタンス変数を制御できるようにしたいという内容のチケット
  • チケットでは以下のように #inspect に出力するインスタンス変数名を指定できるような提案がされている
require 'logger'
logger = Logger.new(STDOUT)

class DatabaseConfig
  def initialize(host, user, password)
    @host = host
    @user = user
    @password = password
  end

  # @host と @user のみ inspect に含める
  def inspect = super(instance_variables: [:@host, :@user])
end


env = {db_config: DatabaseConfig.new("localhost", "root", "hunter2")}
logger.info("something happened, env: #{env}")
# => INFO -- : something happened, env: {db_config: #<DatabaseConfig:0x00000001002b3a08 @host="localhost", @user="root">}
class Foo
  def initialize
    @pub_1 = :A
    @pub_2 = :B
    @priv_1 = :secret
    @priv_2 = :secret
  end

  def pretty_print_instance_variables
    super - [:@priv_1, :@priv_2]
  end
end

pp Foo.new
# => #<Foo:0x0000711c6c83cb70 @pub_1=:A, @pub_2=:B>
  • #pretty_print_instance_variables は知らなかった
  • 別の案だと対象のインスタンス変数が #inspect に含まれるかどうかをチェックするメソッドで判定する案もコメントされていますね
class Foo
  def initialize
    @pub_1 = :A
    @pub_2 = :B
    @priv_1 = :secret
    @priv_2 = :secret
  end

  # false を返すと #inspect には含まれなくなる
  def inspect_include_variable?(ivar)
    ivar != :@priv_1 && ivar != :@priv2
  end
end

pp Foo.new
# => #<Foo:0x0000711c6c83cb70 @pub_1=:A, @pub_2=:B>
  • この場合だとメソッドが定義されていない場合は常に true を返す、みたな最適化ができるみたいですね
  • devise gem でも機密情報が出力されないように #inspect を書き換えたりしているので実際の利用ケースはありそうですねー
  • これなんですが最終的には『 #instance_variables_to_inspect が返したインスタンス変数名のみ #inspect に含める』というような挙動になりました
  • これは既に開発版の Ruby 3.5-dev で実装済みです
class DatabaseConfig
  def initialize(host, user, password)
    @host = host
    @user = user
    @password = password
  end

  # @host と @user のみ inspect に含める
  def instance_variables_to_inspect = [:@host, :@user]
end

p DatabaseConfig.new("localhost", "root", "hunter2")
# Ruby 3.4 => #<DatabaseConfig:0x0000736643e793f8 @host="localhost", @user="root", @password="hunter2">
# Ruby 3.5 => #<DatabaseConfig:0x00007668a74a8fe0 @host="localhost", @user="root">
GitHubで編集を提案

Discussion