caffkane

Ruby debugging for the curious ones

· Logan Kane

Recently I’ve been navigating through some Ruby on Rails systems that are new to me. There are many ways to understand code, and I’ve found that a great way to understand how the code works, is to explore the code.

Some useful gems for deep diving are:

gem "pry"
gem "pry-rails"
gem "pry-stack_explorer"
gem "pry-byebug"
gem "byebug"

We can also utilize the excellent binding.irb, which is built in to Ruby since 2.4.0 but it can be cumbersome not having some easy helpers like ls, step, or continue. Let’s check out the others.

How do we utilize these? The simplest way is to start with binding.pry.

binding.pry, byebug and stack explorer allow us to navigate throughout our Rails program at every frame. We can utilize the tools these gems give us to visualize quite a bit. Some of the helpful commands we can use are available here

Let’s create a class that transforms objects given to it into fun output. We’ll use this class to see how we can understand the methods available on an object and in the console itself.

require "pry"

class AbstractInfoPrinter; end

class SportsInfoPrinter < AbstractInfoPrinter
  SPORTS = %w[⚽️ 🏀 🏈 ⚾️]

  attr_reader :object_to_print
  def initialize(obj)
    @object_to_print = obj
  end

  def print
    transformed_object = transform_object
    puts transformed_object
  end 

  def transform_object
    str = ""
    object_to_print.map do |key, value|
      str << "Player number #{key} has #{value} #{sports_sample}'s"
    end
  end

  private

  def sports_sample
    SPORTS.sample
  end
end

IRB helpers

We can visualize more of what this class does by creating a new instance.

[2] pry(main)> SportsInfoPrinter.methods
=> [:allocate, :superclass, :new, :<=>, :<=, :>=, :==, :===, :included_modules, :include?, :name, :ancestors, :attr, :attr_reader, :attr_writer, :attr_accessor, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :freeze, :inspect, :private_constant, :public_constant, :const_missing, :deprecate_constant, :include, :singleton_class?, :prepend, :module_exec, :module_eval, :class_eval, :remove_method, :<, :>, :undef_method, :class_exec, :method_defined?, :alias_method, :to_s, :private_class_method, :public_method_defined?, :private_method_defined?, :protected_method_defined?, :public_class_method, :autoload, :autoload?, :instance_method, :public_instance_method, :define_method, :hi_there, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_set, :protected_methods, :instance_variables, :instance_variable_get, :public_methods, :private_methods, :method, :public_method, :public_send, :singleton_method, :define_singleton_method, :extend, :to_enum, :enum_for, :=~, :!~, :eql?, :respond_to?, :object_id, :send, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :yield_self, :then, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :equal?, :!, :instance_exec, :!=, :instance_eval, :__id__, :__send__]

Look at all those methods available, 112! Let’s try a few out.

[3] pry(main)> SportsInfoPrinter.included_modules
=> [PP::ObjectMixin, Kernel]

Included modules can help use know if we’ve included something we shouldn’t have, whether through explicit inclusion in the class itself, or through inheritance.

[4] pry(main)> SportsInfoPrinter.superclass
=> AbstractInfoPrinter
[5] pry(main)> SportsInfoPrinter.superclass.superclass
=> Object

We can even chain these methods, to understand the inheritance better.

Debugging

Let’s add binding.pry into one of the methods of our class, re-define the class in the console, then test it out.

...
  def transform_object
    str = ""
    object_to_print.map do |key, value|
      binding.pry
      str << "Player number #{key} has #{value} #{sports_sample}'s"
    end
  end
...

[2] pry(main)> ip = SportsInfoPrinter.new({five: 5})
=> #<SportsInfoPrinter:0x00007ff67617d6c0 @object_to_print={:five=>5}>

[3] pry(main)> ip.print

From: (pry):16 SportsInfoPrinter#transform_object:

    12:   def transform_object
    13:   str = ""
    14:   object_to_print.map do |key, value|
    15:     binding.pry
 => 16:     str << "Player number #{key} has #{value} #{sports_sample}'s"
    17:   end
    18: end

[1] pry(#<SportsInfoPrinter>)> 

binding.pry has pulled up a new instance of pry for us to work with. We can step around the code, evaluate certain things, and see the value of variables. Let’s run a bunch of commands to see what’s available to us

[1] pry(#<SportsInfoPrinter>)> key
=> :five
[2] pry(#<SportsInfoPrinter>)> value
=> 5
[3] pry(#<SportsInfoPrinter>)> sports_sample
=> "⚾️"
[4] pry(#<SportsInfoPrinter>)> sports_sample
=> "🏈"
[5] pry(#<SportsInfoPrinter>)> key = :nine
=> :nine
[6] pry(#<SportsInfoPrinter>)> value
=> 5
[7] pry(#<SportsInfoPrinter>)> ls
SportsInfoPrinter#methods: object_to_print  print  transform_object
instance variables: @object_to_print
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  key  pry_instance  str  value
[8] pry(#<SportsInfoPrinter>)> whereami

From: (pry):16 SportsInfoPrinter#transform_object:

    12:   def transform_object
    13:   str = ""
    14:   object_to_print.map do |key, value|
    15:     binding.pry
 => 16:     str << "Player number #{key} has #{value} #{sports_sample}'s"
    17:   end
    18: end

[9] pry(#<SportsInfoPrinter>)> __callee__
=> :transform_object
[10] pry(#<SportsInfoPrinter>)> __method__
=> :transform_object

[11] pry(#<SportsInfoPrinter>)> step

From: (pry):21 SportsInfoPrinter#sports_sample:

    20:   def sports_sample
 => 21:   SPORTS.sample
    22: end

[11] pry(#<SportsInfoPrinter>)> next

From: (pry):10 SportsInfoPrinter#print:

     8:   def print
     9:   transformed_object = transform_object
 => 10:   puts transformed_object
    11: end 

[11] pry(#<SportsInfoPrinter>)> continue
Player number nine has 5 🏈's
=> nil

As you can see, there are some great tools available to us!

#ruby #writing