SlideShare una empresa de Scribd logo
1 de 133
Descargar para leer sin conexión
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!

Más contenido relacionado

La actualidad más candente

Procedure Typing for Scala
Procedure Typing for ScalaProcedure Typing for Scala
Procedure Typing for Scala
akuklev
 

La actualidad más candente (20)

Easy mockppt
Easy mockpptEasy mockppt
Easy mockppt
 
Javascript
JavascriptJavascript
Javascript
 
Java Script Best Practices
Java Script Best PracticesJava Script Best Practices
Java Script Best Practices
 
Scala test
Scala testScala test
Scala test
 
J query
J queryJ query
J query
 
Java best practices
Java best practicesJava best practices
Java best practices
 
JQuery
JQueryJQuery
JQuery
 
Only oop
Only oopOnly oop
Only oop
 
Introduction to java
Introduction to javaIntroduction to java
Introduction to java
 
Procedure Typing for Scala
Procedure Typing for ScalaProcedure Typing for Scala
Procedure Typing for Scala
 
PHP - Introduction to Object Oriented Programming with PHP
PHP -  Introduction to  Object Oriented Programming with PHPPHP -  Introduction to  Object Oriented Programming with PHP
PHP - Introduction to Object Oriented Programming with PHP
 
Python advance
Python advancePython advance
Python advance
 
Java script basic
Java script basicJava script basic
Java script basic
 
Easymock Tutorial
Easymock TutorialEasymock Tutorial
Easymock Tutorial
 
Swift Programming - Part 2
Swift Programming - Part 2Swift Programming - Part 2
Swift Programming - Part 2
 
Object oreinted php | OOPs
Object oreinted php | OOPsObject oreinted php | OOPs
Object oreinted php | OOPs
 
SWIFT 3
SWIFT 3SWIFT 3
SWIFT 3
 
09 Object Oriented Programming in PHP #burningkeyboards
09 Object Oriented Programming in PHP #burningkeyboards09 Object Oriented Programming in PHP #burningkeyboards
09 Object Oriented Programming in PHP #burningkeyboards
 
Clean code slide
Clean code slideClean code slide
Clean code slide
 
Building DSLs With Eclipse
Building DSLs With EclipseBuilding DSLs With Eclipse
Building DSLs With Eclipse
 

Destacado

Security 202 - Are you sure your site is secure?
Security 202 - Are you sure your site is secure?Security 202 - Are you sure your site is secure?
Security 202 - Are you sure your site is secure?
ConFoo
 
The business behind open source
The business behind open sourceThe business behind open source
The business behind open source
ConFoo
 
Scalable Architecture 101
Scalable Architecture 101Scalable Architecture 101
Scalable Architecture 101
ConFoo
 
Writing a Ruby Gem for beginners
Writing a Ruby Gem for beginnersWriting a Ruby Gem for beginners
Writing a Ruby Gem for beginners
ConFoo
 
Marrow: A Meta-Framework for Python 2.6+ and 3.1+
Marrow: A Meta-Framework for Python 2.6+ and 3.1+Marrow: A Meta-Framework for Python 2.6+ and 3.1+
Marrow: A Meta-Framework for Python 2.6+ and 3.1+
ConFoo
 
Anatomy of a large Django site
Anatomy of a large Django siteAnatomy of a large Django site
Anatomy of a large Django site
ConFoo
 
Opensource Authentication and Authorization
Opensource Authentication and AuthorizationOpensource Authentication and Authorization
Opensource Authentication and Authorization
ConFoo
 

Destacado (7)

Security 202 - Are you sure your site is secure?
Security 202 - Are you sure your site is secure?Security 202 - Are you sure your site is secure?
Security 202 - Are you sure your site is secure?
 
The business behind open source
The business behind open sourceThe business behind open source
The business behind open source
 
Scalable Architecture 101
Scalable Architecture 101Scalable Architecture 101
Scalable Architecture 101
 
Writing a Ruby Gem for beginners
Writing a Ruby Gem for beginnersWriting a Ruby Gem for beginners
Writing a Ruby Gem for beginners
 
Marrow: A Meta-Framework for Python 2.6+ and 3.1+
Marrow: A Meta-Framework for Python 2.6+ and 3.1+Marrow: A Meta-Framework for Python 2.6+ and 3.1+
Marrow: A Meta-Framework for Python 2.6+ and 3.1+
 
Anatomy of a large Django site
Anatomy of a large Django siteAnatomy of a large Django site
Anatomy of a large Django site
 
Opensource Authentication and Authorization
Opensource Authentication and AuthorizationOpensource Authentication and Authorization
Opensource Authentication and Authorization
 

Similar a Metaprogramming in Ruby

Ruby Metaprogramming
Ruby MetaprogrammingRuby Metaprogramming
Ruby Metaprogramming
Nando Vieira
 
Metaprogramming 101
Metaprogramming 101Metaprogramming 101
Metaprogramming 101
Nando Vieira
 
Ruby 程式語言入門導覽
Ruby 程式語言入門導覽Ruby 程式語言入門導覽
Ruby 程式語言入門導覽
Wen-Tien Chang
 
Metaprogramovanie #1
Metaprogramovanie #1Metaprogramovanie #1
Metaprogramovanie #1
Jano Suchal
 
Self, Class and Module
Self, Class and ModuleSelf, Class and Module
Self, Class and Module
Gourav Tiwari
 

Similar a Metaprogramming in Ruby (20)

The Dark Art of Rails Plugins (2008)
The Dark Art of Rails Plugins (2008)The Dark Art of Rails Plugins (2008)
The Dark Art of Rails Plugins (2008)
 
Ruby Metaprogramming
Ruby MetaprogrammingRuby Metaprogramming
Ruby Metaprogramming
 
Metaprogramming 101
Metaprogramming 101Metaprogramming 101
Metaprogramming 101
 
Ruby: Beyond the Basics
Ruby: Beyond the BasicsRuby: Beyond the Basics
Ruby: Beyond the Basics
 
A linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
A linguagem de programação Ruby - Robson "Duda" Sejan Soares DornellesA linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
A linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
 
Ruby Programming Language
Ruby Programming LanguageRuby Programming Language
Ruby Programming Language
 
Ruby
RubyRuby
Ruby
 
Ruby 程式語言入門導覽
Ruby 程式語言入門導覽Ruby 程式語言入門導覽
Ruby 程式語言入門導覽
 
Dsl
DslDsl
Dsl
 
Language supports it
Language supports itLanguage supports it
Language supports it
 
How To Test Everything
How To Test EverythingHow To Test Everything
How To Test Everything
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
 
Ruby for C# Developers
Ruby for C# DevelopersRuby for C# Developers
Ruby for C# Developers
 
Postobjektové programovanie v Ruby
Postobjektové programovanie v RubyPostobjektové programovanie v Ruby
Postobjektové programovanie v Ruby
 
Ruby Metaprogramming
Ruby MetaprogrammingRuby Metaprogramming
Ruby Metaprogramming
 
Ruby singleton class
Ruby singleton classRuby singleton class
Ruby singleton class
 
Metaprogramovanie #1
Metaprogramovanie #1Metaprogramovanie #1
Metaprogramovanie #1
 
Selfish presentation - ruby internals
Selfish presentation - ruby internalsSelfish presentation - ruby internals
Selfish presentation - ruby internals
 
Self, Class and Module
Self, Class and ModuleSelf, Class and Module
Self, Class and Module
 
Lightning talk
Lightning talkLightning talk
Lightning talk
 

Más de ConFoo

Debugging applications with network security tools
Debugging applications with network security toolsDebugging applications with network security tools
Debugging applications with network security tools
ConFoo
 
OWASP Enterprise Security API
OWASP Enterprise Security APIOWASP Enterprise Security API
OWASP Enterprise Security API
ConFoo
 
Introduction à la sécurité des WebServices
Introduction à la sécurité des WebServicesIntroduction à la sécurité des WebServices
Introduction à la sécurité des WebServices
ConFoo
 
Le bon, la brute et le truand dans les nuages
Le bon, la brute et le truand dans les nuagesLe bon, la brute et le truand dans les nuages
Le bon, la brute et le truand dans les nuages
ConFoo
 
The Solar Framework for PHP
The Solar Framework for PHPThe Solar Framework for PHP
The Solar Framework for PHP
ConFoo
 
Décrire un projet PHP dans des rapports
Décrire un projet PHP dans des rapportsDécrire un projet PHP dans des rapports
Décrire un projet PHP dans des rapports
ConFoo
 
Server Administration in Python with Fabric, Cuisine and Watchdog
Server Administration in Python with Fabric, Cuisine and WatchdogServer Administration in Python with Fabric, Cuisine and Watchdog
Server Administration in Python with Fabric, Cuisine and Watchdog
ConFoo
 
Think Mobile First, Then Enhance
Think Mobile First, Then EnhanceThink Mobile First, Then Enhance
Think Mobile First, Then Enhance
ConFoo
 
As-t-on encore besoin d'un framework web ?
As-t-on encore besoin d'un framework web ?As-t-on encore besoin d'un framework web ?
As-t-on encore besoin d'un framework web ?
ConFoo
 
Pragmatic Guide to Git
Pragmatic Guide to GitPragmatic Guide to Git
Pragmatic Guide to Git
ConFoo
 
Building servers with Node.js
Building servers with Node.jsBuilding servers with Node.js
Building servers with Node.js
ConFoo
 
An Overview of Flash Storage for Databases
An Overview of Flash Storage for DatabasesAn Overview of Flash Storage for Databases
An Overview of Flash Storage for Databases
ConFoo
 
Android Jump Start
Android Jump StartAndroid Jump Start
Android Jump Start
ConFoo
 
Develop mobile applications with Flex
Develop mobile applications with FlexDevelop mobile applications with Flex
Develop mobile applications with Flex
ConFoo
 
WordPress pour le développement d'aplications web
WordPress pour le développement d'aplications webWordPress pour le développement d'aplications web
WordPress pour le développement d'aplications web
ConFoo
 
Graphs, Edges & Nodes: Untangling the Social Web
Graphs, Edges & Nodes: Untangling the Social WebGraphs, Edges & Nodes: Untangling the Social Web
Graphs, Edges & Nodes: Untangling the Social Web
ConFoo
 
Rendre son CMS conforme au SGQRI 008 en 20 étapes
Rendre son CMS conforme au SGQRI 008 en 20 étapesRendre son CMS conforme au SGQRI 008 en 20 étapes
Rendre son CMS conforme au SGQRI 008 en 20 étapes
ConFoo
 

Más de ConFoo (17)

Debugging applications with network security tools
Debugging applications with network security toolsDebugging applications with network security tools
Debugging applications with network security tools
 
OWASP Enterprise Security API
OWASP Enterprise Security APIOWASP Enterprise Security API
OWASP Enterprise Security API
 
Introduction à la sécurité des WebServices
Introduction à la sécurité des WebServicesIntroduction à la sécurité des WebServices
Introduction à la sécurité des WebServices
 
Le bon, la brute et le truand dans les nuages
Le bon, la brute et le truand dans les nuagesLe bon, la brute et le truand dans les nuages
Le bon, la brute et le truand dans les nuages
 
The Solar Framework for PHP
The Solar Framework for PHPThe Solar Framework for PHP
The Solar Framework for PHP
 
Décrire un projet PHP dans des rapports
Décrire un projet PHP dans des rapportsDécrire un projet PHP dans des rapports
Décrire un projet PHP dans des rapports
 
Server Administration in Python with Fabric, Cuisine and Watchdog
Server Administration in Python with Fabric, Cuisine and WatchdogServer Administration in Python with Fabric, Cuisine and Watchdog
Server Administration in Python with Fabric, Cuisine and Watchdog
 
Think Mobile First, Then Enhance
Think Mobile First, Then EnhanceThink Mobile First, Then Enhance
Think Mobile First, Then Enhance
 
As-t-on encore besoin d'un framework web ?
As-t-on encore besoin d'un framework web ?As-t-on encore besoin d'un framework web ?
As-t-on encore besoin d'un framework web ?
 
Pragmatic Guide to Git
Pragmatic Guide to GitPragmatic Guide to Git
Pragmatic Guide to Git
 
Building servers with Node.js
Building servers with Node.jsBuilding servers with Node.js
Building servers with Node.js
 
An Overview of Flash Storage for Databases
An Overview of Flash Storage for DatabasesAn Overview of Flash Storage for Databases
An Overview of Flash Storage for Databases
 
Android Jump Start
Android Jump StartAndroid Jump Start
Android Jump Start
 
Develop mobile applications with Flex
Develop mobile applications with FlexDevelop mobile applications with Flex
Develop mobile applications with Flex
 
WordPress pour le développement d'aplications web
WordPress pour le développement d'aplications webWordPress pour le développement d'aplications web
WordPress pour le développement d'aplications web
 
Graphs, Edges & Nodes: Untangling the Social Web
Graphs, Edges & Nodes: Untangling the Social WebGraphs, Edges & Nodes: Untangling the Social Web
Graphs, Edges & Nodes: Untangling the Social Web
 
Rendre son CMS conforme au SGQRI 008 en 20 étapes
Rendre son CMS conforme au SGQRI 008 en 20 étapesRendre son CMS conforme au SGQRI 008 en 20 étapes
Rendre son CMS conforme au SGQRI 008 en 20 étapes
 

Último

IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
Enterprise Knowledge
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
giselly40
 

Último (20)

Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your Business
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 

Metaprogramming in Ruby

  • 1. Metaprogramming in Ruby
  • 2. Things I Wish I Knew When I Started Ruby
  • 3.
  • 4.
  • 7. What? Writing programs that write programs.
  • 8. What? Writing programs that write programs. NOT code generation!
  • 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
  • 17. Drawbacks You can write some difficult-to-understand code.
  • 18. Drawbacks You can write some difficult-to-understand code.
  • 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!
  • 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. Ruby classes class ParsingError < RuntimeError end
  • 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. eval, instance_eval, class_eval
  • 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
  • 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
  • 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' module Project 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 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
  • 71. 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
  • 72. 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
  • 73. Scoping Kernel#binding Let’s you leak the current “bindings”
  • 74. 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
  • 75. 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
  • 76. 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!
  • 77. 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'
  • 78. 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
  • 79. 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
  • 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
  • 84. 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
  • 85. 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>
  • 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
  • 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
  • 97. 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
  • 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
  • 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
  • 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 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)
  • 115. 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!
  • 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 back again, a parsing tale
  • 123. There and back again, a parsing tale gem install ruby_parser gem install sexp_processor gem install ruby2ruby Let’s go!
  • 124. There and back again, a parsing tale Parsing require 'rubygems' require 'ruby_parser' RubyParser.new.process("'string'") s(:str, "string") Type Arguments...
  • 125. 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
  • 126. 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
  • 127. 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
  • 128. There and back again, a parsing tale And, back again... require 'rubygems' require 'ruby2ruby' Ruby2Ruby.new.process [:str, "hello"] # => "hello"
  • 129. 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
  • 130. 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
  • 131. 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")
  • 132. 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")