DEV Community

Cover image for Runtime Introspection in Ruby
Amr El-Bakry
Amr El-Bakry

Posted on • Edited on

Runtime Introspection in Ruby

Ruby offers a variety of methods that allows you to ask an object about its capabilities (which messages/methods does it respond to?), its variables and constants, and its backstory (the object's class and ancestors).

Methods Introspection

Let's start by asking an object about its non-private methods:

str = 'a string'
str.methods

# => [:include?, :%, :*, :+, :unicode_normalize, :to_c, :unicode_normalize!, :unicode_normalized?, :count, ...]

Enter fullscreen mode Exit fullscreen mode

methods returns an array of symbols representing the names of the public and protected methods for the receiver string object str.

We could also ask the object about:

  • private_methods
  • public_methods
  • protected_methods
  • singleton_methods

by default, Ruby will look up and list all the methods in the object's class and its ancestor classes and modules. If you want to list methods in the object's class only, you could pass the argument false or nil to any of these methods.

str = 'a string'
str.public_methods(false)

# public methods defined in String only
# => [:include?, :%, :*, :+, :unicode_normalize, :to_c, :unicode_normalize!, :unicode_normalized?, :count, ...]

Enter fullscreen mode Exit fullscreen mode

Sometimes, you'll only need to know if an object knows about a specific method, and that's when we use the aptly-named respond_to?:

str = 'a string'
str.respond_to?(:include?)
# => true
str.respond_to?(:first)
# => false
Enter fullscreen mode Exit fullscreen mode

Variables and Constants Introspection

In the same sense, we can query a class about instance_variables, class_variables, and constants.

class Cat
  @@cats_count = 2

  CATS = ['Luna', 'Milo']

  attr_accessor :name, :age

  def initialize(name)
    @name = name
  end
end

cat = Cat.new('Max')
cat.age = 2

cat.instance_variables
# => [:@name, :@age]

cat.class.class_variables
# => [:@@cats_count]

cat.class.constants
# => [:CATS]
Enter fullscreen mode Exit fullscreen mode

Also, there are top-level methods to list local_variables and global_variables.

# irb session
>> local_variables
# => [:_]

Enter fullscreen mode Exit fullscreen mode

This underscore local variable is actually a very interesting one; it always holds the last evaluated expression:

Cat.new('Milo')
# => #<Cat:0x00000002415440 @name="Milo">
cat = _
# => #<Cat:0x00000002415440 @name="Milo">
Enter fullscreen mode Exit fullscreen mode

The Object's Backstory

Finally, let's ask the object about its class, superclass, and ancestors

cats = ['luna', 'milo']
cats.class
# => Array

cats.class.superclass
# => Object

cats.class.ancestors
# => [Array, Enumerable, Object, Kernel, BasicObject]
Enter fullscreen mode Exit fullscreen mode

ancestors returns a list which includes its receiver (Array) and modules included in Array (Enumerable, Kernel), Array's superclass (Object), and lastly the superclass of Object which is (BasicObject).

But that's not it. There are also more convenient ways to ask about the object's class...

  • is_a?
  • kind_of?
  • instance_of?

this trio of predicate methods could also help us with that query:

cats = ['luna', 'milo']
cats.is_a?(Array)
# => true
Enter fullscreen mode Exit fullscreen mode

One important difference to note here is that instance_of? will only return true if the receiver object is a direct instance of the class and not of a subclass:

class A; end
class B < A; end

b = B.new
# => #<B:0x00000001fac638>
b.kind_of?(A)
# true
b.instance_of?(A)
# false

Enter fullscreen mode Exit fullscreen mode

This was just a glimpse of ruby's introspection capabilities, but there are always very interesting things you can learn about your objects!

This article can also be found on Medium.

Top comments (1)

Collapse
 
burdettelamar profile image
Burdette Lamar

You can also use my Ruby gem debug_helper, which helps with printf-style debugging. It displays the principal data from objects, sometimes recursively.

The documentation is over at the GitHub project.