1

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?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Paul D
  • 13
  • 3
  • You can probably hack something together using bindings, but my quick attempts so far have had some odd issues. – Linuxios Jan 25 '16 at 17:40

1 Answers1

0

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.

Community
  • 1
  • 1
Wand Maker
  • 18,476
  • 8
  • 53
  • 87
  • I just tried this out. Unfortunately, I'm getting: NoMethodError: private method `local_variables' called for # – Paul D Jan 25 '16 at 18:15
  • That is not possible - method is available on `Binding` class - http://ruby-doc.org/core-2.2.0/Binding.html#method-i-local_variables ...which version of Ruby are you using? – Wand Maker Jan 25 '16 at 18:17
  • I understand the whole idea of public/private/protected methods...but do I really have to modify (I think people call it "monkeypatch") Kernel#local_variables to be public? Or did I mess something else up? – Paul D Jan 25 '16 at 18:18
  • I'm using Ruby 2.1.6-p336 (2015-04-13) – Paul D Jan 25 '16 at 18:19
  • `local_variables` method is added in Ruby 2.2, and in above code - there is no monkey-patching involved. – Wand Maker Jan 25 '16 at 18:19
  • (I apologize in advance - as you can probably tell I'm still kinda new to Ruby) - So you're saying that the method isn't available in 2.1.6? But how come I call just run it straight-away in an irb session? – Paul D Jan 25 '16 at 18:23
  • I have added a workaround for 2.1.6. There are two methods - one `Kernel#local_variables` and another `Bindging#local_variables` - the version with `Kernel` class always uses current binding, and if you call it within a method - then, you will get that method's local variables only. On the contrary, `Binding#local_variables` will be executed in the context of specific binding. – Wand Maker Jan 25 '16 at 18:24