With nodewrap, you can store the code as an AST:

  [pbrannan@zaphod tmp]$ cat test.rb
  def foo
    p 1 + 1
  end
  foo()
  [pbrannan@zaphod tmp]$ ruby -rnwobfusc test.rb > test2.rb
  [pbrannan@zaphod tmp]$ cat test2.rb
  require 'nodewrap'
  if RUBY_VERSION != "1.8.2" then
    $stderr.puts "Wrong Ruby version; please use 1.8.2"
    exit 1
  end
  begin_nodes = Marshal.load("\004\010[\000")
  n = Marshal.load("\004\010u:\020Node::BLOCK\002c\002\004\010[\ai\004*\255\r {\026i\004z\255\r [\ni\003? \010\"\ftest.rb[\aTi\004\336\255\r [\aTi\004p\255\r 0i\004>\255\r [\ni\003?0!\"\ftest.rb[\aFF:\010foo0i\004\254\255\r [\ni\003?(\021\"\ftest.rb[\aTi\004\312\255\r [\aTi\004\266\255\r :\006+i\004p\255\r [\ni\003? \030\"\ftest.rb[\aTi\004\216\255\r [\aFF0i\004\336\255\r [\ni\003?\020\n\"\ftest.rbi\000i\372[\aFFi\0044\255\r [\ni\003?\370\"\"\ftest.rbi\t[\aTi\004>\255\r 0i\004\242\255\r [\ni\003?P\021\"\ftest.rbi\006[\aTi\004\254\255\r [\aFFi\004f\255\r [\ni\003?\030\030\"\ftest.rb[\aFF0[\aTi\004z\255\r i\004*\255\r [\ni\003? \010\"\ftest.rb[\aTi\004R\255\r [\aTi\004 \255\r 0i\004\230\255\r [\ni\003?0\021\"\ftest.rb[\aTi\004\242\255\r :\006p0i\004\\\255\r [\ni\003?P\n\"\ftest.rb[\aTi\004f\255\r ;\000i\ai\004\312\255\r [\ni\003?\320\021\"\ftest.rb[\aFi\00600i\004 \255\r [\ni\003?  \"\ftest.rb[\aTi\0044\255\r [\aFF0i\004\216\255\r [\ni\003?\370\022\"\ftest.rbi\a[\aTi\004\230\255\r 0i\004R\255\r [\ni\003?\370\n\"\ftest.rbi\006[\aTi\004\\\255\r 0i\004\300\255\r [\ni\003?\320\021\"\ftest.rb[\aFi\00600i\004\266\255\r [\ni\003?P\021\"\ftest.rbi\006[\aTi\004\300\255\r [\aFF")
  begin_nodes.each do |node|
    node.eval(self)
  end
  n.eval(self)
  [pbrannan@zaphod tmp]$ ruby test2.rb
  2

The process is still somewhat reversible, but afaik no one has written a
tool yet to do this.

I should also note that the code is still experimental, and you should test
your code thoroughly after transforming it this way.

Paul