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
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
}
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
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
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
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
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
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
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")