133
Metaprogramming in Ruby

Metaprogramming in Ruby

  • Upload
    confoo

  • View
    2.255

  • Download
    2

Embed Size (px)

Citation preview

Page 1: Metaprogramming in Ruby

Metaprogrammingin Ruby

Page 2: Metaprogramming in Ruby

Things I Wish I KnewWhen I Started Ruby

Page 3: Metaprogramming in Ruby
Page 4: Metaprogramming in Ruby
Page 5: Metaprogramming in Ruby

Who?

@jjJoshua Hull

https://github.com/joshbuddy

Page 6: Metaprogramming in Ruby

What?

Page 7: Metaprogramming in Ruby

What?

Writing programs that write programs.

Page 8: Metaprogramming in Ruby

What?

Writing programs that write programs.NOT code generation!

Page 9: Metaprogramming in Ruby

Why?We all do it.

Page 10: Metaprogramming in Ruby

Why?We all do it.

Ruby attr_accessor :my_attribute

Page 11: Metaprogramming in Ruby

Why?We all do it.

Ruby attr_accessor :my_attribute

def my_attribute @my_attributeend

def my_attribute=(my_attribute) @my_attribute = my_attributeend

Page 12: Metaprogramming in Ruby

Why?We all do it.

Ruby

public int getAttribute() { return attribute;}

public void setAttribute(int newAttribute) { attribute = newAttribute;}

Java

attr_accessor :my_attribute

Page 13: Metaprogramming in Ruby

Why?We all do it.

Ruby

Java

Winner

public int getAttribute() { return attribute;}

public void setAttribute(int newAttribute) { attribute = newAttribute;}

attr_accessor :my_attribute

Page 14: Metaprogramming in Ruby

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

def gender @gender || 'not set'end

def name @name || 'not set'end

Page 15: Metaprogramming in Ruby

Why?

[:age, :gender, :name].each do |attr| define_method(attr) do instance_variable_get(:"@#{attr}") || 'not set' endend

def age @age || 'not set'end

def gender @gender || 'not set'end

def name @name || 'not set'end

Page 16: Metaprogramming in Ruby

Drawbacks

Page 17: Metaprogramming in Ruby

Drawbacks

You can write some difficult-to-understand code.

Page 18: Metaprogramming in Ruby

Drawbacks

You can write some difficult-to-understand code.

Page 19: Metaprogramming in Ruby

Method dispatch

Page 20: Metaprogramming in Ruby

Method dispatch

class MyObject attr_accessor :name

def say_hello puts "hello" puts name endend

Page 21: Metaprogramming in Ruby

Method dispatch

class MyObject attr_accessor :name

def say_hello puts "hello" puts name endend This is a

method call,but who receives it?

Page 22: Metaprogramming in Ruby

Method dispatch

class MyObject attr_accessor :name

def say_hello puts "hello" puts self.name endend

Page 23: Metaprogramming in Ruby

Method dispatch

class MyObject attr_accessor :name

def say_hello puts "hello" puts self.name endend

self is always theimplied receiver!

Page 24: Metaprogramming in Ruby

Method dispatch

class MyObject attr_accessor :name

def say_hello puts "hello" puts name endend

Page 25: Metaprogramming in Ruby

Method dispatch

class MyObject self.attr_accessor :name

def say_hello puts "hello" puts name endend

Page 26: Metaprogramming in Ruby

Method dispatch

Who is self here?

class MyObject self.attr_accessor :name

def say_hello puts "hello" puts name endend

Page 27: Metaprogramming in Ruby

Method dispatch

Who is self here?

class MyObject self.attr_accessor :name

def say_hello puts "hello" puts name endend

The class is!

Page 28: Metaprogramming in Ruby

Method dispatch

Who is self here?

The class is!

Module#attr

Page 29: Metaprogramming in Ruby

Ruby classes

Page 30: Metaprogramming in Ruby

Ruby classesclass NewClass def hey puts 'hello!' endend

Page 31: Metaprogramming in Ruby

Ruby classesclass NewClass def hey puts 'hello!' endend

This is normalcode!

{

Page 32: Metaprogramming in Ruby

Ruby classesclass NewClass def hey puts 'hello!' endend

NewClass = Class.new do def hey puts 'hello!' endend

is the same as

Page 33: Metaprogramming in Ruby

Ruby classes

class ParsingError < RuntimeErrorend

Page 34: Metaprogramming in Ruby

Ruby classes

ParsingError = Class.new(RuntimeError)

class ParsingError < RuntimeErrorend

is the same as

Page 35: Metaprogramming in Ruby

Ruby classes

def class_with_accessors(*attributes) Class.new do attr_accessor *attributes endend

Page 36: Metaprogramming in Ruby

Ruby classes

def class_with_accessors(*attributes) Class.new do attr_accessor *attributes endend

Returns a new class!

Page 37: Metaprogramming in Ruby

Ruby classes

def class_with_accessors(*attributes) Class.new do attr_accessor *attributes endend

Returns a new class!

class Person < class_with_accessors(:name, :age, :sex) # ...end

Page 38: Metaprogramming in Ruby

eval, instance_eval, class_eval

Page 39: Metaprogramming in Ruby

eval, instance_eval, class_eval

eval

instance_eval

class_eval

Method

Page 40: Metaprogramming in Ruby

eval, instance_eval, class_eval

eval

instance_eval

class_eval

your current context

the object

the object’s class

Method Context

Page 41: Metaprogramming in Ruby

eval, instance_eval, class_eval

eval "puts 'hello'"eval

# hello

Page 42: Metaprogramming in Ruby

eval, instance_eval, class_eval

class MyClassend

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

instance_eval

Page 43: Metaprogramming in Ruby

eval, instance_eval, class_eval

instance_eval

MyClass.hi# 'hi'

class MyClassend

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

Page 44: Metaprogramming in Ruby

eval, instance_eval, class_eval

class MyClassend

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

instance_eval

obj = MyClass.new# <MyClass:0x10178aff8> obj.hi# NoMethodError: undefined method `hi' for #<MyClass>

Page 45: Metaprogramming in Ruby

eval, instance_eval, class_eval

class MyClassend

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

class_eval

Page 46: Metaprogramming in Ruby

eval, instance_eval, class_eval

class MyClassend

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

class_eval

MyClass.hi# NoMethodError: undefined method `hi' for MyClass:Class

Page 47: Metaprogramming in Ruby

eval, instance_eval, class_eval

class MyClassend

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

class_eval

obj = MyClass.new# <MyClass:0x101849688> obj.hi# "hi"

Page 48: Metaprogramming in Ruby

eval, instance_eval, class_eval

class_eval

class MyClassend

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

obj = MyClass.new# <MyClass:0x101849688> obj.hi# "hi"

instance_eval

MyClass.hi# 'hi'

class MyClassend

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

Page 49: Metaprogramming in Ruby

eval, instance_eval, class_eval

Ninja’s will attack you if ...

you don’t use __FILE__, __LINE__

Page 50: Metaprogramming in Ruby

eval, instance_eval, class_eval

class HiThereend

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

HiThere.new.hiHiThere.new.hi_with_niceness

Page 51: Metaprogramming in Ruby

eval, instance_eval, class_eval

class HiThereend

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

HiThere.new.hiHiThere.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

Page 52: Metaprogramming in Ruby

eval, instance_eval, class_eval

class HiThereend

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

HiThere.new.hiHiThere.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

So nice. <3

Page 53: Metaprogramming in Ruby

eval, instance_eval, class_eval

Implement attr_accessor!

Page 54: Metaprogramming in Ruby

eval, instance_eval, class_eval

class Module def create_attr(attribute) class_eval("def #{attribute}; @#{attribute}; end") endend

Page 55: Metaprogramming in Ruby

eval, instance_eval, class_eval

class Module def create_attr(attribute) class_eval("def #{attribute}; @#{attribute}; end") endend

class M create_attr :hiend

Page 56: Metaprogramming in Ruby

eval, instance_eval, class_eval

class Module def create_attr(attribute) class_eval("def #{attribute}; @#{attribute}; end") endend

class M create_attr :hiend

def M def hi @hi endend

Page 57: Metaprogramming in Ruby

Defining methods

Page 58: Metaprogramming in Ruby

Defining methodsFor an object

o = Object.newo.instance_eval("def just_this_object; end")o.just_this_object

Page 59: Metaprogramming in Ruby

Defining methodsFor an object

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

o = Object.newo.instance_eval("def just_this_object; end")o.just_this_object

Page 60: Metaprogramming in Ruby

Defining methodsFor an object

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

o = Object.newo.instance_eval("def just_this_object; end")o.just_this_object

o = Object.newo.instance_eval { def just_this_object end}

Page 61: Metaprogramming in Ruby

Defining methodsFor an object

o = Object.newo.extend(Module.new { def just_this_object end})

Page 62: Metaprogramming in Ruby

Defining methodsFor a class

MyClass = Class.new

class MyClass def new_method endend

MyClass.new.respond_to?(:new_method) # true

Page 63: Metaprogramming in Ruby

Defining methodsFor a class

MyClass = Class.new

MyClass.class_eval " def new_method end"

MyClass.class_eval do def new_method endend

MyClass.send(:define_method, :new_method) { # your method body}

Page 64: Metaprogramming in Ruby

Defining methodsAliasing

Module#alias_method

Page 65: Metaprogramming in Ruby

Scoping

Page 66: Metaprogramming in Ruby

Scoping

module Project class Main def run # ... end endend

Page 67: Metaprogramming in Ruby

Scoping

module Project class Main def run # ... end endend

Class definitionsModule definitionsMethod definitions

Page 68: Metaprogramming in Ruby

Scopinga = 'hello'

module Project class Main def run puts a end endend

Project::Main.new.run

Page 69: Metaprogramming in Ruby

Scopinga = 'hello'

module Project class Main def run puts a end endend

Project::Main.new.run

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

Page 70: Metaprogramming in Ruby

Scopingmodule Project class Main endend

a = 'hello'

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

Project::Main.new.run # => hello

Page 71: Metaprogramming in Ruby

ScopingExample: 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 endend

Page 72: Metaprogramming in Ruby

ScopingExample: 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 endend

Client = Class.newAddConnections.add_connection_methods(Client, 'localhost', 8080)

Client.new.get_connection # Getting connection for localhost:8080Client.new.host # localhost Client.new.port # 8080

Page 73: Metaprogramming in Ruby

ScopingKernel#bindingLet’s you leak the current “bindings”

Page 74: Metaprogramming in Ruby

ScopingKernel#bindingLet’s you leak the current “bindings”

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

connection = nilcreate_connection(binding)connection # => I am a connection

Page 75: Metaprogramming in Ruby

ScopingKernel#bindingLet’s you leak the current “bindings”

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

connection = nilcreate_connection(binding)connection # => I am a connection

Callswith thecurrentstate

Page 76: Metaprogramming in Ruby

ScopingKernel#bindingLet’s you leak the current “bindings”

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

connection = nilcreate_connection(binding)connection # => I am a connection

Callswith thecurrentstate MAGIC!

Page 77: Metaprogramming in Ruby

ScopingKernel#bindingLet’s you leak the current “bindings”

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

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

Page 78: Metaprogramming in Ruby

ScopingKernel#bindingLet’s you leak the current “bindings”

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

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

You can’t add to the local variables via binding

Page 79: Metaprogramming in Ruby

ScopingKernel#bindingLet’s you leak the current “bindings”

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

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

You can’t add to the local variables via eval

Page 80: Metaprogramming in Ruby

ScopingKernel#binding

TOPLEVEL_BINDING

Page 81: Metaprogramming in Ruby

ScopingKernel#binding

TOPLEVEL_BINDING

a = 'hello'

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

Program::Main.new.run # => hello

Page 82: Metaprogramming in Ruby

Interception!(aka lazy magic)

Page 83: Metaprogramming in Ruby

Interception!method_missing(method, *args, &blk)

Page 84: Metaprogramming in Ruby

Interception!method_missing(method, *args, &blk)

class MethodMissing def method_missing(m, *args, &blk) puts "method_missing #{m} #{args.inspect} #{blk.inspect}" super endend

Page 85: Metaprogramming in Ruby

Interception!method_missing(method, *args, &blk)

class MethodMissing def method_missing(m, *args, &blk) puts "method_missing #{m} #{args.inspect} #{blk.inspect}" super endend

mm = MethodMissing.newmm.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>

Page 86: Metaprogramming in Ruby

Interception!Example: Timingmodule 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 endend

Page 87: Metaprogramming in Ruby

Interception!Example: Timingmodule 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 endend

class SlowClass include MethodsWithTiming def slow sleep 1 endend

sc = SlowClass.newsc.slow

sc.slow_with_timing# Method slow_with_timing took 0.000000 0.000000 0.000000 ( 1.000088)

Page 88: Metaprogramming in Ruby

Interception!Example: Proxyclass Proxy def initialize(backing) @backing = backing end

def method_missing(m, *args, &blk) @backing.send(m, *args, &blk) endend

Page 89: Metaprogramming in Ruby

Interception!Example: Proxyclass 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) endend

Page 90: Metaprogramming in Ruby

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 endend

nc = NameCollector.newnc.joshnc.bobnc.janenc.names.join(' ') # => Josh Bob Jane

Page 91: Metaprogramming in Ruby

Interception!Object#respond_to?(sym)

Page 92: Metaprogramming in Ruby

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) endend

Page 93: Metaprogramming in Ruby

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) endend

It gets

better!

Page 94: Metaprogramming in Ruby

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 endend

Page 95: Metaprogramming in Ruby

Interception!const_missing(sym)

Page 96: Metaprogramming in Ruby

Interception!const_missing(sym)MyClass::MyOtherClass

# MyClass.const_missing(:MyOtherClass)

Page 97: Metaprogramming in Ruby

Interception!const_missing(sym)

Example: Loader

MyClass::MyOtherClass

# MyClass.const_missing(:MyOtherClass)

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 endend

Page 98: Metaprogramming in Ruby

Interception!Example: Loaderclass 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 endend

Page 99: Metaprogramming in Ruby

Interception!Example: Loaderclass 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 endend

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

# or, if you have an ./auto.rbLoader::Auto# => Auto

Page 100: Metaprogramming in Ruby

Callbacks

Page 101: Metaprogramming in Ruby

CallbacksModule#method_added

Page 102: Metaprogramming in Ruby

CallbacksModule#method_added

Page 103: Metaprogramming in Ruby

CallbacksModule#method_addedclass 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

Page 104: Metaprogramming in Ruby

CallbacksModule#method_addedclass 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

defining my methodadding my_methoddone defining my method

Page 105: Metaprogramming in Ruby

CallbacksModule#method_addedExample: 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 endend

Page 106: Metaprogramming in Ruby

CallbacksModule#method_addedExample: 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 endend

Record the description

When a method is added,record the description associatedwith that method

Provide the description for a method, or, if not found, some default string.

Page 107: Metaprogramming in Ruby

CallbacksModule#method_addedExample: 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 endend

Record the description

When a method is added,record the description associatedwith that method

Provide the description for a method, or, if not found, some default string.

Described!

Page 108: Metaprogramming in Ruby

CallbacksModule#method_addedExample: 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 endend

puts Tasks.method_description(:start)# => Start serverputs Tasks.method_description(:stop)# => This action isn't documentedputs Tasks.method_description(:restart)# => This action doesn't exist

Query your methods!

Page 109: Metaprogramming in Ruby

CallbacksObject#singleton_method_added

Page 110: Metaprogramming in Ruby

CallbacksObject#singleton_method_added

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

def self.another endend

Page 111: Metaprogramming in Ruby

CallbacksObject#singleton_method_added

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

def self.another endend

# ADDING! singleton_method_added# ADDING! another

Page 112: Metaprogramming in Ruby

CallbacksObject#singleton_method_added

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

def self.another endend

# ADDING! singleton_method_added# ADDING! another

Holy meta!

Page 113: Metaprogramming in Ruby

CallbacksModule#includedmodule Logger def self.included(m) puts "adding logging to #{m}" endend

class Server include Loggerend

# adding logging to Server

Page 114: Metaprogramming in Ruby

CallbacksModule#includedExample: ClassMethods pattern

module Logger def self.included(m) puts "adding logging to #{m}" end

def self.log(message) puts "LOG: #{message}" endend

class Server include Logger

def self.create log("Creating server!") endend

Server.create# `create': undefined method `log' for Server:Class (NoMethodError)

Page 115: Metaprogramming in Ruby

CallbacksModule#includedExample: ClassMethods pattern

class Server include Logger

def self.create log("Creating server!") endend

module Logger def self.included(m) m.extend(ClassMethods) end

module ClassMethods def log(message) puts "LOG: #{message}" end endend

Server.create# LOG: Creating server!

Page 116: Metaprogramming in Ruby

CallbacksModule#extended

module One def self.extended(obj) puts "#{self} has been extended by #{obj}" endend

Object.new.extend(One)

# One has been extended by #<Object:0x1019614a8>

Page 117: Metaprogramming in Ruby

CallbacksClass#inherited

class Parent def self.inherited(o) puts "#{self} was inherited by #{o}" endend

class Child < Parentend

# Parent was inherited by Child

Page 118: Metaprogramming in Ruby

CallbacksGuarding callbacks

Module#append_featuresModule#extend_object

includeextend

def self.extend_object(o) super end

def self.append_features(o) super end

Page 119: Metaprogramming in Ruby

CallbacksGuarding callbacks

Module#append_featuresModule#extend_object

includeextend

def self.append_features(o) o.instance_method(:<=>) ? super : warn('you no can uze')end

Page 120: Metaprogramming in Ruby

CallbacksKernel#callerdef one twoend

def two threeend

def three p callerend

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

file name linemethod name

(optional)

https://github.com/joshbuddy/callsite

Page 121: Metaprogramming in Ruby

CallbacksModule#nesting

module A module B module C p Module.nesting end endend

# [A::B::C, A::B, A]

Page 122: Metaprogramming in Ruby

There and back again, a parsing tale

Page 123: Metaprogramming in Ruby

gem install ruby_parsergem install sexp_processor

Let’s go!

There and back again, a parsing tale

gem install ruby2ruby

Page 124: Metaprogramming in Ruby

There and back again, a parsing tale

require 'rubygems'require 'ruby_parser'RubyParser.new.process("'string'")

s(:str, "string")

Type Arguments...

Parsing

Page 125: Metaprogramming in Ruby

There and back again, a parsing tale

require 'rubygems'require 'ruby_parser'RubyParser.new.process("'string'")

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

Sexp.superclass# Array

Parsing

Page 126: Metaprogramming in Ruby

There and back again, a parsing tale

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

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

Methodcall Receiver Method

name Arguments

Parsing

Page 127: Metaprogramming in Ruby

There and back again, a parsing tale

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

Methodcall

Receiver Methodname Arguments

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

Parsing

Page 128: Metaprogramming in Ruby

There and back again, a parsing tale

And, back again...

require 'rubygems'require 'ruby2ruby'

Ruby2Ruby.new.process [:str, "hello"] # => "hello"

Page 129: Metaprogramming in Ruby

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

Page 130: Metaprogramming in Ruby

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) endend

Page 131: Metaprogramming in Ruby

There and back again, a parsing tale

Roundtripclass 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) endend

ast = RubyParser.new.process('puts "hello"')Ruby2Ruby.new.process(JarJarify.new.process(ast))# => puts("YOUZA GONNA SAY hello")

Page 132: Metaprogramming in Ruby

There and back again, a parsing tale

Roundtripclass 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) endend

ast = RubyParser.new.process('puts "hello"')Ruby2Ruby.new.process(JarJarify.new.process(ast))# => puts("YOUZA GONNA SAY hello")

Process type :str

Consume the current sexpReturn a new one

Page 133: Metaprogramming in Ruby

IT’S OVER!