Metaprogramming With Ruby
-
Upload
farooq-ali -
Category
Business
-
view
17.716 -
download
8
description
Transcript of Metaprogramming With Ruby
Metaprogramming Metaprogramming with Rubywith Ruby
Julie YaunchesFarooq Ali
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
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
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
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
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
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”
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.
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
Object IntrospectionObject Introspection
Instance variablesInstance variables
Instance MethodsInstance Methods
Class scope via singleton classClass scope via singleton class
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"]
Object Introspection:Object Introspection:MethodsMethods
> 'Away Day’.methods=> ["capitalize", "split", "strip", …]
> 5.public_methods=> ["+", "/", "integer?", …]
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
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?!
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
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
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
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
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
Calling methods Calling methods dynamicallydynamically
> a = "Hello World!"=> "Hello World!"
> a.downcase=> "hello world!"
> a.send(:downcase)=> "hello world!"
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…
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
CallbacksCallbacks
class VaultController < BankController before_filter :authorize
private def authorize user.can_access?(vault) endend
CallbacksCallbacks
class SymbolFilter < Filter def initialize(filter) @filter = filter end
def call(controller, &block) controller.send(@filter, &block) endend
Convention-Oriented CodingConvention-Oriented Codingclass CreditCard def validate validate_number
validate_expiration end
private def validate_number
... end
def validate_expiration ... endend
Convention-Oriented CodingConvention-Oriented Coding
def validate methods.grep /^validate_/ do |m| self.send m endend
Convention-Oriented CodingConvention-Oriented Coding
class CreditCard validate :number, :expirationend
Convention-Oriented CodingConvention-Oriented Coding
def validate(*validations) validations.each do |v| self.send "validate_#{v}" endend
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
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
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;} }}
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
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
Another example: Lazy Another example: Lazy LoadingLoading
class Person List<Person> friends; public List<Person> Friends { get { friends = friends ?? LoadFriends(); return friends; } }
Lazy LoadingLazy Loading
class Person lazy_loaded_attr :friendsend
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
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; } }}
Declarative codeDeclarative code
class Album < ActiveRecord::Base has_many :tracks belongs_to :artist acts_as_taggable has_many :lyrics, :through => :tracksend
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 …
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
Person Person
walk()
define_method :walk
define_method :everyone
? Person
everyone()
Define instance method:
Define class method:
Person Person
walk()
define_method :walk
define_method :everyone
Person'
Person'
everyone()
Define instance method:
Define class method:
Person Person
walk()
define_method :walk
define_method :everyone
Person'
Person'
everyone()
Define instance method:
Define class method:
Person
everyone()
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
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>
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.
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
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
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
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
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
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
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"
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
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
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
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
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
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
Questions?Questions?