Metaprogramming 101

Post on 08-May-2015

2858 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

Transcript

Metaprogramming 101Nando Vieira

codeplane.com.br

howtocode.com.br

O que veremosobject model, method dispatching, evaluation, hooks, DSLs.

selfsempre será o receiver padrão.

person.name

!"#"$%"!

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

class User attr_accessor :first_name, :last_name def initialize(options = {}) options.each do |name, value| send("#{name}=", value) end endend

User.new({ :first_name => "John", :last_name => "Doe"})

selfarmazena as variáveis de instância.

class User attr_accessor :first_name, :last_nameend

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

user.instance_variables#=> ["@last_name", "@first_name"]

variáveis de instânciaisso se aplica a todos os objetos.

class Config @directory = "/some/directory" @environment = :productionend

Config.instance_variables#=> ["@environment", "@directory"]

singleton_classmetaclasse, eigenclass, ghostclass.

class Config class << self endend

class Config singleton_class.class do # your code here endend

RUBY 1.9

class Object unless Object.respond_to?(:singleton_class) def singleton_class class << self; self; end end endend

RUBY 1.8

class Config def self.root_dir @root_dir end def self.root_dir=(dir) @root_dir = dir endend

class Config class << self attr_accessor :root_dir endend

estendendo o selfadicionando novos métodos.

person = Object.new

def person.name "John Doe"end

person.name#=> "John Doe"

person.singleton_methods#=> ["name"]

estendendo o selfadicionando novos métodos em classes.

class Config def Config.root_dir "/some/path" endend

Config.root_dir#=> "/some/path"

Config.singleton_methods#=> ["root_dir"]

class Config puts self == Configend

#=> true

class Config def Config.root_dir "/some/path" endend

Config.root_dir#=> "/some/path"

métodos de classeeles não existem no Ruby.

métodoslookup + dispatching.

method lookupup and right.

person.name

person class << person

Person class << Person

Class class << Class

Module class << Module

Object class << Object

BasicObject class << BasicObject

NoMethodError

method dispatchingexecuçã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) end endend

"#{name}="

evaluationclass_eval, instance_eval, instance_exec, eval.

class_evala.k.a. module_eval.

class Person; end

Person.class_eval do puts self == Personend

#=> true

class Person; end

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

#=> true

class Person; end

Person.class_eval do def self.some_class_method end def some_instance_method endend

class Person; end

module Helpers # some code hereend

Person.class_eval do include Helpersend

Person.class_eval do include Helpersend

class Person class_eval <<-RUBY, __FILE__, __LINE__ def some_method # some code end RUBYend

__FILE__, __LINE__

class Person class_eval <<-RUBY, __FILE__, __LINE__ def some_method raise "ffffuuuuuuuuuu" end RUBYend

begin Person.new.some_methodrescue Exception => error error.backtrace # ["person.rb:3:in `some_method'", "person.rb:10"]end # ["person.rb:3:in `some_method'", "person.rb:10"]

instance_evala.k.a. class_eval para instâncias.

Person = Class.newperson = Person.new

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

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

instance_execa.k.a. instance_eval on redbull.

require "ostruct"

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

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

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

evalevaluation com contexto configurável.

def get_binding name = "John Doe" bindingend

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

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

eval("name", get_binding)#=> "John Doe"

hooksinterceptando eventos do Ruby.

módulos & classesincluded, const_missing, extended, inherited, initialize_clone, initialize_copy, initialize_dup.

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

base.class_eval do include InstanceMethods extend ClassMethods end

class Person include Ffffffuuuend

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

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

métodosmethod_added, method_missing, method_removed, method_undefined, singleton_method_added, singleton_method_removed, singleton_method_undefined

method_missingexecutado 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" endend

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 endend

def respond_to?(method, include_private = false) return true if LEVELS.include?(method.to_sym) superend

DSLsfuckyeah.

interfaces fluenteschaining.

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

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 endend

class Message extend FluentAttribute fluent_attr :from, :to, :textend

dúvidas?

obrigadohttp://nandovieira.com.br.

top related