Metaprogramming With Ruby

63
Metaprogramming Metaprogramming with Ruby with Ruby Julie Yaunches Farooq Ali

description

Presented at the November '07 ThoughtWorks UK Away Day, the talk was centered around the various techniques used to create and mold your objects and domain model using declaritive, self-inspecting, and self-modifying code.

Transcript of Metaprogramming With Ruby

Page 1: Metaprogramming With Ruby

Metaprogramming Metaprogramming with Rubywith Ruby

Julie YaunchesFarooq Ali

Page 2: Metaprogramming With Ruby

What is Metaprogramming?What is Metaprogramming?

Code that writes codeCode that writes code

Know ThyselfKnow Thyself

Reflection.kind_of? Reflection.kind_of? MetaprogrammingMetaprogramming

Compile-time vs. RuntimeCompile-time vs. Runtime

Page 3: Metaprogramming With Ruby

Metaprogramming == Metaprogramming == ProgrammingProgramming

No natural separationNo natural separation

Ruby is interpreted, not compiledRuby is interpreted, not compiled

Everything’s an object. Even classes!Everything’s an object. Even classes!

Extensible type systemExtensible type system

"Meta, shmeta! "Meta, shmeta! Learning Ruby isn’t like Learning Ruby isn’t like scaling a mountain. It’s more like scaling a mountain. It’s more like

exploring a plain.exploring a plain.""- David Black- David Black

Page 4: Metaprogramming With Ruby

Exploring the plainExploring the plain

define_method

alias_method

foo.send :bar

instance_variable_get

class_eval

instance_eval

module_eval

instance_variable_set

eval

block.call

Class.new

method_missing

class << self

block.binding

Foo.instance_methods

method_added

method_removed

Page 5: Metaprogramming With Ruby

Ruby object model I: classes, objects, Ruby object model I: classes, objects, modules and blocksmodules and blocks

Object introspectionObject introspection Blocks and the strategy patternBlocks and the strategy pattern Dynamic methodsDynamic methods Ruby object model II: singletons and open Ruby object model II: singletons and open

classesclasses Method aliasing and the decorator patternMethod aliasing and the decorator pattern Evaluation techniquesEvaluation techniques Object lifecycle hooks and method_missingObject lifecycle hooks and method_missing

OutlineOutline

Page 6: Metaprogramming With Ruby

Ruby Object Model IRuby Object Model I

Everything is an Everything is an instance of Objectinstance of Object

Every object has a Every object has a number of things:number of things:

klass, superclass, klass, superclass, methods methods

Developer = Class.new Developer = Class.new

julie = Developer.newjulie = Developer.new

Page 7: Metaprogramming With Ruby

Ruby Object Model IRuby Object Model I

Module – a collection of methods and constantsModule – a collection of methods and constants– Methods can be instance methods or module methodsMethods can be instance methods or module methods– Modules can be ‘mixedin’ to a class.Modules can be ‘mixedin’ to a class.

module Britishmodule British

def goes_to_pubdef goes_to_pub

“ “I love beer and geeky talk”I love beer and geeky talk”

endend

endend

class Developerclass Developer

include Britishinclude British

endend

julie.goes_to_pubjulie.goes_to_pub => “I love beer and geeky talk”=> “I love beer and geeky talk”

Page 8: Metaprogramming With Ruby

Ruby Object Model IRuby Object Model I

When you create When you create a module, it’s a module, it’s methods become methods become available to the available to the instances of the instances of the class via a proxy class via a proxy class.class.

Page 9: Metaprogramming With Ruby

Ruby object model I: classes, objects, Ruby object model I: classes, objects, modules and blocksmodules and blocks

Object introspectionObject introspection Blocks and the strategy patternBlocks and the strategy pattern Dynamic methodsDynamic methods Ruby object model II: singletons and open Ruby object model II: singletons and open

classesclasses Method aliasing and the decorator patternMethod aliasing and the decorator pattern Evaluation techniquesEvaluation techniques Object lifecycle hooks and method_missingObject lifecycle hooks and method_missing

OutlineOutline

Page 10: Metaprogramming With Ruby

Object IntrospectionObject Introspection

Instance variablesInstance variables

Instance MethodsInstance Methods

Class scope via singleton classClass scope via singleton class

Page 11: Metaprogramming With Ruby

Object Introspection:Object Introspection:Instance VariablesInstance Variables

> @event = 'Away Day'> instance_variable_get '@event'=> "Away Day"

> instance_variable_set '@region', 'UK'> @region=> "UK"

> instance_variables=> ["@event", "@region"]

Page 12: Metaprogramming With Ruby

Object Introspection:Object Introspection:MethodsMethods

> 'Away Day’.methods=> ["capitalize", "split", "strip", …]

> 5.public_methods=> ["+", "/", "integer?", …]

Page 13: Metaprogramming With Ruby

Object Introspection:Object Introspection:MethodsMethods

> 'abc'.method(:capitalize)=> #<Method: String#capitalize>

class

method

strip

capitalize

String

gsub

split

class

method

unbind

arity

Method

to_proc

call

Page 14: Metaprogramming With Ruby
Page 15: Metaprogramming With Ruby

Introspection in ActionIntrospection in Action

class Person def initialize(name, age, sex) @name = name @age = age @sex = sex endend

Person.new('Farooq', 23, :male)

Did we just think in a for-loop?!

Page 16: Metaprogramming With Ruby

Introspection in ActionIntrospection in Action

class Person def initialize(attributes) attributes.each do |attr, val| instance_variable_set("@#{attr}", val) end endend

Person.new :name => 'Farooq', :age => '23',:sex => :male

Page 17: Metaprogramming With Ruby

Ruby object model I: classes, objects, Ruby object model I: classes, objects, modules and blocksmodules and blocks

Object introspectionObject introspection Blocks and the strategy patternBlocks and the strategy pattern Dynamic methodsDynamic methods Ruby object model II: singletons and open Ruby object model II: singletons and open

classesclasses Method aliasing and the decorator patternMethod aliasing and the decorator pattern Evaluation techniquesEvaluation techniques Object lifecycle hooks and method_missingObject lifecycle hooks and method_missing

OutlineOutline

Page 18: Metaprogramming With Ruby

BlocksBlocks Blocks are basically nameless

functions. You can pass a a block to another function, and then that function can invoke the passed-in nameless function.

def foo yieldendfoo {puts “foo”} => “foo”foo {puts “bar”} => “bar”

Iterating through a collection

Records.each do |record|print_title

end

Encapsulating error prone procedures

open(“some_file”) do |input|input.each_line do |line|

process_input_line lineend

end

Page 19: Metaprogramming With Ruby

Ruby object model I: classes, objects, Ruby object model I: classes, objects, modules and blocksmodules and blocks

Object introspectionObject introspection Blocks and the strategy patternBlocks and the strategy pattern Dynamic methodsDynamic methods Ruby object model II: singletons and open Ruby object model II: singletons and open

classesclasses Method aliasing and the decorator patternMethod aliasing and the decorator pattern Evaluation techniquesEvaluation techniques Object lifecycle hooks and method_missingObject lifecycle hooks and method_missing

OutlineOutline

Page 20: Metaprogramming With Ruby

Calling methods dynamicallyCalling methods dynamically

Convention-Oriented codingConvention-Oriented coding

Defining them on the flyDefining them on the fly

Writing declarative codeWriting declarative code

Dynamic MethodsDynamic Methods

Page 21: Metaprogramming With Ruby

Calling methods Calling methods dynamicallydynamically

> a = "Hello World!"=> "Hello World!"

> a.downcase=> "hello world!"

> a.send(:downcase)=> "hello world!"

Page 22: Metaprogramming With Ruby
Page 23: Metaprogramming With Ruby

Calling methods Calling methods dynamically:dynamically:

Why should I care?Why should I care? Parameterized method namesParameterized method names

Callbacks and ObserversCallbacks and Observers

Convention-oriented codeConvention-oriented code

Working with method collectionsWorking with method collections

and more…and more…

Page 24: Metaprogramming With Ruby

Parameterized method Parameterized method namesnames

controller: @person = Person.new('Farooq')

view:<%= text_field :person, :name %>

def text_field(obj, method) iv = instance_variable_get "@#{obj}" val = iv.send(method) "<input type='text' value='#{val}'>"end

Page 25: Metaprogramming With Ruby

CallbacksCallbacks

class VaultController < BankController before_filter :authorize

private def authorize user.can_access?(vault) endend

Page 26: Metaprogramming With Ruby

CallbacksCallbacks

class SymbolFilter < Filter def initialize(filter) @filter = filter end

def call(controller, &block) controller.send(@filter, &block) endend

Page 27: Metaprogramming With Ruby

Convention-Oriented CodingConvention-Oriented Codingclass CreditCard def validate validate_number

validate_expiration end

private def validate_number

... end

def validate_expiration ... endend

Page 28: Metaprogramming With Ruby

Convention-Oriented CodingConvention-Oriented Coding

def validate methods.grep /^validate_/ do |m| self.send m endend

Page 29: Metaprogramming With Ruby

Convention-Oriented CodingConvention-Oriented Coding

class CreditCard validate :number, :expirationend

Page 30: Metaprogramming With Ruby

Convention-Oriented CodingConvention-Oriented Coding

def validate(*validations) validations.each do |v| self.send "validate_#{v}" endend

Page 31: Metaprogramming With Ruby

You can You can define define methods on methods on the fly too!the fly too!

define_method :say_hello do puts "Hello World!"end

Page 32: Metaprogramming With Ruby

define_methoddefine_method

Defines an instance method on a classDefines an instance method on a class

Can define class methods via singletonCan define class methods via singleton

Great for writing declarative codeGreat for writing declarative code

Page 33: Metaprogramming With Ruby
Page 34: Metaprogramming With Ruby

Dynamic Method Definition in Dynamic Method Definition in ActionAction

class Person attr_reader :name, sexend

class Person { string name, sex; public string Name { get {return name;} } public string Sex { get {return sex;} }}

Page 35: Metaprogramming With Ruby

Dynamic Method Definition in Dynamic Method Definition in ActionAction

class Object def Object.attr_reader(*attrs) attrs.each do |attr| define_method(attr) do instance_variable_get("@#{attr}") end end endend

Page 36: Metaprogramming With Ruby

Dynamic Method Definition in Dynamic Method Definition in ActionAction

class Object def Object.attr_reader(*attrs) attrs.each do |attr| define_method(attr) do instance_variable_get("@#{attr}") end end endend

Page 37: Metaprogramming With Ruby

Another example: Lazy Another example: Lazy LoadingLoading

class Person List<Person> friends; public List<Person> Friends { get { friends = friends ?? LoadFriends(); return friends; } }

Page 38: Metaprogramming With Ruby

Lazy LoadingLazy Loading

class Person lazy_loaded_attr :friendsend

Page 39: Metaprogramming With Ruby

Lazy LoadingLazy Loading

class Object def self.lazy_loaded_attr(*attrs) attrs.each do |attr| define_method(attr) do eval "@#{attr} ||= load_#{attr}" end end endend

Page 40: Metaprogramming With Ruby

Lazy LoadingLazy Loadingclass Person lazy_loaded_attr :friends, :children, :parentsend

class Person List<Person> friends, children, parents; public List<Person> Friends { get { friends = friends ?? LoadFriends(); return friends; } } public List<Person> Children { get { children = children ?? LoadChildren(); return children; } } public List<Person> Parents { get { parents = parents ?? LoadParents(); return parents; } }}

Page 41: Metaprogramming With Ruby

Declarative codeDeclarative code

class Album < ActiveRecord::Base has_many :tracks belongs_to :artist acts_as_taggable has_many :lyrics, :through => :tracksend

Page 42: Metaprogramming With Ruby

Declarative code with Declarative code with define_methoddefine_method

class Album < ActiveRecord::Base has_many :tracksend

class Album < ActiveRecord::Base def tracks Track.find :all, :conditions => "album_id=#{id}" endend

define_method :tracks …

Page 43: Metaprogramming With Ruby

Declarative code with Declarative code with define_methoddefine_method

class ActiveRecord::Base def self.has_many(records) define_method(records) do foreign_key = "#{self.name.underscore}_id" klass = records.to_s.classify.constantize klass.find :all, :conditions => "#{foreign_key}=#{id}" end endend

Page 44: Metaprogramming With Ruby

Person Person

walk()

define_method :walk

define_method :everyone

? Person

everyone()

Define instance method:

Define class method:

Page 45: Metaprogramming With Ruby

Person Person

walk()

define_method :walk

define_method :everyone

Person'

Person'

everyone()

Define instance method:

Define class method:

Page 46: Metaprogramming With Ruby

Person Person

walk()

define_method :walk

define_method :everyone

Person'

Person'

everyone()

Define instance method:

Define class method:

Person

everyone()

Page 47: Metaprogramming With Ruby

Ruby object model I: classes, objects, Ruby object model I: classes, objects, modules and blocksmodules and blocks

Object introspectionObject introspection Blocks and the strategy patternBlocks and the strategy pattern Dynamic methodsDynamic methods Ruby object model II: singletons and open Ruby object model II: singletons and open

classesclasses Method aliasing and the decorator patternMethod aliasing and the decorator pattern Evaluation techniquesEvaluation techniques Object lifecycle hooks and method_missingObject lifecycle hooks and method_missing

OutlineOutline

Page 48: Metaprogramming With Ruby

Ruby Object Model IIRuby Object Model II

You can open any object and define methods on it.You can open any object and define methods on it.

Developer = Class.newDeveloper = Class.newDeveloper.instance_eval doDeveloper.instance_eval do def pairing def pairing

“julie and farooq are pairing" “julie and farooq are pairing" end endendend

Developer.class_eval doDeveloper.class_eval do def write_code def write_code

“type type type" “type type type" end endendendDeveloper.write_code #=> undefined method ‘write_code’ for Developer.write_code #=> undefined method ‘write_code’ for Foo:ClassFoo:ClassDeveloper.new. write_code #=> “type type type"Developer.new. write_code #=> “type type type"Developer.pairing #=> " julie and farooq are pairing"Developer.pairing #=> " julie and farooq are pairing"Developer.new.pairing #=> undefined method ‘baz’ for Developer.new.pairing #=> undefined method ‘baz’ for #<Foo:0x7dce8>#<Foo:0x7dce8>

Page 49: Metaprogramming With Ruby

Ruby Object Model IIRuby Object Model IIWe’ve now opened and We’ve now opened and defined methods on both defined methods on both the instance of Class the instance of Class Developer and all Developer and all instances of Class instances of Class Developer.Developer.

– When you define When you define methods on an instance methods on an instance of an object, you are of an object, you are defining them on it’s defining them on it’s ‘metaclass’ or singleton.‘metaclass’ or singleton.

Page 50: Metaprogramming With Ruby

Ruby object model I: classes, objects, Ruby object model I: classes, objects, modules and blocksmodules and blocks

Object introspectionObject introspection Blocks and the strategy patternBlocks and the strategy pattern Dynamic methodsDynamic methods Ruby object model II: singletons and open Ruby object model II: singletons and open

classesclasses Method aliasingMethod aliasing Evaluation techniquesEvaluation techniques Object lifecycle hooks and method_missingObject lifecycle hooks and method_missing

OutlineOutline

Page 51: Metaprogramming With Ruby

Method AliasingMethod Aliasing

Clones a methodClones a method Popularly used in decoratorsPopularly used in decorators

– AOP "around advice" AOP "around advice" – LISP around method combinatorLISP around method combinator

Monkey patchesMonkey patches

class LoginService alias_method :original_login, :loginend

Page 52: Metaprogramming With Ruby

Decorator Pattern with Decorator Pattern with AliasingAliasing

class LoginService

alias_method :original_login, :login def login(user,pass) log_event "#{user} logging in" original_login(user,pass) end

end

Page 53: Metaprogramming With Ruby

Decorator Pattern with Decorator Pattern with AliasingAliasing

class LoginService

alias_method_chain :login, :logging def login_with_logging(user,pass) log_event "#{user} logging in" login_without_logging(user,pass) end

end

Page 54: Metaprogramming With Ruby

Ruby object model I: classes, objects, Ruby object model I: classes, objects, modules and blocksmodules and blocks

Object introspectionObject introspection Blocks and the strategy patternBlocks and the strategy pattern Dynamic methodsDynamic methods Ruby object model II: singletons and open Ruby object model II: singletons and open

classesclasses Method aliasing and the decorator patternMethod aliasing and the decorator pattern Evaluation techniquesEvaluation techniques Object lifecycle hooks and method_missingObject lifecycle hooks and method_missing

OutlineOutline

Page 55: Metaprogramming With Ruby

Evaluation TechniquesEvaluation Techniques

Evaluate code at runtimeEvaluate code at runtime

In instance and class scopesIn instance and class scopes

With different block bindingsWith different block bindings

eval, instance_eval, class_eval, eval, instance_eval, class_eval, module_evalmodule_eval

Page 56: Metaprogramming With Ruby

evaleval

> eval "foo = 5"> foo=> 5

Evaluates a stringEvaluates a string

Optional bindingOptional binding

def get_binding(str)return bindingendstr = "hello"eval "str + ' Fred'" ! "hello Fred"eval "str + ' Fred'", get_binding("bye") ! "bye Fred"

Page 57: Metaprogramming With Ruby

Ruby object model I: classes, objects, Ruby object model I: classes, objects, modules and blocksmodules and blocks

Object introspectionObject introspection Blocks and the strategy patternBlocks and the strategy pattern Dynamic methodsDynamic methods Ruby object model II: singletons and open Ruby object model II: singletons and open

classesclasses Method aliasing and the decorator patternMethod aliasing and the decorator pattern Evaluation techniquesEvaluation techniques Object lifecycle hooks and method_missingObject lifecycle hooks and method_missing

OutlineOutline

Page 58: Metaprogramming With Ruby

HooksHooks

Allow you to trap some event, such as object Allow you to trap some event, such as object creation…creation…

– Example – adding a timestamp to every object created. (Taken Example – adding a timestamp to every object created. (Taken from the pickaxe)from the pickaxe)

class Objectclass Object

attr_accessor :timestampattr_accessor :timestamp

endend

class Classclass Class

alias_method :old_new, :newalias_method :old_new, :new

def new(*args)def new(*args)

result = old_new(*args)result = old_new(*args)

result.timestamp = Time.nowresult.timestamp = Time.now

resultresult

endend

endend

Page 59: Metaprogramming With Ruby

HooksHooks

You can know when a Module was included in You can know when a Module was included in another Object definition.another Object definition.

module A module A def A.included(mod) def A.included(mod) puts "#{self} included in #{mod}“puts "#{self} included in #{mod}“ endendend end module Enumerable module Enumerable include Ainclude Aend end

This is done via a call to append_features on the This is done via a call to append_features on the module doing the includingmodule doing the including

Page 60: Metaprogramming With Ruby

HooksHooks method_missingmethod_missing method_addedmethod_added method_removedmethod_removed

You can hook into any of these and perform some operation You can hook into any of these and perform some operation when something happens to object under consideration.when something happens to object under consideration.

class Fooclass Foo

def method_missing(methodname, *args)def method_missing(methodname, *args) #log something #log something

#handle in some way#handle in some way

endendend end

Page 61: Metaprogramming With Ruby

HooksHooks

Can allow you to hook into how your plugins are Can allow you to hook into how your plugins are intialized with Rails.intialized with Rails.

Rails uses something called Rails::Plugin::Loader Rails uses something called Rails::Plugin::Loader – You can hook into thisYou can hook into this

Rails::Initializer.run do |config| Rails::Initializer.run do |config|

config.plugin_loader = PluginLoaderWithDependencies config.plugin_loader = PluginLoaderWithDependencies

EndEnd

Similarly, hooking into other framework type stuff Similarly, hooking into other framework type stuff (like ActiveRecord or even Subversion) is possible(like ActiveRecord or even Subversion) is possible

Page 62: Metaprogramming With Ruby

Metaprogramming with Metaprogramming with RubyRuby

No longer black magicNo longer black magic Fresh design patternsFresh design patterns Declarative programmingDeclarative programming Higher level of abstractionHigher level of abstraction Tone down the YAGNITone down the YAGNI

Page 63: Metaprogramming With Ruby

Questions?Questions?