Feature #21219
open`Object#inspect` accept a list of instance variables to display
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
.