Metaprogramming
    in Ruby
Things I Wish I Knew
When I Started Ruby
Who?
Joshua Hull
@jj
https://github.com/joshbuddy
What?
What?


Writing programs that write programs.
What?


Writing programs that write programs.
        NOT code generation!
Why?
We all do it.
Why?
           We all do it.
Ruby   attr_accessor :my_attribute
Why?
           We all do it.
Ruby   attr_accessor :my_attribute



       def my_attribute
         @my_attribute
       end

       def my_attribute=(my_attribute)
         @my_attribute = my_attribute
       end
Why?
           We all do it.
Ruby   attr_accessor :my_attribute



Java   public int getAttribute() {
         return attribute;
       }

       public void setAttribute(int newAttribute) {
         attribute = newAttribute;
       }
Why?
               We all do it.
   ner
 in Ruby
W          attr_accessor :my_attribute



    Java   public int getAttribute() {
             return attribute;
           }

           public void setAttribute(int newAttribute) {
             attribute = newAttribute;
           }
Why?
def age
  @age || 'not set'
end

def gender
  @gender || 'not set'
end

def name
  @name || 'not set'
end
Why?
              def age
                @age || 'not set'
              end

              def gender
                @gender || 'not set'
              end

              def name
                @name || 'not set'
              end




[:age, :gender, :name].each do |attr|
  define_method(attr) do
    instance_variable_get(:"@#{attr}") || 'not set'
  end
end
Drawbacks
Drawbacks
You can write some difficult-to-understand code.
Drawbacks
You can write some difficult-to-understand code.
Method dispatch
Method dispatch

   class MyObject
     attr_accessor :name

     def say_hello
       puts "hello"
       puts name
     end
   end
Method dispatch

   class MyObject
     attr_accessor :name

     def say_hello
       puts "hello"
       puts name
     end
   end                     This is a
                          method call,
                      but who receives it?
Method dispatch

   class MyObject
     attr_accessor :name

     def say_hello
       puts "hello"
       puts self.name
     end
   end
Method dispatch

   class MyObject
     attr_accessor :name

     def say_hello
       puts "hello"
       puts self.name
     end
   end
                           self is always the
                            implied receiver!
Method dispatch

   class MyObject
     attr_accessor :name

     def say_hello
       puts "hello"
       puts name
     end
   end
Method dispatch

  class MyObject
    self.attr_accessor :name

    def say_hello
      puts "hello"
      puts name
    end
  end
Method dispatch

              class MyObject

Who is self     self.attr_accessor :name

 here?          def say_hello
                  puts "hello"
                  puts name
                end
              end
Method dispatch

              class MyObject

Who is self     self.attr_accessor :name

  here?         def say_hello
                  puts "hello"
The class         puts name
                end
   is!        end
Method dispatch
              Module#attr

Who is self
  here?
The class
   is!
Ruby classes
Ruby classes
class NewClass
  def hey
    puts 'hello!'
  end
end
Ruby classes
                 class NewClass


             {
                   def hey
                     puts 'hello!'
                   end
                 end
This is normal
    code!
Ruby classes
class NewClass
  def hey
    puts 'hello!'
  end
end

     is the same as

NewClass = Class.new do
  def hey
    puts 'hello!'
  end
end
Ruby classes

class ParsingError < RuntimeError
end
Ruby classes

class ParsingError < RuntimeError
end

            is the same as


ParsingError = Class.new(RuntimeError)
Ruby classes
def class_with_accessors(*attributes)
  Class.new do
    attr_accessor *attributes
  end
end
Ruby classes
def class_with_accessors(*attributes)
  Class.new do
    attr_accessor *attributes
  end
end

                     Returns a new class!
Ruby classes
def class_with_accessors(*attributes)
  Class.new do
    attr_accessor *attributes
  end
end

                     Returns a new class!

class Person < class_with_accessors(:name, :age, :sex)
  # ...
end
eval, instance_eval,
     class_eval
eval, instance_eval,
        class_eval
Method
eval

instance_eval

class_eval
eval, instance_eval,
        class_eval
Method          Context
eval            your current context

instance_eval   the object

class_eval      the object’s class
eval, instance_eval,
       class_eval
eval
eval "puts 'hello'"

# hello
eval, instance_eval,
        class_eval
instance_eval
class MyClass
end

MyClass.instance_eval("def hi; 'hi'; end")
eval, instance_eval,
        class_eval
instance_eval
class MyClass
end

MyClass.instance_eval("def hi; 'hi'; end")



MyClass.hi
# 'hi'
eval, instance_eval,
        class_eval
instance_eval
class MyClass
end

MyClass.instance_eval("def hi; 'hi' end")



obj = MyClass.new
# <MyClass:0x10178aff8>
obj.hi
# NoMethodError: undefined method `hi' for #<MyClass>
eval, instance_eval,
        class_eval
class_eval
class MyClass
end

MyClass.class_eval("def hi; 'hi' end")
eval, instance_eval,
        class_eval
class_eval
class MyClass
end

MyClass.class_eval("def hi; 'hi' end")


MyClass.hi
# NoMethodError: undefined method `hi' for MyClass:Class
eval, instance_eval,
        class_eval
class_eval
class MyClass
end

MyClass.class_eval("def hi; 'hi' end")


obj = MyClass.new
# <MyClass:0x101849688>
obj.hi
# "hi"
eval, instance_eval,
                     class_eval
class_eval                                instance_eval
class MyClass                             class MyClass
end                                       end

MyClass.class_eval("def hi; 'hi'; end")   MyClass.instance_eval("def hi; 'hi'; end")


obj = MyClass.new                         MyClass.hi
# <MyClass:0x101849688>                   # 'hi'
obj.hi
# "hi"
eval, instance_eval,
       class_eval

Ninja’s will attack you if ...
you don’t use __FILE__, __LINE__
eval, instance_eval,
               class_eval
class HiThere
end

HiThere.class_eval "def hi; raise; end"
HiThere.class_eval "def hi_with_niceness; raise; end", __FILE__, __LINE__

HiThere.new.hi
HiThere.new.hi_with_niceness
eval, instance_eval,
               class_eval
class HiThere
end

HiThere.class_eval "def hi; raise; end"
HiThere.class_eval "def hi_with_niceness; raise; end", __FILE__, __LINE__

HiThere.new.hi
HiThere.new.hi_with_niceness


(eval):1:in `hi': unhandled exception
  from my_file.rb:7


my_file.rb:5:in `hi_with_niceness': unhandled exception
  from my_file.rb:7
eval, instance_eval,
               class_eval
class HiThere
end

HiThere.class_eval "def hi; raise; end"
HiThere.class_eval "def hi_with_niceness; raise; end", __FILE__, __LINE__

HiThere.new.hi
HiThere.new.hi_with_niceness


(eval):1:in `hi': unhandled exception
  from my_file.rb:7
                                              So nice. <3

my_file.rb:5:in `hi_with_niceness': unhandled exception
  from my_file.rb:7
eval, instance_eval,
     class_eval

  Implement attr_accessor!
eval, instance_eval,
           class_eval
class Module
  def create_attr(attribute)
    class_eval("def #{attribute}; @#{attribute}; end")
  end
end
eval, instance_eval,
           class_eval
class Module
  def create_attr(attribute)
    class_eval("def #{attribute}; @#{attribute}; end")
  end
end

class M
  create_attr :hi
end
eval, instance_eval,
           class_eval
class Module
  def create_attr(attribute)
    class_eval("def #{attribute}; @#{attribute}; end")
  end
end
                                   def M
class M                              def hi
  create_attr :hi                      @hi
end                                  end
                                   end
Defining methods
Defining methods
              For an object
o = Object.new
o.instance_eval("def just_this_object; end")
o.just_this_object
Defining methods
              For an object
o = Object.new
o.instance_eval("def just_this_object; end")
o.just_this_object


Object.new.just_this_object
# NoMethodError: undefined method `just_this_object'
Defining methods
              For an object
o = Object.new
o.instance_eval("def just_this_object; end")
o.just_this_object


Object.new.just_this_object
# NoMethodError: undefined method `just_this_object'


o = Object.new
o.instance_eval {
  def just_this_object
  end
}
Defining methods
      For an object

   o = Object.new
   o.extend(Module.new {
      def just_this_object
      end
   })
Defining methods
               For a class

MyClass = Class.new


class MyClass
  def new_method
  end
end


MyClass.new.respond_to?(:new_method) # true
Defining methods
                For a class

MyClass = Class.new

MyClass.class_eval "     MyClass.class_eval do
  def new_method           def new_method
  end                      end
"                        end


MyClass.send(:define_method, :new_method) {
  # your method body
}
Defining methods
                      Aliasing
Module#alias_method
Scoping
Scoping
module Project
  class Main
    def run
      # ...
    end
  end
end
Scoping
   module Project
     class Main
       def run
         # ...
       end
     end
   end


 Class definitions
Module definitions
Method definitions
Scoping
a = 'hello'

module Project
  class Main
    def run
      puts a
    end
  end
end

Project::Main.new.run
Scoping
                       a = 'hello'

                       module Project
                         class Main
                           def run
                             puts a
                           end
                         end
                       end

                        Project::Main.new.run

# undefined local variable or method `a' for #<Project::Main> (NameError)
Scoping
  module Project
    class Main
    end
  end

  a = 'hello'

  Project::Main.class_eval do
    define_method(:run) do
      puts a
    end
  end



Project::Main.new.run # => hello
Scoping
Example: Connection Sharing
module AddConnections
  def self.add_connection_methods(cls, host, port)
    cls.class_eval do
      define_method(:get_connection) do
        puts "Getting connection for #{host}:#{port}"
      end
      define_method(:host) { host }
      define_method(:port) { port }
    end
  end
end
Scoping
Example: Connection Sharing
module AddConnections
  def self.add_connection_methods(cls, host, port)
    cls.class_eval do
      define_method(:get_connection) do
        puts "Getting connection for #{host}:#{port}"
      end
      define_method(:host) { host }
      define_method(:port) { port }
    end
  end
end

Client = Class.new
AddConnections.add_connection_methods(Client, 'localhost', 8080)

Client.new.get_connection # Getting connection for localhost:8080
Client.new.host           # localhost
Client.new.port           # 8080
Scoping
Kernel#binding
Let’s you leak the current “bindings”
Scoping
Kernel#binding
Let’s you leak the current “bindings”

                def create_connection(bind)
                  eval '
                    connection = "I am a connection"
                  ', bind
                end

                connection = nil
                create_connection(binding)
                connection # => I am a connection
Scoping
Kernel#binding
Let’s you leak the current “bindings”

                def create_connection(bind)
                  eval '
                    connection = "I am a connection"
                  ', bind
                end

 Calls          connection = nil
with the        create_connection(binding)
current         connection # => I am a connection
 state
Scoping
Kernel#binding
Let’s you leak the current “bindings”

                def create_connection(bind)
                  eval '
                    connection = "I am a connection"
                  ', bind
                end

 Calls          connection = nil
with the        create_connection(binding)
current         connection # => I am a connection
 state
                                        MAGIC!
Scoping
Kernel#binding
Let’s you leak the current “bindings”

       def create_connection(bind)
         eval '
           connection = "I am a connection"
         ', bind
       end

       # connection = nil
       create_connection(binding)
       connection
       # undefined local variable or method `connection'
Scoping
Kernel#binding
Let’s you leak the current “bindings”

       def create_connection(bind)
         eval '
           connection = "I am a connection"
         ', bind
       end

       # connection = nil
       create_connection(binding)
       connection
       # undefined local variable or method `connection'

       You can’t add to the local variables via binding
Scoping
Kernel#binding
Let’s you leak the current “bindings”

       def create_connection(bind)
         eval '
           connection = "I am a connection"
         ', bind
       end

       eval "connection = nil"
       create_connection(binding)
       connection
       # undefined local variable or method `connection'

         You can’t add to the local variables via eval
Scoping
Kernel#binding
TOPLEVEL_BINDING
Scoping
Kernel#binding
TOPLEVEL_BINDING
       a = 'hello'

       module Program
         class Main
           def run
             puts eval("a", TOPLEVEL_BINDING)
           end
         end
       end

       Program::Main.new.run # => hello
Interception!
(aka lazy magic)
Interception!
method_missing(method, *args, &blk)
Interception!
method_missing(method, *args, &blk)
class MethodMissing
  def method_missing(m, *args, &blk)
    puts "method_missing #{m} #{args.inspect} #{blk.inspect}"
    super
  end
end
Interception!
method_missing(method, *args, &blk)
class MethodMissing
  def method_missing(m, *args, &blk)
    puts "method_missing #{m} #{args.inspect} #{blk.inspect}"
    super
  end
end

mm = MethodMissing.new
mm.i_dont_know_this(1, 2, 3)
# method_missing i_dont_know_this [1, 2, 3] nil
# NoMethodError: undefined method `i_dont_know_this' for #<MethodMissing>
Interception!
Example: Timing
module MethodsWithTiming
  def method_missing(m, *args, &blk)
    if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and respond_to?(timed_method)
      respond = nil
      measurement = Benchmark.measure {
        respond = send(timed_method, *args, &blk)
      }
      puts "Method #{m} took #{measurement}"
      respond
    else
      super
    end
  end
end
Interception!
  Example: Timing
   module MethodsWithTiming
     def method_missing(m, *args, &blk)
       if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and respond_to?(timed_method)
         respond = nil
         measurement = Benchmark.measure {
           respond = send(timed_method, *args, &blk)
         }
         puts "Method #{m} took #{measurement}"
         respond
       else
         super
       end
     end
   end

                              sc = SlowClass.new
class SlowClass
                              sc.slow
  include MethodsWithTiming
  def slow
    sleep 1
                              sc.slow_with_timing
  end
                              # Method slow_with_timing took   0.000000   0.000000   0.000000 (   1.000088)
end
Interception!
Example: Proxy
class Proxy
  def initialize(backing)
    @backing = backing
  end

  def method_missing(m, *args, &blk)
    @backing.send(m, *args, &blk)
  end
end
Interception!
Example: Proxy
class LoggingProxy
  def initialize(backing)
    @backing = backing
  end

  def method_missing(m, *args, &blk)
    puts "Calling method #{m} with #{args.inspect}"
    @backing.send(m, *args, &blk)
  end
end
Interception!
Example: Simple DSL
 class NameCollector
   attr_reader :names
   def initialize
     @names = []
   end

   def method_missing(method, *args, &blk)
     args.empty? ? @names.push(method.to_s.capitalize) : super
   end
 end

 nc = NameCollector.new
 nc.josh
 nc.bob
 nc.jane
 nc.names.join(' ') # => Josh Bob Jane
Interception!
Object#respond_to?(sym)
Interception!
   Object#respond_to?(sym)
   Example: Timing
module MethodsWithTiming
  alias_method :original_respond_to?, :respond_to?

  def method_missing(m, *args, &blk)
    if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and original_respond_to?(timed_method)
      respond = nil
      measurement = Benchmark.measure {
        respond = send(timed_method, *args, &blk)
      }
      puts "Method #{m} took #{measurement}"
      respond
    else
      super
    end
  end

  def respond_to?(sym)
    (timed_method = sym.to_s[/^(.*)_with_timing$/, 1]) ?
      original_respond_to?(timed_method.to_sym) :
      original_respond_to?(sym)
  end
end
Interception!
   Object#respond_to?(sym)
   Example: Timing
module MethodsWithTiming




                                       ge  ts
  alias_method :original_respond_to?, :respond_to?




                                    It
  def method_missing(m, *args, &blk)




                                             r!
    if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and original_respond_to?(timed_method)




                                          te
      respond = nil



                                       et
      measurement = Benchmark.measure {



                                     b
        respond = send(timed_method, *args, &blk)
      }
      puts "Method #{m} took #{measurement}"
      respond
    else
      super
    end
  end

  def respond_to?(sym)
    (timed_method = sym.to_s[/^(.*)_with_timing$/, 1]) ?
      original_respond_to?(timed_method.to_sym) :
      original_respond_to?(sym)
  end
end
Interception!
   Object#respond_to_missing?(sym) (1.9 only)
   Example: Timing
module MethodsWithTiming
  def method_missing(m, *args, &blk)
    if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and respond_to?(timed_method)
      respond = nil
      measurement = Benchmark.measure {
        respond = send(timed_method, *args, &blk)
      }
      puts "Method #{m} took #{measurement}"
      respond
    else
      super
    end
  end

  def respond_to_missing?(sym)
    (timed_method = sym.to_s[/^(.*)_with_timing$/, 1]) ?
      respond_to?(timed_method.to_sym) :
      super
  end
end
Interception!
const_missing(sym)
Interception!
const_missing(sym)
MyClass::MyOtherClass

# MyClass.const_missing(:MyOtherClass)
Interception!
const_missing(sym)
MyClass::MyOtherClass

# MyClass.const_missing(:MyOtherClass)

Example: Loader
class Loader
  def self.const_missing(sym)
    file = File.join(File.dirname(__FILE__), "#{sym.to_s.downcase}.rb")
    if File.exist?(file)
      require file
      Object.const_defined?(sym) ? Object.const_get(sym) : super
    else
      puts "can't find #{file}, sorry!"
      super
    end
  end
end
Interception!
Example: Loader
class Loader
  def self.const_missing(sym)
    file = File.join(File.dirname(__FILE__), "#{sym.to_s.downcase}.rb")
    if File.exist?(file)
      require file
      Object.const_defined?(sym) ? Object.const_get(sym) : super
    else
      puts "can't find #{file}, sorry!"
      super
    end
  end
end
Interception!
Example: Loader
class Loader
  def self.const_missing(sym)
    file = File.join(File.dirname(__FILE__), "#{sym.to_s.downcase}.rb")
    if File.exist?(file)
      require file
      Object.const_defined?(sym) ? Object.const_get(sym) : super
    else
      puts "can't find #{file}, sorry!"
      super
    end
  end
end


Loader::Auto
# can't find ./auto.rb, sorry!
# NameError: uninitialized constant Loader::Auto

# or, if you have an ./auto.rb
Loader::Auto
# => Auto
Callbacks
Callbacks
Module#method_added
Callbacks
Module#method_added
Callbacks
Module#method_added
class MyClass
  def self.method_added(m)
    puts "adding #{m}"
  end

  puts "defining my method"
  def my_method
    'two'
  end
  puts "done defining my method"
end
Callbacks
Module#method_added
class MyClass                      defining my method
  def self.method_added(m)         adding my_method
    puts "adding #{m}"             done defining my method
  end

  puts "defining my method"
  def my_method
    'two'
  end
  puts "done defining my method"
end
Callbacks
Module#method_added
Example: Thor!
class Tasks
  def self.desc(desc)
    @desc = desc
  end

  def self.method_added(m)
    (@method_descs ||= {})[m] = @desc
    @desc = nil
  end

  def self.method_description(m)
    method_defined?(m) ?
      @method_descs[m] || "This action isn't documented" :
      "This action doesn't exist"
  end

 desc "Start server"
 def start
 end

  def stop
  end
end
Callbacks
Module#method_added
Example: Thor!                                               Record the description
class Tasks
  def self.desc(desc)
    @desc = desc
                                                             When a method is added,
  end                                                        record the description associated
  def self.method_added(m)                                   with that method
    (@method_descs ||= {})[m] = @desc
    @desc = nil
  end                                                        Provide the description for a
  def self.method_description(m)
                                                             method, or, if not found, some
    method_defined?(m) ?                                     default string.
      @method_descs[m] || "This action isn't documented" :
      "This action doesn't exist"
  end

 desc "Start server"
 def start
 end

  def stop
  end
end
Callbacks
Module#method_added
Example: Thor!                                               Record the description
class Tasks
  def self.desc(desc)
    @desc = desc
                                                             When a method is added,
  end                                                        record the description associated
  def self.method_added(m)                                   with that method
    (@method_descs ||= {})[m] = @desc
    @desc = nil
  end                                                        Provide the description for a
  def self.method_description(m)
                                                             method, or, if not found, some
    method_defined?(m) ?                                     default string.
      @method_descs[m] || "This action isn't documented" :
      "This action doesn't exist"
  end

 desc "Start server"
 def start
 end
                        Described!
  def stop
  end
end
Callbacks
Module#method_added
Example: Thor!                                               Query your methods!
class Tasks                                                  puts   Tasks.method_description(:start)
  def self.desc(desc)                                        # =>   Start server
    @desc = desc                                             puts   Tasks.method_description(:stop)
  end
                                                             # =>   This action isn't documented
  def self.method_added(m)                                   puts   Tasks.method_description(:restart)
    (@method_descs ||= {})[m] = @desc                        # =>   This action doesn't exist
    @desc = nil
  end

  def self.method_description(m)
    method_defined?(m) ?
      @method_descs[m] || "This action isn't documented" :
      "This action doesn't exist"
  end

 desc "Start server"
 def start
 end

  def stop
  end
end
Callbacks
Object#singleton_method_added
Callbacks
Object#singleton_method_added
class ClassWithMethods
  def self.singleton_method_added(m)
    puts "ADDING! #{m}"
  end

  def self.another
  end
end
Callbacks
Object#singleton_method_added
class ClassWithMethods
  def self.singleton_method_added(m)
    puts "ADDING! #{m}"
  end

  def self.another
  end
end

# ADDING! singleton_method_added
# ADDING! another
Callbacks
Object#singleton_method_added
class ClassWithMethods
  def self.singleton_method_added(m)
    puts "ADDING! #{m}"
  end

  def self.another
  end
end
                                       Holy meta!
# ADDING! singleton_method_added
# ADDING! another
Callbacks
Module#included
module Logger
  def self.included(m)
    puts "adding logging to #{m}"
  end
end

class Server
  include Logger
end


# adding logging to Server
Callbacks
Module#included
Example: ClassMethods pattern
module Logger                       class Server
  def self.included(m)                include Logger
    puts "adding logging to #{m}"
  end                                 def self.create
                                        log("Creating server!")
  def self.log(message)               end
    puts "LOG: #{message}"          end
  end
end


Server.create
# `create': undefined method `log' for Server:Class (NoMethodError)
Callbacks
Module#included
Example: ClassMethods pattern
module Logger                  class Server
  def self.included(m)           include Logger
    m.extend(ClassMethods)
  end                            def self.create
                                   log("Creating server!")
  module ClassMethods            end
    def log(message)           end
      puts "LOG: #{message}"
    end
  end
end

Server.create
# LOG: Creating server!
Callbacks
Module#extended
module One
  def self.extended(obj)
    puts "#{self} has been extended by #{obj}"
  end
end

Object.new.extend(One)



# One has been extended by #<Object:0x1019614a8>
Callbacks
Class#inherited
class Parent
  def self.inherited(o)
    puts "#{self} was inherited by #{o}"
  end
end

class Child < Parent
end


# Parent was inherited by Child
Callbacks
Guarding callbacks
Module#append_features         include
Module#extend_object           extend
 def self.extend_object(o)
   super
 end

 def self.append_features(o)
   super
 end
Callbacks
Guarding callbacks
Module#append_features                     include
Module#extend_object                       extend
def self.append_features(o)
  o.instance_method(:<=>) ? super : warn('you no can uze')
end
Callbacks
Kernel#caller
def one
  two
end

def two
                                                   method name
  three
                 file name            line              (optional)
end

def three
  p caller
end

# ["method.rb:156:in `two'", "method.rb:152:in `one'", "method.rb:163"]




             https://github.com/joshbuddy/callsite
Callbacks
Module#nesting
module A
  module B
    module C
      p Module.nesting
    end
  end
end

# [A::B::C, A::B, A]
There and back again, a
     parsing tale
There and back again, a
     parsing tale

   gem install ruby_parser
   gem install sexp_processor
   gem install ruby2ruby


          Let’s go!
There and back again, a
               parsing tale
Parsing
            require 'rubygems'
            require 'ruby_parser'
            RubyParser.new.process("'string'")



                     s(:str, "string")



              Type             Arguments...
There and back again, a
               parsing tale
Parsing
            require 'rubygems'
            require 'ruby_parser'
            RubyParser.new.process("'string'")




                s(:str, "string")
                [:str, "string"]    # Sexp

                Sexp.superclass
                # Array
There and back again, a
                parsing tale
Parsing

          RubyParser.new.process("'string' + 'string'")




s(:call, s(:str, "string"), :+, s(:arglist, s(:str, "string")))

 Method                    Method
               Receiver                   Arguments
  call                      name
There and back again, a
                parsing tale
Parsing

          RubyParser.new.process("'string' + 'string'")




  s(:call, nil, :puts, s(:arglist, s(:str, "hello world")))

  Method    Receiver   Method
                                     Arguments
   call                 name
There and back again, a
              parsing tale
And, back again...

      require 'rubygems'
      require 'ruby2ruby'

      Ruby2Ruby.new.process [:str, "hello"] # => "hello"
There and back again, a
              parsing tale
And, back again...

      require 'rubygems'
      require 'ruby2ruby'

      Ruby2Ruby.new.process [:str, "hello"] # => "hello"
      Ruby2Ruby.new.process [:lit, :symbol] # => :symbol
There and back again, a
                parsing tale
Roundtrip
 require 'sexp_processor'
 require 'ruby2ruby'
 require 'ruby_parser'

 class JarJarify < SexpProcessor
   def initialize
     self.strict = false
     super
   end

   def process_str(str)
     new_string = "YOUZA GONNA SAY #{str[-1]}"
     str.clear
     s(:str, new_string)
   end
 end
There and back again, a
               parsing tale
Roundtrip
 class JarJarify < SexpProcessor
   def initialize
     self.strict = false
     super
   end

   def process_str(str)
     new_string = "YOUZA GONNA SAY #{str[-1]}"
     str.clear
     s(:str, new_string)
   end
 end

 ast = RubyParser.new.process('puts "hello"')
 Ruby2Ruby.new.process(JarJarify.new.process(ast))
 # => puts("YOUZA GONNA SAY hello")
There and back again, a
               parsing tale
Roundtrip
 class JarJarify < SexpProcessor
   def initialize
     self.strict = false
                                                 Process type :str
     super
   end

   def process_str(str)
     new_string = "YOUZA GONNA SAY #{str[-1]}"
     str.clear
     s(:str, new_string)            Consume the current sexp
   end
 end                                Return a new one
 ast = RubyParser.new.process('puts "hello"')
 Ruby2Ruby.new.process(JarJarify.new.process(ast))
 # => puts("YOUZA GONNA SAY hello")
IT’S OVER!

Metaprogramming

  • 1.
  • 2.
    Things I WishI Knew When I Started Ruby
  • 5.
  • 6.
  • 7.
  • 8.
    What? Writing programs thatwrite programs. NOT code generation!
  • 9.
  • 10.
    Why? We all do it. Ruby attr_accessor :my_attribute
  • 11.
    Why? We all do it. Ruby attr_accessor :my_attribute def my_attribute @my_attribute end def my_attribute=(my_attribute) @my_attribute = my_attribute end
  • 12.
    Why? We all do it. Ruby attr_accessor :my_attribute Java public int getAttribute() { return attribute; } public void setAttribute(int newAttribute) { attribute = newAttribute; }
  • 13.
    Why? We all do it. ner in Ruby W attr_accessor :my_attribute Java public int getAttribute() { return attribute; } public void setAttribute(int newAttribute) { attribute = newAttribute; }
  • 14.
    Why? def age @age || 'not set' end def gender @gender || 'not set' end def name @name || 'not set' end
  • 15.
    Why? def age @age || 'not set' end def gender @gender || 'not set' end def name @name || 'not set' end [:age, :gender, :name].each do |attr| define_method(attr) do instance_variable_get(:"@#{attr}") || 'not set' end end
  • 16.
  • 17.
    Drawbacks You can writesome difficult-to-understand code.
  • 18.
    Drawbacks You can writesome difficult-to-understand code.
  • 19.
  • 20.
    Method dispatch class MyObject attr_accessor :name def say_hello puts "hello" puts name end end
  • 21.
    Method dispatch class MyObject attr_accessor :name def say_hello puts "hello" puts name end end This is a method call, but who receives it?
  • 22.
    Method dispatch class MyObject attr_accessor :name def say_hello puts "hello" puts self.name end end
  • 23.
    Method dispatch class MyObject attr_accessor :name def say_hello puts "hello" puts self.name end end self is always the implied receiver!
  • 24.
    Method dispatch class MyObject attr_accessor :name def say_hello puts "hello" puts name end end
  • 25.
    Method dispatch class MyObject self.attr_accessor :name def say_hello puts "hello" puts name end end
  • 26.
    Method dispatch class MyObject Who is self self.attr_accessor :name here? def say_hello puts "hello" puts name end end
  • 27.
    Method dispatch class MyObject Who is self self.attr_accessor :name here? def say_hello puts "hello" The class puts name end is! end
  • 28.
    Method dispatch Module#attr Who is self here? The class is!
  • 29.
  • 30.
    Ruby classes class NewClass def hey puts 'hello!' end end
  • 31.
    Ruby classes class NewClass { def hey puts 'hello!' end end This is normal code!
  • 32.
    Ruby classes class NewClass def hey puts 'hello!' end end is the same as NewClass = Class.new do def hey puts 'hello!' end end
  • 33.
  • 34.
    Ruby classes class ParsingError< RuntimeError end is the same as ParsingError = Class.new(RuntimeError)
  • 35.
    Ruby classes def class_with_accessors(*attributes) Class.new do attr_accessor *attributes end end
  • 36.
    Ruby classes def class_with_accessors(*attributes) Class.new do attr_accessor *attributes end end Returns a new class!
  • 37.
    Ruby classes def class_with_accessors(*attributes) Class.new do attr_accessor *attributes end end Returns a new class! class Person < class_with_accessors(:name, :age, :sex) # ... end
  • 38.
  • 39.
    eval, instance_eval, class_eval Method eval instance_eval class_eval
  • 40.
    eval, instance_eval, class_eval Method Context eval your current context instance_eval the object class_eval the object’s class
  • 41.
    eval, instance_eval, class_eval eval eval "puts 'hello'" # hello
  • 42.
    eval, instance_eval, class_eval instance_eval class MyClass end MyClass.instance_eval("def hi; 'hi'; end")
  • 43.
    eval, instance_eval, class_eval instance_eval class MyClass end MyClass.instance_eval("def hi; 'hi'; end") MyClass.hi # 'hi'
  • 44.
    eval, instance_eval, class_eval instance_eval class MyClass end MyClass.instance_eval("def hi; 'hi' end") obj = MyClass.new # <MyClass:0x10178aff8> obj.hi # NoMethodError: undefined method `hi' for #<MyClass>
  • 45.
    eval, instance_eval, class_eval class_eval class MyClass end MyClass.class_eval("def hi; 'hi' end")
  • 46.
    eval, instance_eval, class_eval class_eval class MyClass end MyClass.class_eval("def hi; 'hi' end") MyClass.hi # NoMethodError: undefined method `hi' for MyClass:Class
  • 47.
    eval, instance_eval, class_eval class_eval class MyClass end MyClass.class_eval("def hi; 'hi' end") obj = MyClass.new # <MyClass:0x101849688> obj.hi # "hi"
  • 48.
    eval, instance_eval, class_eval class_eval instance_eval class MyClass class MyClass end end MyClass.class_eval("def hi; 'hi'; end") MyClass.instance_eval("def hi; 'hi'; end") obj = MyClass.new MyClass.hi # <MyClass:0x101849688> # 'hi' obj.hi # "hi"
  • 49.
    eval, instance_eval, class_eval Ninja’s will attack you if ... you don’t use __FILE__, __LINE__
  • 50.
    eval, instance_eval, class_eval class HiThere end HiThere.class_eval "def hi; raise; end" HiThere.class_eval "def hi_with_niceness; raise; end", __FILE__, __LINE__ HiThere.new.hi HiThere.new.hi_with_niceness
  • 51.
    eval, instance_eval, class_eval class HiThere end HiThere.class_eval "def hi; raise; end" HiThere.class_eval "def hi_with_niceness; raise; end", __FILE__, __LINE__ HiThere.new.hi HiThere.new.hi_with_niceness (eval):1:in `hi': unhandled exception from my_file.rb:7 my_file.rb:5:in `hi_with_niceness': unhandled exception from my_file.rb:7
  • 52.
    eval, instance_eval, class_eval class HiThere end HiThere.class_eval "def hi; raise; end" HiThere.class_eval "def hi_with_niceness; raise; end", __FILE__, __LINE__ HiThere.new.hi HiThere.new.hi_with_niceness (eval):1:in `hi': unhandled exception from my_file.rb:7 So nice. <3 my_file.rb:5:in `hi_with_niceness': unhandled exception from my_file.rb:7
  • 53.
    eval, instance_eval, class_eval Implement attr_accessor!
  • 54.
    eval, instance_eval, class_eval class Module def create_attr(attribute) class_eval("def #{attribute}; @#{attribute}; end") end end
  • 55.
    eval, instance_eval, class_eval class Module def create_attr(attribute) class_eval("def #{attribute}; @#{attribute}; end") end end class M create_attr :hi end
  • 56.
    eval, instance_eval, class_eval class Module def create_attr(attribute) class_eval("def #{attribute}; @#{attribute}; end") end end def M class M def hi create_attr :hi @hi end end end
  • 57.
  • 58.
    Defining methods For an object o = Object.new o.instance_eval("def just_this_object; end") o.just_this_object
  • 59.
    Defining methods For an object o = Object.new o.instance_eval("def just_this_object; end") o.just_this_object Object.new.just_this_object # NoMethodError: undefined method `just_this_object'
  • 60.
    Defining methods For an object o = Object.new o.instance_eval("def just_this_object; end") o.just_this_object Object.new.just_this_object # NoMethodError: undefined method `just_this_object' o = Object.new o.instance_eval { def just_this_object end }
  • 61.
    Defining methods For an object o = Object.new o.extend(Module.new { def just_this_object end })
  • 62.
    Defining methods For a class MyClass = Class.new class MyClass def new_method end end MyClass.new.respond_to?(:new_method) # true
  • 63.
    Defining methods For a class MyClass = Class.new MyClass.class_eval " MyClass.class_eval do def new_method def new_method end end " end MyClass.send(:define_method, :new_method) { # your method body }
  • 64.
    Defining methods Aliasing Module#alias_method
  • 65.
  • 66.
    Scoping module Project class Main def run # ... end end end
  • 67.
    Scoping module Project class Main def run # ... end end end Class definitions Module definitions Method definitions
  • 68.
    Scoping a = 'hello' moduleProject class Main def run puts a end end end Project::Main.new.run
  • 69.
    Scoping a = 'hello' module Project class Main def run puts a end end end Project::Main.new.run # undefined local variable or method `a' for #<Project::Main> (NameError)
  • 70.
    Scoping moduleProject class Main end end a = 'hello' Project::Main.class_eval do define_method(:run) do puts a end end Project::Main.new.run # => hello
  • 71.
    Scoping Example: Connection Sharing moduleAddConnections def self.add_connection_methods(cls, host, port) cls.class_eval do define_method(:get_connection) do puts "Getting connection for #{host}:#{port}" end define_method(:host) { host } define_method(:port) { port } end end end
  • 72.
    Scoping Example: Connection Sharing moduleAddConnections def self.add_connection_methods(cls, host, port) cls.class_eval do define_method(:get_connection) do puts "Getting connection for #{host}:#{port}" end define_method(:host) { host } define_method(:port) { port } end end end Client = Class.new AddConnections.add_connection_methods(Client, 'localhost', 8080) Client.new.get_connection # Getting connection for localhost:8080 Client.new.host # localhost Client.new.port # 8080
  • 73.
    Scoping Kernel#binding Let’s you leakthe current “bindings”
  • 74.
    Scoping Kernel#binding Let’s you leakthe current “bindings” def create_connection(bind) eval ' connection = "I am a connection" ', bind end connection = nil create_connection(binding) connection # => I am a connection
  • 75.
    Scoping Kernel#binding Let’s you leakthe current “bindings” def create_connection(bind) eval ' connection = "I am a connection" ', bind end Calls connection = nil with the create_connection(binding) current connection # => I am a connection state
  • 76.
    Scoping Kernel#binding Let’s you leakthe current “bindings” def create_connection(bind) eval ' connection = "I am a connection" ', bind end Calls connection = nil with the create_connection(binding) current connection # => I am a connection state MAGIC!
  • 77.
    Scoping Kernel#binding Let’s you leakthe current “bindings” def create_connection(bind) eval ' connection = "I am a connection" ', bind end # connection = nil create_connection(binding) connection # undefined local variable or method `connection'
  • 78.
    Scoping Kernel#binding Let’s you leakthe current “bindings” def create_connection(bind) eval ' connection = "I am a connection" ', bind end # connection = nil create_connection(binding) connection # undefined local variable or method `connection' You can’t add to the local variables via binding
  • 79.
    Scoping Kernel#binding Let’s you leakthe current “bindings” def create_connection(bind) eval ' connection = "I am a connection" ', bind end eval "connection = nil" create_connection(binding) connection # undefined local variable or method `connection' You can’t add to the local variables via eval
  • 80.
  • 81.
    Scoping Kernel#binding TOPLEVEL_BINDING a = 'hello' module Program class Main def run puts eval("a", TOPLEVEL_BINDING) end end end Program::Main.new.run # => hello
  • 82.
  • 83.
  • 84.
    Interception! method_missing(method, *args, &blk) classMethodMissing def method_missing(m, *args, &blk) puts "method_missing #{m} #{args.inspect} #{blk.inspect}" super end end
  • 85.
    Interception! method_missing(method, *args, &blk) classMethodMissing def method_missing(m, *args, &blk) puts "method_missing #{m} #{args.inspect} #{blk.inspect}" super end end mm = MethodMissing.new mm.i_dont_know_this(1, 2, 3) # method_missing i_dont_know_this [1, 2, 3] nil # NoMethodError: undefined method `i_dont_know_this' for #<MethodMissing>
  • 86.
    Interception! Example: Timing module MethodsWithTiming def method_missing(m, *args, &blk) if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and respond_to?(timed_method) respond = nil measurement = Benchmark.measure { respond = send(timed_method, *args, &blk) } puts "Method #{m} took #{measurement}" respond else super end end end
  • 87.
    Interception! Example:Timing module MethodsWithTiming def method_missing(m, *args, &blk) if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and respond_to?(timed_method) respond = nil measurement = Benchmark.measure { respond = send(timed_method, *args, &blk) } puts "Method #{m} took #{measurement}" respond else super end end end sc = SlowClass.new class SlowClass sc.slow include MethodsWithTiming def slow sleep 1 sc.slow_with_timing end # Method slow_with_timing took 0.000000 0.000000 0.000000 ( 1.000088) end
  • 88.
    Interception! Example: Proxy class Proxy def initialize(backing) @backing = backing end def method_missing(m, *args, &blk) @backing.send(m, *args, &blk) end end
  • 89.
    Interception! Example: Proxy class LoggingProxy def initialize(backing) @backing = backing end def method_missing(m, *args, &blk) puts "Calling method #{m} with #{args.inspect}" @backing.send(m, *args, &blk) end end
  • 90.
    Interception! Example: Simple DSL class NameCollector attr_reader :names def initialize @names = [] end def method_missing(method, *args, &blk) args.empty? ? @names.push(method.to_s.capitalize) : super end end nc = NameCollector.new nc.josh nc.bob nc.jane nc.names.join(' ') # => Josh Bob Jane
  • 91.
  • 92.
    Interception! Object#respond_to?(sym) Example: Timing module MethodsWithTiming alias_method :original_respond_to?, :respond_to? def method_missing(m, *args, &blk) if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and original_respond_to?(timed_method) respond = nil measurement = Benchmark.measure { respond = send(timed_method, *args, &blk) } puts "Method #{m} took #{measurement}" respond else super end end def respond_to?(sym) (timed_method = sym.to_s[/^(.*)_with_timing$/, 1]) ? original_respond_to?(timed_method.to_sym) : original_respond_to?(sym) end end
  • 93.
    Interception! Object#respond_to?(sym) Example: Timing module MethodsWithTiming ge ts alias_method :original_respond_to?, :respond_to? It def method_missing(m, *args, &blk) r! if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and original_respond_to?(timed_method) te respond = nil et measurement = Benchmark.measure { b respond = send(timed_method, *args, &blk) } puts "Method #{m} took #{measurement}" respond else super end end def respond_to?(sym) (timed_method = sym.to_s[/^(.*)_with_timing$/, 1]) ? original_respond_to?(timed_method.to_sym) : original_respond_to?(sym) end end
  • 94.
    Interception! Object#respond_to_missing?(sym) (1.9 only) Example: Timing module MethodsWithTiming def method_missing(m, *args, &blk) if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and respond_to?(timed_method) respond = nil measurement = Benchmark.measure { respond = send(timed_method, *args, &blk) } puts "Method #{m} took #{measurement}" respond else super end end def respond_to_missing?(sym) (timed_method = sym.to_s[/^(.*)_with_timing$/, 1]) ? respond_to?(timed_method.to_sym) : super end end
  • 95.
  • 96.
  • 97.
    Interception! const_missing(sym) MyClass::MyOtherClass # MyClass.const_missing(:MyOtherClass) Example: Loader classLoader def self.const_missing(sym) file = File.join(File.dirname(__FILE__), "#{sym.to_s.downcase}.rb") if File.exist?(file) require file Object.const_defined?(sym) ? Object.const_get(sym) : super else puts "can't find #{file}, sorry!" super end end end
  • 98.
    Interception! Example: Loader class Loader def self.const_missing(sym) file = File.join(File.dirname(__FILE__), "#{sym.to_s.downcase}.rb") if File.exist?(file) require file Object.const_defined?(sym) ? Object.const_get(sym) : super else puts "can't find #{file}, sorry!" super end end end
  • 99.
    Interception! Example: Loader class Loader def self.const_missing(sym) file = File.join(File.dirname(__FILE__), "#{sym.to_s.downcase}.rb") if File.exist?(file) require file Object.const_defined?(sym) ? Object.const_get(sym) : super else puts "can't find #{file}, sorry!" super end end end Loader::Auto # can't find ./auto.rb, sorry! # NameError: uninitialized constant Loader::Auto # or, if you have an ./auto.rb Loader::Auto # => Auto
  • 100.
  • 101.
  • 102.
  • 103.
    Callbacks Module#method_added class MyClass def self.method_added(m) puts "adding #{m}" end puts "defining my method" def my_method 'two' end puts "done defining my method" end
  • 104.
    Callbacks Module#method_added class MyClass defining my method def self.method_added(m) adding my_method puts "adding #{m}" done defining my method end puts "defining my method" def my_method 'two' end puts "done defining my method" end
  • 105.
    Callbacks Module#method_added Example: Thor! class Tasks def self.desc(desc) @desc = desc end def self.method_added(m) (@method_descs ||= {})[m] = @desc @desc = nil end def self.method_description(m) method_defined?(m) ? @method_descs[m] || "This action isn't documented" : "This action doesn't exist" end desc "Start server" def start end def stop end end
  • 106.
    Callbacks Module#method_added Example: Thor! Record the description class Tasks def self.desc(desc) @desc = desc When a method is added, end record the description associated def self.method_added(m) with that method (@method_descs ||= {})[m] = @desc @desc = nil end Provide the description for a def self.method_description(m) method, or, if not found, some method_defined?(m) ? default string. @method_descs[m] || "This action isn't documented" : "This action doesn't exist" end desc "Start server" def start end def stop end end
  • 107.
    Callbacks Module#method_added Example: Thor! Record the description class Tasks def self.desc(desc) @desc = desc When a method is added, end record the description associated def self.method_added(m) with that method (@method_descs ||= {})[m] = @desc @desc = nil end Provide the description for a def self.method_description(m) method, or, if not found, some method_defined?(m) ? default string. @method_descs[m] || "This action isn't documented" : "This action doesn't exist" end desc "Start server" def start end Described! def stop end end
  • 108.
    Callbacks Module#method_added Example: Thor! Query your methods! class Tasks puts Tasks.method_description(:start) def self.desc(desc) # => Start server @desc = desc puts Tasks.method_description(:stop) end # => This action isn't documented def self.method_added(m) puts Tasks.method_description(:restart) (@method_descs ||= {})[m] = @desc # => This action doesn't exist @desc = nil end def self.method_description(m) method_defined?(m) ? @method_descs[m] || "This action isn't documented" : "This action doesn't exist" end desc "Start server" def start end def stop end end
  • 109.
  • 110.
    Callbacks Object#singleton_method_added class ClassWithMethods def self.singleton_method_added(m) puts "ADDING! #{m}" end def self.another end end
  • 111.
    Callbacks Object#singleton_method_added class ClassWithMethods def self.singleton_method_added(m) puts "ADDING! #{m}" end def self.another end end # ADDING! singleton_method_added # ADDING! another
  • 112.
    Callbacks Object#singleton_method_added class ClassWithMethods def self.singleton_method_added(m) puts "ADDING! #{m}" end def self.another end end Holy meta! # ADDING! singleton_method_added # ADDING! another
  • 113.
    Callbacks Module#included module Logger def self.included(m) puts "adding logging to #{m}" end end class Server include Logger end # adding logging to Server
  • 114.
    Callbacks Module#included Example: ClassMethods pattern moduleLogger class Server def self.included(m) include Logger puts "adding logging to #{m}" end def self.create log("Creating server!") def self.log(message) end puts "LOG: #{message}" end end end Server.create # `create': undefined method `log' for Server:Class (NoMethodError)
  • 115.
    Callbacks Module#included Example: ClassMethods pattern moduleLogger class Server def self.included(m) include Logger m.extend(ClassMethods) end def self.create log("Creating server!") module ClassMethods end def log(message) end puts "LOG: #{message}" end end end Server.create # LOG: Creating server!
  • 116.
    Callbacks Module#extended module One def self.extended(obj) puts "#{self} has been extended by #{obj}" end end Object.new.extend(One) # One has been extended by #<Object:0x1019614a8>
  • 117.
    Callbacks Class#inherited class Parent def self.inherited(o) puts "#{self} was inherited by #{o}" end end class Child < Parent end # Parent was inherited by Child
  • 118.
    Callbacks Guarding callbacks Module#append_features include Module#extend_object extend def self.extend_object(o) super end def self.append_features(o) super end
  • 119.
    Callbacks Guarding callbacks Module#append_features include Module#extend_object extend def self.append_features(o) o.instance_method(:<=>) ? super : warn('you no can uze') end
  • 120.
    Callbacks Kernel#caller def one two end def two method name three file name line (optional) end def three p caller end # ["method.rb:156:in `two'", "method.rb:152:in `one'", "method.rb:163"] https://github.com/joshbuddy/callsite
  • 121.
    Callbacks Module#nesting module A module B module C p Module.nesting end end end # [A::B::C, A::B, A]
  • 122.
    There and backagain, a parsing tale
  • 123.
    There and backagain, a parsing tale gem install ruby_parser gem install sexp_processor gem install ruby2ruby Let’s go!
  • 124.
    There and backagain, a parsing tale Parsing require 'rubygems' require 'ruby_parser' RubyParser.new.process("'string'") s(:str, "string") Type Arguments...
  • 125.
    There and backagain, a parsing tale Parsing require 'rubygems' require 'ruby_parser' RubyParser.new.process("'string'") s(:str, "string") [:str, "string"] # Sexp Sexp.superclass # Array
  • 126.
    There and backagain, a parsing tale Parsing RubyParser.new.process("'string' + 'string'") s(:call, s(:str, "string"), :+, s(:arglist, s(:str, "string"))) Method Method Receiver Arguments call name
  • 127.
    There and backagain, a parsing tale Parsing RubyParser.new.process("'string' + 'string'") s(:call, nil, :puts, s(:arglist, s(:str, "hello world"))) Method Receiver Method Arguments call name
  • 128.
    There and backagain, a parsing tale And, back again... require 'rubygems' require 'ruby2ruby' Ruby2Ruby.new.process [:str, "hello"] # => "hello"
  • 129.
    There and backagain, a parsing tale And, back again... require 'rubygems' require 'ruby2ruby' Ruby2Ruby.new.process [:str, "hello"] # => "hello" Ruby2Ruby.new.process [:lit, :symbol] # => :symbol
  • 130.
    There and backagain, a parsing tale Roundtrip require 'sexp_processor' require 'ruby2ruby' require 'ruby_parser' class JarJarify < SexpProcessor def initialize self.strict = false super end def process_str(str) new_string = "YOUZA GONNA SAY #{str[-1]}" str.clear s(:str, new_string) end end
  • 131.
    There and backagain, a parsing tale Roundtrip class JarJarify < SexpProcessor def initialize self.strict = false super end def process_str(str) new_string = "YOUZA GONNA SAY #{str[-1]}" str.clear s(:str, new_string) end end ast = RubyParser.new.process('puts "hello"') Ruby2Ruby.new.process(JarJarify.new.process(ast)) # => puts("YOUZA GONNA SAY hello")
  • 132.
    There and backagain, a parsing tale Roundtrip class JarJarify < SexpProcessor def initialize self.strict = false Process type :str super end def process_str(str) new_string = "YOUZA GONNA SAY #{str[-1]}" str.clear s(:str, new_string) Consume the current sexp end end Return a new one ast = RubyParser.new.process('puts "hello"') Ruby2Ruby.new.process(JarJarify.new.process(ast)) # => puts("YOUZA GONNA SAY hello")
  • 133.