Paul D
Paul D

Reputation: 13

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?

Upvotes: 1

Views: 234

Answers (1)

Wand Maker
Wand Maker

Reputation: 18762

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.

Upvotes: 0

Related Questions