# QAPrototype (#91)

**URL:** <https://www.ruby-forum.com/t/qaprototype-91/66343>\
**Category:** Ruby\
**Created:** [August 24, 2006, 8:30pm UTC](https://www.ruby-forum.com/t/qaprototype-91/66343 "2006-08-24T20:30:50Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [August 24, 2006, 8:30pm UTC](https://www.ruby-forum.com/t/qaprototype-91/66343/1 "2006-08-24T20:30:50Z")

</div>

While this quiz is based on the fun idea of an interactive Ruby  
conversation,  
the answers reinforced my belief that it holds practical value in  
prototyping  
Ruby objects. Here’s a sample run of me playing with one of the  
solutions:

```
$ irb -r qap.rb -r enumerator

Call #start if you want to get a head start on QAPrototype...

>> class PascalsTriangle; include QAPrototype; end
=> PascalsTriangle
>> tri = PascalsTriangle.new
=> #<PascalsTriangle:0x33082c>
>> tri.next

next is undefined.
Please define what I should do, starting with arguments
this method should accept (skip and end with newline):

def next
  case row
  when 0 then (@rows = [[1]]).last
  when 1 then (@rows << [1, 1]).last
  else
    ( @rows <<
      [1] + @rows.last.enum_for(:each_cons, 2).map { |l, r| l + r } + 

```

[1]  
).last  
end  
ensure  
@row += 1

```
end

Okay, I got it.

Calling the method now!

row is undefined.
Please define what I should do, starting with arguments
this method should accept (skip and end with newline):

def row
  @row ||= 0

end

Okay, I got it.

Calling the method now!

=> [1]
>> tri.next
=> [1, 1]
>> tri.next
=> [1, 2, 1]
>> tri.next
=> [1, 3, 3, 1]
>> tri.dump
class PascalsTriangle
  def next
    case row
    when 0 then (@rows = [[1]]).last
    when 1 then (@rows << [1, 1]).last
    else
      ( @rows <<
        [1] + @rows.last.enum_for(:each_cons, 2).map { |l, r| l + r } + 

```

[1]  
).last  
end  
ensure  
@row += 1  
end  
def row  
@row ||= 0  
end  
end  
=\> nil

Notice how I just used the non-existent row() method in my definition of  
next().  
I knew I would get a chance to fill it in when it was needed. Building  
up a  
class this way lets you work from the high level down, coding as you go.  
It’s  
an interesting new way to think about programming. I encourage everyone  
to play  
with it a little and decide what you think.

The code for this is not hard to implement. Here’s a trivial solution  
by Erik  
Veenstra:

```
module QAPrototype
  def method_missing(method_name)
    puts "#{method_name} is undefined"
    puts "Please define what I should do (end with a newline):"

    (code ||= "") << (line = $stdin.gets) until line and 

```

line.chomp.empty?

```
    self.class.module_eval do
      define_method(method_name){eval code}
    end

    at_exit do
      puts ""
      puts "class #{self.class}"
      puts " def #{method_name}"
      code.gsub(/[\r\n]+$/, "").split(/\r*\n/).each{|s| puts " "*4+s}
      puts " end"
      puts "end"
    end
  end
end

```

This code defines a QAPrototype module you can mix into any class you  
wish to  
interactively add methods to. The only method in it currently is  
method\_missing(), which Ruby calls whenever an unknown method is called.  
This  
method prompts you for a method body, gathers some code, defines a new  
method on  
the current class that uses the given code, and finally arranges to have  
the  
method printed when IRb exits. That literally covers everything asked  
for in  
the quiz, including my editor’s note.

A limitation of the above code is that it doesn’t deal with method  
arguments.  
That means you can only use it to create and call unparameterized  
methods.  
Another element that made many of the solutions interesting is the extra  
methods  
they defined to further evolve the interaction process.

Let’s look at another solution that handles arguments and adds some nice  
extras.  
Here’s the beginning of the code used in the opening example of this  
summary, by  
Matt T.:

```
module QAPrototype

  attr :_methods_added

  def method_missing name, *args, &block
    puts "\n#{name} is undefined.\n"
    puts "Please define what I should do, starting with arguments"
    puts "this method should accept (skip and end with newline):\n\n"

    # get arguments
    print "def #{name} "; $stdout.flush; arguments = $stdin.gets

    # get method body
    method = ""
    while (print ' '; $stdout.flush; line = $stdin.gets) != "\n"
      method << " " << line
    end
    puts "end\n"

    if method == ""
      puts "\nOops: you left the method empty so we didn't add it.\n\n"
      return
    end

    puts "\nOkay, I got it.\n\n"

    # now define a new method
    self.class.class_eval <<-"end;"
      def #{name} #{arguments}
        #{method}
      end
    end;

    # and store the results to the stack for undoes and dumps
    @_methods_added ||= []
    @_methods_added << { :name => name,
                         :arguments => arguments.chomp,
                         :body => method.chomp }

    puts "\nCalling the method now!\n\n"

    return self.method(name).call(*args, &block)
  end

  # ...

```

This method is not too different from the one we examined earlier. Again  
the  
user is prompted to enter code, but this time the code prompts for an  
argument  
list first. This allows you to specify fixed or variable argument  
lists, with  
or without default values. Once it has the arguments, the method reads  
the body  
of code until you feed it a blank line. If you leave the body blank,  
the code  
skips adding it. Otherwise a quick call to class\_eval() installs the  
method.  
Note the clever use of end; to close the heredoc in this code. A Hash  
of method  
details is also added to the @\_methods\_added Array, so the code can look  
up  
information in later operations. Before this method exits, it triggers  
the call  
you wanted to make in the first place.

Here’s a new feature provided by this module:

```
  # ...

  def undo
    the_method = @_methods_added.pop

    if the_method.nil?
      puts "\nYou have not interactively defined any methods!\n"
      return
    end

    self.class.class_eval { remove_method the_method[:name] }

    puts "\n#{the_method[:name]} is now gone from this class.\n\n"
  end

  # ...

```

When called, undo() pulls the last method added back out of its internal  
list  
and removes it from the class. Future calls to the same method will  
again be  
funneled through method\_missing() so you can redefine the code.

Here are a few more methods you can use to get information back out of  
the  
object:

```
  def dump filename = nil
    body = ""
    @_methods_added.each do |method|
      body << <<-"end;"
  def #{method[:name]} #{method[:arguments]}
#{method[:body]}
  end
end;
    end

    klass = <<-"end;"
class #{self.class}
#{body.chomp}
end
end;

    if !filename.nil?
      File.open(filename, File::CREAT|File::TRUNC|File::RDWR, 0644) do 

```

|file|  
if file.write klass  
puts “\nClass was written to #{filename} successfully!\n\n”  
end  
end  
else  
puts klass  
end  
end

```
  def added_methods
    @_methods_added
  end

  alias :methods_added :added_methods

end

# ...

```

The main point of interest here is the dump() method. When called, is  
builds up  
method definitions for all interactively defined methods and wraps those  
in a  
class definition. If you provided a filename with the call, the entire  
definition is dumped to the indicated file. Otherwise the code is  
printed to  
STDOUT.

This code has one final interesting feature, a sort of jump-start mode  
for IRb:

```
# ...

class Foo; include QAPrototype; end

puts "\nCall #start if you want to get a head start on 

```

QAPrototype…\n\n"

```
def start
  puts "\nirb(prep):001:0> @f = Foo.new"
  puts "irb(prep):002:0> @f.bar\n\n"

  puts "For your convenience in testing, I've created a class called"
  puts "Foo and have already mixed in QAPrototype! Aren't you glad?"
  puts "And while I was at it, I went ahead and created an instance"
  puts "of Foo and put it into @f. Now we can all shout for joy!"
  puts "Heck, we even started up the conversation with method_missing!"

  @f = Foo.new
  @f.bar
end

```

This code just loads the module into a class and provides a globally  
available  
method to create one of these objects and start the method definition  
process  
with a call to a non-existent method. This is just a nice shortcut for  
getting  
started right away when you are playing with this module.

My thanks to all for their creative development of an exciting new way  
to  
incrementally and interactively develop code. I think we may be on to  
something  
here, for a least some use cases.

Tomorrow I will launch the last queued quiz submission. We’ve had a  
sensational  
run of submissions, so don’t let it end now! Keep the ideas rolling on  
in…
