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