Skip to content

Instantly share code, notes, and snippets.

@tiagopog
Last active August 29, 2015 14:21
Show Gist options
  • Save tiagopog/aa90d8b45cd2082f9974 to your computer and use it in GitHub Desktop.
Save tiagopog/aa90d8b45cd2082f9974 to your computer and use it in GitHub Desktop.
Ruby - Metaprogramming
# Each object in Ruby also has its own metaclass,
# a Class that can have methods, but is only attached to the object itself.
matz = Object.new
def matz.speak
"Place your burden to machine's shoulders"
end
# What's going on here is that we're adding the speak method to matz's
# metaclass, and the matz object inherits from its metaclass and then Object.
# The reason this is somewhat less clear than ideal is that the metaclass
# is invisible in Ruby:
matz = Object.new
def matz.speak
"Place your burden to machine's shoulders"
end
matz.class #=> Object
# In fact, matz's "class" is its invisible metaclass.
# We can even get access to the metaclass:
metaclass = class << matz; self; end
metaclass.instance_methods.grep(/speak/) #=> ["speak"]
# Ruby provides a syntax for accessing an object's metaclass directly.
# By doing class << Person, we are setting self to Person's metaclass for the
# duration of the block. As a result, the species method is added to Person's
# metaclass, rather than the class itself:
class << Person
def species
"Homo Sapien"
end
self.name #=> ""
end
# Here, we combine several of the techniques. First, we open Person, making self equal
# to the Person class. Next, we do class << self, making self equal to Person's
# metaclass. When we then define the species method, it is defined
# on Person's metaclass:
class Person
class << self
def species
"Homo Sapien"
end
self.name #=> ""
end
end
class Person
attr_accessor :name
def initialize(name)
@name = name
end
end
class << Person
def race; 'Human' end
end
p = Person.new('Tiago')
puts p.name
def p.full_name
name + ' Guedes'
end
foobar =
class << p
def foobar
"Just a method named foobar"
end
end
puts foobar
puts foobar.class
puts p.full_name
puts Person.race
metaclass = class << p; self end
puts metaclass
puts metaclass.instance_methods.grep(/name/)
foo = Object.new
class << foo
def name; 'Foo' end
end
puts foo.name
class << Object
def cool_name; 'Uga Buga' end
end
foobar = Object.new
puts foo.cool_name rescue nil
puts foobar.cool_name rescue nil
puts Object.cool_name
class MailTruck
# Generate getters and setters.
attr_accessor :driver, :route
def initialize(driver, route)
@driver, @route = driver, route
end
end
m = MailTruck.new('Foobar', ['12 Corrigan Way', '23 Antler Ave'])
puts m.class #=> MailTruck
# Define instance variables.
m.instance_variable_set('@speed', 45)
m.instance_variable_set('@driver', 'Tiago')
puts m.speed rescue nil #=> nil
puts m.driver #=> "Tiago"
# Evals and <<
m.instance_eval do
def current_speed
"Current speed: #{@speed}"
end
end
puts m.current_speed #=> "Current speed: 45"
MailTruck.class_eval do
class << self
def foo; 'foo'; end
end
end
puts MailTruck.foo #=> "foo"
class << MailTruck
def bar; 'bar'; end
end
puts MailTruck.bar
# Object IDs
puts m.object_id #=> 70129925547360
puts MailTruck.object_id #=> 70129925547660
# Object and Class
o = Object.new
puts m.class #=> MailTruck
puts m.class.class #=> Class
puts m.class.superclass #=> Object
puts o.class #=> Object
puts Object.class #=> Class
puts Object.superclass #=> BasicObject
puts Object.superclass.superclass #=> nil
# Class
class Class
def awesome_method(*attrs)
print attrs
end
end
class MyNewClass
awesome_method :foo, :bar #=> [:foo, :bar]
end
class Class
def other_awesome_method(*attrs)
print attrs
end
end
class OtherNewClass
awesome_method :foo, :bar #=> [:foo, :bar]
other_awesome_method :lorem, :ipsum #=> [:lorem, :ipsum]
end
class Person
@@occupations = []
attr_accessor :name, :last_name
def initialize(name, last_name = nil)
@name, @last_name = name, last_name
end
def full_name; "#{@name} #{@last_name}" end
class << self
def occupation(name)
@@occupations << name
end
def occupations; @@occupations end
end
end
p = Person.new('Foo', 'Bar')
puts p.full_name #=> Foo Bar
class Programmer < Person
occupation self.name
end
prog = Programmer.new('Tiago', 'Guedes')
puts prog.full_name
class Teacher < Person
occupation self.name
end
puts Person.occupations #=> "Programmer"
#=> "Teacher"
puts Programmer.occupations #=> "Programmer"
#=> "Teacher"
puts Teacher.occupations #=> "Programmer"
#=> "Teacher"
class Foo
occupation self.name rescue nil
end
puts Person.occupations #=> "Programmer"
#=> "Teacher"
class Object
def metaclass
class << self; self; end
end
def meta_eval(&blk)
metaclass.instance_eval &blk
end
def meta_def(name, &blk)
meta_eval { define_method name, &blk }
end
end
class Person
class << self
@@occupations = []
def occupation(name)
meta_def :occupation do; @@occupations << name end
end
end
end
class Programmer < Person
occupation 'Programmer'
end
# TODO: check why it did not work:
puts Person.metaclass.methods.grep(/occ/)
puts Programmer.metaclass.methods.grep(/occ/)
# Constructing a sample of DSL.
class Mailer
def self.deliver(&block)
mail = MailBuilder.new(&block).mail
mail.send_mail
end
Mail = Struct.new(:from, :to, :subject, :body) do
def send_mail
fib(30)
puts "Email from: #{from}"
puts "Email to : #{to}"
puts "Subject : #{subject}"
puts "Body : #{body}"
end
def fib(n)
n < 2 ? n : fib(n-1) + fib(n-2)
end
end
class MailBuilder
def initialize(&block)
@mail = Mail.new
instance_eval(&block)
end
attr_reader :mail
%w(from to subject body).each do |m|
define_method(m) do |val|
@mail.send("#{m}=", val)
end
end
end
end
Mailer.deliver do
from "[email protected]"
to "[email protected]"
subject "Threading and Forking"
body "Some content"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment