HomeHome ArchiveArchive

Reducing magic in Ruby’s Forwardable class implementation

Abstract

Using string-eval in Ruby for metaprogramming is unnecessarily obscuring. Ruby’s more modern and specific metaprogramming methods should be used instead whenever possible. This problem is illustrated on the example of Ruby’s Forwardable class.

In detail…

Ruby’s Forwardable class is using metaprogramming to forward calls from a frontend interface to an instance in the back executing the call.

Metaprogramming is the discipline of making code that creates code. This task allready is rather abstract and hard to grasp in itself. Having hard to grasp code is a liability. One of the goals of writing code is allways to keep the code as simple and as well understandable as possible.

Additionaly, metaprogramming code itself is difficult to read and understand: that is because the metaprogramming code will not necessarily express what the code it is creating is about, but only how it is creating that code. As such the code it is creating can be invisible to you as a reader of the source code - the created code will only start to exist at runtime.

One would therefore expect that programmers would try especially hard when they metaprogram to make that particular kind of code expressive and easy to understand.

Another consequence of the fact that the code produced by metaprogramming is not necessarily visible, is that debugging becomes more difficult: when analyzing problems you’ll not only be unsure how the programm works, but in addition, you won’t even be sure how the code that is executed looks like - since it is only generated at runtime.

This post is focusing on the last problem: debugging of metaprogrammed code.

There are two approaches to metaprogramming. One is to have as far as possible compile-time parseable code and the other is to let the code only be parsed at runtime.

As of version 1.9.2, Ruby’s Forwardable class is using the latter. The metaprogramming code in Ruby 1.8.7 looks like this:

    module_eval(<<-EOS, "(__FORWARDABLE__)", 1)
      def #{ali}(*args, &block)
        begin
          #{accessor}.__send__(:#{method}, *args, &block)
        rescue Exception
          $@.delete_if{|s| /^\\(__FORWARDABLE__\\):/ =~ s} unless Forwardable::debug
          Kernel::raise
        end
      end
    EOS
  end

As said, this has the consequence of the metaprogrammed code being completely invisible to the parser and other tools such as editors and debuggers.

This results in the following:

$ cat queue.rb
require 'rubygems'
require 'forwardable'
require 'ruby-debug'

class Queue
  extend Forwardable

  def initialize
    @q = [ ]    # prepare delegate object
  end

  # setup preferred interface, enq() and deq()...
  def_delegator :@q, :push, :enq
  def_delegator :@q, :shift, :deq

  # support some general Array methods that fit Queues well
  def_delegators :@q, :clear, :first, :push, :shift, :size
end

q = Queue.new
debugger # ------ DEBUGGING FROM HERE ON -----
q.enq 1, 2, 3, 4, 5
q.push 6

q.shift    # => 1
while q.size > 0
  puts q.deq
end

q.enq "Ruby", "Perl", "Python"
puts q.first
q.clear
puts q.first


$ ruby queue.rb

queue.rb:24
q.enq 1, 2, 3, 4, 5

(rdb:1) step
(__FORWARDABLE__):2

(rdb:1) list =
*** No sourcefile available for (__FORWARDABLE__)

(rdb:1) step
(__FORWARDABLE__):3

In other words, you are rather lost allready - otherwise you probably wouldn’t be stepping through your code - and in that situation it happens that your debugger gets completely lost as well, since it does not know any more where in the code it is and what it exactly is executing.

That’s nothing the programmer wishes for. In a situation where you are debugging you want to have a maximally clear view of all state, including what code you are currently executing.

Chaning that situation requires making as much of the metaprogrammed code visible to the parser, which is the second approach to metaprogramming mentioned previously:

$ cat forwardable2.rb
...
    self.send(:define_method, ali) do |*args,&block|
      begin
        instance_variable_get(accessor).__send__(method, *args,&block)
      rescue Exception
        $@.delete_if{|s| /^\\(__FORWARDABLE__\\):/ =~ s} unless Forwardable2::debug
        Kernel::raise
      end
    end

Note that it’s the same code as before, except that we do not do eval("string") any more, but instead are using specific, more modern metaprogramming tools provided by standard Ruby.

The result is the following:

$ ruby queue.rb

queue.rb:22
q.enq 1, 2, 3, 4, 5

(rdb:1) step
/usr/lib/ruby/1.8/forwardable2.rb:149
begin

(rdb:1) list =
[144, 153] in /usr/lib/ruby/1.8/forwardable2.rb
   144      accessor = accessor.id2name if accessor.kind_of?(Integer)
   145      method = method.id2name if method.kind_of?(Integer)
   146      ali = ali.id2name if ali.kind_of?(Integer)
   147
   148      self.send(:define_method, ali) do |*args,&block|
=> 149        begin
   150          instance_variable_get(accessor).__send__(method, *args,&block)
   151        rescue Exception
   152          $@.delete_if{|s| /^\\(__FORWARDABLE__\\):/ =~ s} unless Forwardable2::debug
   153          Kernel::raise

(rdb:1) step
/usr/lib/ruby/1.8/forwardable2.rb:150
instance_variable_get(accessor).__send__(method, *args,&block)

Allready much, much better.

Of course, with the string-eval approach to metaprogramming Ruby itself could do better by saving the string that is being evaled to be able to refer to it later at step-through time. However currently we don’t have this option.

Tomáš Pospíšek

QGIS Mobile GSoC has started

Yesterday, we had our first meeting with Marco Bernasocchi, who just started his Google Summer of Code project. The project goals are:

  • porting QGIS to the Android platform
  • adapt the QGIS GUI for tablet computers
  • write a driver for the built-in GPS
  • create a QGIS “mini” application for mobile phones

Marco Hugentobler is mentoring the project and updated information will be available on a QGIS Wiki page. We wish Marco good luck and are looking forward to a portable QGIS this year!

extending RedCloth markup

There are various approaches when trying to extend the Textile markup that RedCloth understands with own tags or syntax. Some approaches documented on the net have changed or don’t work any more, since RedCloth has been rewritten in Version 4.

Below is a fairly robust aproach, that is based on the assumption, that RedCloth leaves HTML tags inside the markup untouched and passes them on to the application consuming the translated markup.

The below code has been used in the Madek project. Madek, a Ruby On Rails application, uses irwi which provides a Wiki to Madek. And finally irwi uses RedCloth to do the rendering from Textile to HTML. That’s where we hook in.

We want to have a few special tags, which make the life of the Madek Wiki admin simpler, by letting him write the following inside the Textile markup:

[media=210      | Das Huhn]
[screenshot=210 | Das Huhn]
[video=210      | Das Huhn]

That will finally produce this HTML output:

<a href="/media_entries/210">Das Huhn</a>
<img src="/media_entries/210/image" title="Das Huhn"/>
<video src="/media_entries/210/image" title="Das Huhn"/>
  <a href='/media_entries/210'>(see video)</a>
</video>

Here’s the implementation:

class RedClothMadek

  ActionView::Base.sanitized_allowed_tags << 'video'

  def initialize
    require 'redcloth'
  end

  def format( text )
    ::RedCloth.new( replace_madek_tags(text) ).to_html
  end

  # Transforms the follwing Textile markups:
  # 
  #   [media=210      | Das Huhn] -> <a href="/media_entries/210">Das Huhn</a>
  #   [screenshot=210 | Das Huhn] -> <img src="/media_entries/210/image" title="Das Huhn"/>
  #   [video=210      | Das Huhn] -> <video src="/media_entries/210/image" title="Das Huhn"/>
  #                                    <a href='/media_entries/210'>(see video)</a>
  #                                  </video>
  #
  def replace_madek_tags( text )

    # unfortunately having multiple matches in gsub doesn't seem to work, therefore
    # we fall back to $1 $2
    #
    text.gsub(/\[\s*media\s*=\s*(\d+)\s*\|\s*([^\]]+)\s*\]/) { |number,txt|

           "<a href='/media_entries/#{$1}'>#{h($2)}</a>"                 }.

         gsub(/\[\s*screenshot\s*=\s*(\d+)\s*\|\s*([^\]]+)\s*\]/) { |number,title|

           "<img src='/media_entries/#{$1}/image' title='#{h($2)}'/>"    }.

         gsub(/\[\s*video\s*=\s*(\d+)\s*\|\s*([^\]]+)\s*\]/) { |number,title|
           "<video src='/media_entries/#{$1}/image' title='#{h($2)}'>" +
             "<a href='/media_entries/#{$1}'>(see Wideo)</a>" +
           "</video>"  }
  end

end

And finally, irwi needs to be told to use that formatter instead of RedCloth directly. From config/environment.rb:

require "#{Rails.root}/lib/red_cloth_madek.rb"
Irwi.config.formatter = RedClothMadek.new

Tomáš Pospíšek

killing deprecation warning of rubygems 1.7.x

There might be a reason to upgrade to rubygems 1.7.x, however if you have done so by misstake and find your console swamped by thousands of deprecation warnings:

NOTE: Gem::Specification#default_executable= is deprecated with no replacement. It will be removed on or after 2011-10-01.

and you’re crying desperately for help, then you might try the following very ugly hack, which you should revert at some point in time:

First look where your rubygems are installed:

$ gem list -d rubygems

Find the deprecate.rb file there, and make the def self.skip class method allways return true. That’s it. Happy hacking on. And don’t forget, that you’ll need to undo this change sometime!

Tomáš Pospíšek