How to run IRB.start in context of current class
Asked Answered
P

9

30

I've been just going through PragProg Continuous Testing With Ruby, where they talk about invoking IRB in context of current class to inspect the code manually.

However, they quote that if you invoke IRB.start in a class, self is predefined, and refers to the object we were in when start was called which isn't true in my case.

Even for very simple example like

a = "hello"
require 'irb'
ARGV.clear # otherwise all script parameters get passed to IRB
IRB.start

When I try to access the a variable, I get the obvious

NameError: undefined local variable or method `a' for main:Object

It works only when I change a to global variable

$a = "hello"
require 'irb'
ARGV.clear # otherwise all script parameters get passed to IRB
IRB.start

then I can access it

irb(main):001:0> $a
=> 1

Is there any way around this to access local and instance variables in the current class?

Potency answered 15/11, 2010 at 23:17 Comment(0)
A
14

I'd suggest trying this in ripl, an irb alternative. The above example works:

a = 'hello'
require 'ripl'
Ripl.start :binding => binding

Note that local variables work because your passing the current binding with the :binding option.

You could possibly do the same in irb, but since it's poorly documented and untested, your chances of doing it cleanly are slim to none.

Anglicist answered 16/11, 2010 at 0:12 Comment(3)
Unfortunately, this is a no-go under JRuby.Tolerance
You should open up an issue: github.com/cldwalker/ripl/issues. ripl has been verified to work on jrubyAnglicist
Second the advice to avoid IRB for this. Pry is another option.Duffel
L
40

As you've already discovered, self does not refer to the object where IRB was started, but to the TOPLEVEL_BINDING, which seems to be an instance of the Object class itself.

You can still run an IRB session with a specific class or object as the context, but it's not as simple as just starting IRB.

If you care about is starting IRB with a specific context, then it's really easy to do when you're starting IRB manually. Just start IRB normally and then call the irb method, passing it the object/class you want as the context.

$ irb
irb(main):002:0> require 'myclass'
=> true
irb(main):003:0> irb MyClass
irb#1(MyClass):001:0> self
=> MyClass

You can also start an IRB session programmatically and specify the context, but it's not nearly as easy as it should be because you have to reproduce some of IRB's start-up code. After a lot of experimenting and digging around in the IRB source code, I was able to come up with something that works:

require 'irb'
IRB.setup nil
IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
require 'irb/ext/multi-irb'
IRB.irb nil, self
Lipophilic answered 13/2, 2011 at 23:51 Comment(5)
IMHO the only answer that answers this question.Carnelian
Upvoted, works for me and answers the original question (instead of suggesting alternatives), thanks!Dagenham
Epic answer. Thank you - totally works for me (ruby 1.9.3) first try. Thanks for doing that work so I don't have to.Chondrite
Dang it, not working here. Maybe it's because we're using JRuby. :(Shepperd
Doesn't seem to be working to run in a selected namespace. Ended up using module A; binding.irb; end. Within class context I guess call directly binding.irb as in David's answer.Rowney
H
16

As of Ruby 2.4.0, you can do this:

require 'irb'
binding.irb

This will start an IBR REPL where you will have the correct value for self and you will be able to access all local variables and instance variables that are in scope. Type Ctrl+D or quit in order to resume your Ruby program.

Hema answered 12/1, 2017 at 3:14 Comment(1)
yes! Just found it here and wanted to write the answer...Sielen
A
14

I'd suggest trying this in ripl, an irb alternative. The above example works:

a = 'hello'
require 'ripl'
Ripl.start :binding => binding

Note that local variables work because your passing the current binding with the :binding option.

You could possibly do the same in irb, but since it's poorly documented and untested, your chances of doing it cleanly are slim to none.

Anglicist answered 16/11, 2010 at 0:12 Comment(3)
Unfortunately, this is a no-go under JRuby.Tolerance
You should open up an issue: github.com/cldwalker/ripl/issues. ripl has been verified to work on jrubyAnglicist
Second the advice to avoid IRB for this. Pry is another option.Duffel
N
11

Instead of global you could use instance variables, e.g.:

require 'irb'
@a = "hello"
ARGV.clear
IRB.start

>> @a
=> "hello"
Nagel answered 15/11, 2010 at 23:28 Comment(2)
What would using instance variables change ?Headstone
@ulvund Well, unlike local variables they remain visible after IRB.start (i.e., replacing @a in this code with a would not work), but unlike global variables they do not pollute the global namespace (just that of the main object). In any case, other answers already mention ways to get locals working as well, so if using an external library or doing a bit of more work is not a problem, then those are better alternatives. (I would prefer Pry myself.)Nagel
H
10

Use Pry:

a = 'hello'
require 'pry'
binding.pry
Hathorn answered 27/5, 2012 at 4:35 Comment(2)
I have have some problems with Ripl (which is the accepted solution) giving me another context than I expected. So on closer inspection I'm voting for Pry as well.Headstone
I couldn't get pry to work, maybe some incompatibility /usr/local/share/gems/gems/term-ansicolor-1.2.2/lib/term/ansicolor.rb:188:in 'color': wrong number of arguments (0 for 1..2) (ArgumentError) from /usr/local/share/gems/gems/pry-0.10.0/lib/pry/output.rb:35:in 'decolorize_maybe'Rowney
B
6

Here's how to invoke IRB from your script in the context of where you call IRB.start..

require 'irb'
class C
    def my_method
        @var = 'hi'
        $my_binding = binding
        IRB.start(__FILE__)
    end
end

C.new.my_method

Executing your script will invoke IRB. When you get to the prompt you have one more thing to do...

% ./my_script.rb
irb(main):001:0> @var.nil?
=> true
irb(main):002:0> cb $my_binding
=> #<C:0x000000009da300 @var="hi">
irb(#<C:0x000000009da300>):003:0> @var.nil?
=> false
irb(#<C:0x000000009da300>):004:0> @var
=> "hi"

Enjoy!

Beefcake answered 13/8, 2011 at 4:46 Comment(2)
That may be. But the question is, "How to run IRB.start in context of current class". My answer is the only one that actually answers the question.Beefcake
You can't access local variables, thus it doesn't answer the question.Judicatory
S
2

My solution for Ruby 2.2.3. It is very similar to Bryant's

def to_s
  "Sample"
end

def interactive
  banana = "Hello"
  @banana = "There"
  require 'irb'
  IRB.setup(nil)
  workspace = IRB::WorkSpace.new(binding)
  irb = IRB::Irb.new(workspace)
  IRB.conf[:MAIN_CONTEXT] = irb.context
  irb.eval_input
end

irb(Sample):001:0> puts banana
Hello
=> nil
irb(Sample):002:0> puts @banana
There
=> nil
irb(Sample):003:0>

I can access local variables and instance variables. The require 'irb' of course could be at the top of the file. The setting of banana and @banana is just to prove I can access them from the irb prompt. The to_s is one method of getting a pretty prompt but there are other choices. And there is no real reason to make a separate method but as it stands, you can plop this in anywhere and it should work.

Socinian answered 5/12, 2015 at 4:5 Comment(0)
D
0

The ruby-debug-base gem adds a binding_n method to the Kernel module and this will give you access binding object that can be used in an eval to give access to call-stack variables. Remember to issue Debugger.start to turn on call stack tracking.

Here is an example showing its use to introspect what is going on inside irb (a Ruby program). One could put the require's and Debugger.start inside your own Ruby code as well.

$ irb
ruby-1.8.7-p302 > require 'rubygems'
 => true 
ruby-1.8.7-p302 > require 'ruby-debug-base'
 => true 
ruby-1.8.7-p302 > Debugger.start
 => true 
ruby-1.8.7-p302 > puts caller
/tmp/.rvm/rubies/ruby-1.8.7-p302/lib/ruby/1.8/irb/workspace.rb:52  :i  n `irb_binding' #`
/tmp/.rvm/rubies/ruby-1.8.7-p302/lib/ruby/1.8/irb/workspace.rb:52
=> nil 
ruby-1.8.7-p302 > eval "main", binding_n(2)
 => #<Object:0xb7762958 @prompt={:PROMPT_I=>"ruby-1.8.7-p302 > ", :PROMPT_N=>"  ruby-1.8.7-p302 ?> ", :PROMPT_S=>"ruby-1.8.7-p302%l> ", :PROMPT_C=>"ruby-1.8.7-p302 > ", :AUTO_INDENT=>true, :RETURN=>" => %s \n"}> 
ruby-1.8.7-p302 > 

If you are willing to run a patched version of Ruby for 1.9.2 see http://gitnub.com/rocky/rb-threadframe for what I think is better access to the call stack. Rubinius provides this capability built in via Rubinius::VM.backtrace.

Dorran answered 15/11, 2010 at 23:17 Comment(0)
P
0

This one is kind of a mash up of all the answers in this thread. Is the smallest piece of code that would produce the behavior requested by the person who asked:

require 'irb'

module Craft
  class Console
    def start
      IRB.setup(__FILE__)
      workspace = IRB::WorkSpace.new(self)
      IRB::Irb.new(workspace).run(IRB.conf)
    end

    def hello
      puts 'hello world!'
    end
  end
end

There is no need to clear the ARGV and also no need for explicitly calling eval_input.

Here is how the output looks:

bundle exec craft console
irb(#<Craft::Console:0x00007fb0135592c0>):001:0>hello
hello world!
=> nil
Piaffe answered 22/7, 2021 at 11:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.