Metaprogramming 101
Nando Vieira
codeplane.com.br
ode.c om.br
how   toc
O que veremos
object model, method dispatching,
evaluation, hooks, DSLs.
self
sempre será o receiver padrão.
person.name
!"#"$%"!
class User
  attr_accessor :first_name, :last_name

  def fullname
    "#{first_name} #{last_name}"
  end
end
class User
  attr_accessor :first_name, :last_name

  def initialize(options = {})
    options.each do |name, value|
      send("#{name}=", value)
    end
  end
end

User.new({
   :first_name => "John",
   :last_name => "Doe"
})
self
armazena as variáveis de instância.
class User
  attr_accessor :first_name, :last_name
end

user = User.new
user.first_name = "John"
user.last_name = "Doe"

user.instance_variables
#=> ["@last_name", "@first_name"]
variáveis de instância
isso se aplica a todos os objetos.
class Config
  @directory = "/some/directory"
  @environment = :production
end

Config.instance_variables
#=> ["@environment", "@directory"]
singleton_class
metaclasse, eigenclass, ghostclass.
class Config
  class << self
  end
end
RUBY 1.9




class Config
  singleton_class.class do
    # your code here
  end
end
RUBY 1.8



class Object
  unless Object.respond_to?(:singleton_class)
    def singleton_class
      class << self; self; end
    end
  end
end
class Config
  def self.root_dir
    @root_dir
  end

  def self.root_dir=(dir)
    @root_dir = dir
  end
end
class Config
  class << self
    attr_accessor :root_dir
  end
end
estendendo o self
adicionando novos métodos.
person = Object.new

def person.name
  "John Doe"
end

person.name
#=> "John Doe"
person.singleton_methods
#=> ["name"]
estendendo o self
adicionando novos métodos em classes.
class Config
  def Config.root_dir
    "/some/path"
  end
end

Config.root_dir
#=> "/some/path"
Config.singleton_methods
#=> ["root_dir"]
class Config
  puts self == Config
end

#=> true
class Config
  def Config.root_dir
    "/some/path"
  end
end

Config.root_dir
#=> "/some/path"
métodos de classe
eles não existem no Ruby.
métodos
lookup + dispatching.
method lookup
up and right.
BasicObject    class << BasicObject



    Object     class << Object



    Module     class << Module



     Class     class << Class



    Person     class << Person



    person     class << person



 person.name
NoMethodError
method dispatching
execução de métodos.
person.name
person.send :name
person.send :say, "hello"
person.__send__ :name
person.public_send :name
class Person
  attr_accessor :name, :age, :email

  def initialize(options = {})
    options.each do |name, value|
      send("#{name}=", value)
           "#{name}="
    end
  end
end
evaluation
class_eval, instance_eval,
instance_exec, eval.
class_eval
a.k.a. module_eval.
class Person; end

Person.class_eval do
  puts self == Person
end

#=> true
class Person; end

Person.class_eval <<-RUBY
  puts self == Person
RUBY

#=> true
class Person; end

Person.class_eval do
  def self.some_class_method
  end

  def some_instance_method
  end
end
class Person; end

module Helpers
  # some code here
end

Person.class_eval do
  include Helpers
end
class Person
  class_eval <<-RUBY, __FILE__, __LINE__
    def some_method
       # some code
    end
  RUBY
end
class Person
  class_eval <<-RUBY, __FILE__, __LINE__
    def some_method
       raise "ffffuuuuuuuuuu"
    end
  RUBY
end

begin
  Person.new.some_method
rescue Exception => error
  error.backtrace
  # ["person.rb:3:in `some_method'", "person.rb:10"]
end
instance_eval
a.k.a. class_eval para instâncias.
Person = Class.new
person = Person.new

Person.respond_to?(:class_eval)      #=> true
Person.respond_to?(:instance_eval)   #=> true

person.respond_to?(:class_eval)      #=> false
person.respond_to?(:instance_eval)   #=> true
instance_exec
a.k.a. instance_eval on redbull.
require "ostruct"

john = OpenStruct.new(:name => "John Doe")

block = proc do |time = nil|
  puts name
  puts time
end

john.instance_eval(&block)
#=> John Doe
#=> #<OpenStruct name="John Doe">

john.instance_exec(Time.now, &block)
#=> John Doe
#=> 2011-07-08 11:44:01 -0300
eval
evaluation com contexto configurável.
def get_binding
  name = "John Doe"
  binding
end

eval("defined?(name)")
#=> nil

eval("defined?(name)", get_binding)
#=> "local-variable"

eval("name", get_binding)
#=> "John Doe"
hooks
interceptando eventos do Ruby.
módulos & classes
included, const_missing, extended,
inherited, initialize_clone,
initialize_copy, initialize_dup.
included
executado toda vez que um módulo é
incluído.
module Ffffffuuu
  def self.included(base)
    base.class_eval do
      include InstanceMethods
      extend ClassMethods
    end
  end

  module InstanceMethods
    # some instance methods
  end

  module ClassMethods
    # some class methods
  end
end
class Person
  include Ffffffuuu
end

Person.singleton_class.included_modules
#=> [Ffffffuuu::ClassMethods, Kernel]

Person.included_modules
#=> [Ffffffuuu::InstanceMethods, Ffffffuuu, Kernel]
métodos
method_added, method_missing,
method_removed, method_undefined,
singleton_method_added,
singleton_method_removed,
singleton_method_undefined
method_missing
executado toda vez que um método não
for encontrado.
class Logger
  attr_accessor :output

  LEVELS = [
    :debug, :info, :warn, :error, :critical
  ]

  def initialize(output)
    @output = output
  end

  def log(level, message)
    output << "[#{level}] #{message}n"
  end
end
logger = Logger.new(STDOUT)
logger.log :debug, "Fffffuuuuu"
class Logger
  attr_accessor :output

  LEVELS = [
    :debug, :info, :warn, :error, :critical
  ]

  def initialize(output)
    @output = output
  end

  def log(level, message)
    output << "[#{level}] #{message}n"
  end

  def method_missing(name, *args, &block)
    return send(:log, name, args.first) if LEVELS.include?(name)
    super
  end
end
def respond_to?(method, include_private = false)
  return true if LEVELS.include?(method.to_sym)
  super
end
DSLs
fuckyeah.
interfaces fluentes
chaining.
@message = Message.new
@message.to("john").from("mary").text("bring milk.")
@message.deliver
#=> "mary said: john, bring milk."
class Message
  def to(name)
    @to = name
    self
  end

  def from(name)
    @from = name
    self
  end

  def text(message)
    @text = message
    self
  end
end
module FluentAttribute
  def fluent_attr(*names)
    names.each do |name|
      class_eval <<-RUBY, __FILE__, __LINE__
        def #{name}(value)        # def to(value)
           @#{name} = value       #   @to = value
           self                   #   self
        end                       # end
      RUBY
    end
  end
end
class Message
  extend FluentAttribute

  fluent_attr :from, :to, :text
end
dúvidas?
obrigado
http://nandovieira.com.br.

Metaprogramming 101