Search code examples
rubyscopeirb

How to escape scope in IRB?


In my .irbrc file I'm trying to write some kind of construct, class, method, module, such that when I invoke the construct in an IRB session, it will output an interleaved array of currently available objects and what classes those objects came from.

In an active IRB session, I successfully created this interleaved array using:

>> class Ticket
>>    def event
>>        "Stuff"
>>    end
>> end
>>
>> ticket1 = Ticket.new
>> => #<Ticket:0x25f5900>
>> ticket2 = Ticket.new
>> => #<Ticket:0x27622c8>
>> ticket3 = Ticket.new
>> => #<Ticket:0x304f590>
>>
>> available_objects_array = ObjectSpace.each_object(Ticket).to_a
>> => [#<Ticket:0x25f5900>, #<Ticket:0x27622c8>, #<Ticket:0x304f590>]
>> local_variables_array = local_variables.delete_if {|y| eval(y.to_s).class != Ticket}
>> => [:ticket3, :ticket2, :ticket1]
>> local_variables_array_reversed = local_variables_array.reverse.map {|z| z.to_s}
>> => ["ticket1", "ticket2", "ticket3"]
>> objects_and_variables_array = local_variables_array_reversed.zip(available_objects_array).flatten.compact
>> => ["ticket1", #<Ticket:0x25f5900>, "ticket2", #<Ticket:0x27622c8>, "ticket3", #<Ticket:0x304f590>]

So, all is well so far.

Then I made a new method in my .irbrc file:

def show_top_locals(class_param)
    available_objects_array = ObjectSpace.each_object(class_param).to_a
    local_variables_array = local_variables.delete_if {|y| eval(y.to_s).class != class_param}
    local_variables_array_reversed = local_variables_array.reverse.map {|z| z.to_s}
objects_and_variables_array = local_variables_array_reversed.zip(available_objects_array).flatten.compact
end

and started another IRB session and manually recreated the Ticket class and ticket1, ticket2, and ticket3 variables. Finally, I tried to use the show_top_locals method:

>> show_top_locals(Ticket)
=> []

I got nothing back because the Kernel.local_variables method that I'm using in the local_variables_array variable only looks for variables that are defined within the show_top_locals method scope.

Then I made a module in a separate file and required the file and included the module in my .irbrc:

require '.\testruby.rb'
include Top

Here's my module:

#testruby.rb
module Top
    ShowVars = Class.new do
        p Kernel.local_variables

        define_method(:toplevelvars) do
            p Kernel.local_variables
        end
    end
end

Back in a new IRB session:

>> show_vars = ShowVars.new
>> => #<Top::ShowVars:0x1eac1e8>
>> show_vars.toplevelvars
[]
=> []

I'm still trapped in the wrong scope.

How can I write code in .irbrc that I can use in an IRB session to give me the aforementioned interleaved object and variable array?


Solution

  • You don't seem to be returning all the values from your method show_top_locals. Also, the call to Kernel#local_variables inside a method will only lookup local variables from the method scope. You should pass the binding from the place you are calling the method so that you get list of local variables at the place where method was invoked.

    require "pp"
    
    # For backward compatibility with Ruby 2.1 and below
    def local_vars(binding)
        if binding.respond_to? :local_variables
            binding.local_variables
        else
            eval "Kernel.local_variables", binding
        end
    end
    
    def show_top_locals(class_param, binding)
        available_objects_array = ObjectSpace.each_object(class_param).to_a
        local_variables_array = local_vars(binding).delete_if {|y| binding.eval(y.to_s).class != class_param}
        local_variables_array_reversed = local_variables_array.reverse.map {|z| z.to_s}
        objects_and_variables_array = local_variables_array_reversed.zip(available_objects_array).flatten.compact
        return {
           available_objs: available_objects_array,
           local_vars: local_variables_array,
           local_vars_reversed: local_variables_array_reversed,
           objs_and_vars: objects_and_variables_array
         }
    end
    
    class Foo
    end
    
    a = Foo.new
    b = Foo.new
    
    pp show_top_locals(Foo, binding)
    #=> {:available_objs=>[#<Foo:0x000000027825b0>, #<Foo:0x000000027825d8>],
    #    :local_vars=>[:a, :b],
    #    :local_vars_reversed=>["b", "a"],
    #    :objs_and_vars=>["b", #<Foo:0x000000027825b0>, "a", #<Foo:0x000000027825d8>]}
    

    You can place the method show_top_locals in a .rb file and refer to "Can you 'require' ruby file in irb session, automatically, on every command?" to autoload it in IRB.