Project

General

Profile

Actions

Feature #21219

open

`Object#inspect` accept a list of instance variables to display

Added by byroot (Jean Boussier) 7 days ago. Updated 4 days ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:121551]

Description

Context

The default Object#inspect implementation is quite useful to have a generic representation of objects in error message and similar places.

However sometimes objects are referencing other objects with a very large inspect representation, making error message hard to understand.

In some other cases, some instance variables are holding secrets such as password or private keys, and the default inspect behavior can cause
these secrets to be leaked in logs among other places.

You can of course define your own inspect implementation for any object, but it's not as simple as it may seems because you need to handle circular references, otherwise you can end up with a SystemStackError.
Also, it's more minor, but since Ruby 2.7, you can no longer access an object's address, so you can't implement an inspect method that is consistent with Object#inspect

From my experience, user defined implementations of #inspect are very rare, and I think the above is in part responsible.

Feature

I think it would be useful if the default Object#inspect implementation accepted a list of instance variables to display, so that you could very easily hide internal state, either because it's too verbose, or because it is secret:

require 'logger'
logger = Logger.new(STDOUT)

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

  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">}

Updated by mame (Yusuke Endoh) 7 days ago

Just FYI, pretty_print already has that mechanism. It allows to control the list of instance variables that should be displayed by defining a method named pretty_print_instance_variables.

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:0x00007f319f5077b0 @pub_1=:A, @pub_2=:B>

I am not sure how to achieve this with #inspect. Should we introduce a method like #inspect_instance_variables or something to do the same protocol?
I don't think the keyword argument in #inspect is a very good API because specifying the ivar name list outside of the class definition looks a bit unconfortable.

Updated by jeremyevans0 (Jeremy Evans) 7 days ago

I agree with @mame (Yusuke Endoh) that a keyword argument to #inspect is undesirable. #inspect_instance_variables is one possible approach. Another possible approach:

  private def inspect_include_variable?(ivar)
    ivar != :@priv_1 && ivar != :@priv2     
  end

#inspect would call this method with each ivar, and not include the ivar if it returned false. The default implementation would return true for all ivars. This could be optimized so that it checks whether the object responds to the method, and if not, it assumes it would return true without attempting to call it.

Updated by byroot (Jean Boussier) 7 days ago

I'm fine with either of those, with perhaps a slight preference for private def inspect_instance_variables = [:@a, :@b].

Updated by bkuhlmann (Brooke Kuhlmann) 7 days ago

From an developer ergonomic standoint, could only symbols be used to simplify the syntax further? Example:

# First suggestion.
def inspect = super(:host, :user)
 
# Third suggestion.
private def inspect_instance_variables = %i[a b].

Updated by byroot (Jean Boussier) 7 days ago

could only symbols be used to simplify the syntax further?

Technically possible, but not ideal because of various implementation details (instance variables without a @ prefix exist internally).

But regardless, it's also not good because then you can't use the values returned by Object#instance_variables.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0