├── meta ├── blocks │ ├── 02.rb │ ├── 01.rb │ ├── 04.rb │ ├── 05.rb │ └── 03.rb ├── method_missing │ ├── 01.rb │ ├── 02.rb │ └── 03.rb ├── macros │ ├── 02.rb │ ├── 01.rb │ ├── 05.rb │ ├── 03.rb │ └── 04.rb └── define_method │ ├── 01.rb │ ├── 02.rb │ └── 03.rb ├── object_model ├── 08.rb ├── 09.rb ├── 07.rb ├── 06.rb ├── 11.rb ├── 03.rb ├── 02.rb ├── 04.rb ├── 12.rb ├── 01.rb ├── 13.rb ├── 14.rb ├── 10.rb └── 05.rb └── README.rdoc /meta/blocks/02.rb: -------------------------------------------------------------------------------- 1 | say = lambda { puts "hi" } 2 | 3 | say.call -------------------------------------------------------------------------------- /meta/method_missing/01.rb: -------------------------------------------------------------------------------- 1 | class Person 2 | end 3 | 4 | p = Person.new 5 | p.name -------------------------------------------------------------------------------- /meta/blocks/01.rb: -------------------------------------------------------------------------------- 1 | [1, 2].each {|i| puts i } 2 | 3 | [1, 2].each do |i| 4 | puts i 5 | end -------------------------------------------------------------------------------- /meta/blocks/04.rb: -------------------------------------------------------------------------------- 1 | msg = "hi" 2 | [1, 2].each {|msg| puts msg} 3 | puts msg 4 | 5 | # This changes in 1.9.1 ? -------------------------------------------------------------------------------- /object_model/08.rb: -------------------------------------------------------------------------------- 1 | class Guy 2 | def self.chug 3 | "CHUG!" 4 | end 5 | end 6 | 7 | puts Guy.chug -------------------------------------------------------------------------------- /meta/macros/02.rb: -------------------------------------------------------------------------------- 1 | class Person 2 | def self.say(msg) 3 | puts msg 4 | end 5 | 6 | say "hello" 7 | end -------------------------------------------------------------------------------- /object_model/09.rb: -------------------------------------------------------------------------------- 1 | class Guy 2 | class << self 3 | def chug 4 | "CHUG!" 5 | end 6 | end 7 | end 8 | 9 | puts Guy.chug -------------------------------------------------------------------------------- /meta/macros/01.rb: -------------------------------------------------------------------------------- 1 | class Person 2 | attr_accessor :name 3 | end 4 | 5 | p = Person.new 6 | puts p.name 7 | p.name = "Jack" 8 | puts p.name -------------------------------------------------------------------------------- /object_model/07.rb: -------------------------------------------------------------------------------- 1 | class Guy 2 | # ... 3 | end 4 | 5 | puts defined?(Guy) 6 | 7 | Person = Class.new 8 | p = Person.new 9 | puts p.inspect -------------------------------------------------------------------------------- /meta/define_method/01.rb: -------------------------------------------------------------------------------- 1 | class Person 2 | define_method :say do |msg| 3 | puts msg 4 | end 5 | end 6 | 7 | p = Person.new 8 | p.say("hi") -------------------------------------------------------------------------------- /meta/blocks/05.rb: -------------------------------------------------------------------------------- 1 | def converse 2 | puts "hello" 3 | yield 4 | puts "bye" 5 | end 6 | 7 | converse do 8 | puts "how are you?" 9 | end 10 | -------------------------------------------------------------------------------- /meta/macros/05.rb: -------------------------------------------------------------------------------- 1 | class Animal 2 | def self.say(msg) 3 | puts msg 4 | end 5 | end 6 | 7 | class Person < Animal 8 | say "hello" 9 | end 10 | -------------------------------------------------------------------------------- /object_model/06.rb: -------------------------------------------------------------------------------- 1 | class Guy 2 | def name 3 | "Jack Daniels" 4 | end 5 | end 6 | 7 | jack = Guy.new 8 | 9 | puts jack.class 10 | puts Guy.class -------------------------------------------------------------------------------- /meta/macros/03.rb: -------------------------------------------------------------------------------- 1 | module Speech 2 | def say(msg) 3 | puts msg 4 | end 5 | end 6 | 7 | class Person 8 | extend Speech 9 | say "hello" 10 | end 11 | -------------------------------------------------------------------------------- /object_model/11.rb: -------------------------------------------------------------------------------- 1 | module Mana 2 | def tap 3 | puts "tapped!" 4 | end 5 | end 6 | 7 | class Land 8 | include Mana 9 | end 10 | 11 | swamp = Land.new 12 | swamp.tap -------------------------------------------------------------------------------- /object_model/03.rb: -------------------------------------------------------------------------------- 1 | class Person 2 | def strength 3 | 33 4 | end 5 | end 6 | 7 | class Guy < Person 8 | def name 9 | "Jack Daniels" 10 | end 11 | end 12 | 13 | jack = Guy.new 14 | 15 | puts jack.strength -------------------------------------------------------------------------------- /object_model/02.rb: -------------------------------------------------------------------------------- 1 | class Guy 2 | def name 3 | puts self.inspect 4 | "Jack Daniels" 5 | end 6 | end 7 | 8 | jack = Guy.new 9 | 10 | puts self.inspect 11 | 12 | # "Go to the right." 13 | puts jack.name 14 | 15 | puts self.inspect -------------------------------------------------------------------------------- /object_model/04.rb: -------------------------------------------------------------------------------- 1 | class Guy 2 | def name 3 | "Jack Daniels" 4 | end 5 | end 6 | 7 | jack = Guy.new 8 | 9 | def jack.name 10 | puts "Mmm, breakfast of champions." 11 | end 12 | 13 | jack.sip 14 | 15 | fred = Guy.new 16 | fred.sip -------------------------------------------------------------------------------- /object_model/12.rb: -------------------------------------------------------------------------------- 1 | module Mana 2 | def tap 3 | puts "tapped!" 4 | end 5 | end 6 | 7 | class Land 8 | self.extend Mana 9 | # self -> Land 10 | end 11 | 12 | Land.tap 13 | 14 | swamp = Object.new 15 | swamp.extend Mana 16 | swamp.tap -------------------------------------------------------------------------------- /meta/blocks/03.rb: -------------------------------------------------------------------------------- 1 | say = lambda { |msg| puts msg } 2 | 3 | say.call("hi") 4 | 5 | b = lambda {|name, msg| puts "#{name} said #{msg}"} 6 | 7 | b.call('Rick', 'Hi') 8 | b.call('Joe', 'Hi!') 9 | b.call('Rick', 'Bye Bye') 10 | b.call('Joe', 'Bye Bye!') 11 | -------------------------------------------------------------------------------- /meta/define_method/02.rb: -------------------------------------------------------------------------------- 1 | def attr_accessor(name) 2 | 3 | define_method(name) do 4 | instance_variable_get "@#{name}" 5 | end 6 | 7 | define_method("#{name}=") do |value| 8 | instance_variable_set "@#{name}", value 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /meta/macros/04.rb: -------------------------------------------------------------------------------- 1 | module Speech 2 | def self.included(base) 3 | base.extend ClassMethods 4 | end 5 | 6 | module ClassMethods 7 | def say(msg) 8 | puts msg 9 | end 10 | end 11 | end 12 | 13 | class Person 14 | include Speech 15 | say "hello" 16 | end 17 | -------------------------------------------------------------------------------- /meta/define_method/03.rb: -------------------------------------------------------------------------------- 1 | class Module 2 | def aa(name) 3 | define_method("#{name}=") { |value| instance_variable_set("@#{name}", value) } 4 | define_method(name) { instance_variable_get("@#{name}") } 5 | end 6 | end 7 | 8 | class Person 9 | aa :name 10 | end 11 | 12 | p = Person.new 13 | puts p.name 14 | 15 | p.name = "Rick Bob" 16 | puts p.name -------------------------------------------------------------------------------- /meta/method_missing/02.rb: -------------------------------------------------------------------------------- 1 | # class Person 2 | # def method_missing(method_id, *args) 3 | # puts "You tried to call #{method_id} with 4 | # #{args.join(', ')} on an instance of #{self.class.name}" 5 | # end 6 | # end 7 | # 8 | # p = Person.new 9 | # p.get_crazy("O", "M", "G") 10 | 11 | # name, age 12 | 13 | class Person < ActiveRecord::Base 14 | 15 | end 16 | 17 | p = Person.find_by_name_and_age("matt", 28) -------------------------------------------------------------------------------- /object_model/01.rb: -------------------------------------------------------------------------------- 1 | class Person 2 | def name=(val) 3 | @name = val 4 | end 5 | 6 | def name 7 | @name 8 | end 9 | end 10 | 11 | fred = Person.new 12 | fred.name = 'Fred' 13 | 14 | puts fred.name 15 | 16 | jack = Person.new 17 | jack.name = 'Jack' 18 | 19 | puts jack.name 20 | 21 | class Person 22 | def name=(val) 23 | @name = val 24 | end 25 | 26 | def name 27 | @name + "OMG" 28 | end 29 | end 30 | 31 | puts fred.name 32 | puts jack.name 33 | -------------------------------------------------------------------------------- /object_model/13.rb: -------------------------------------------------------------------------------- 1 | module Mana 2 | def self.included(base) 3 | puts "Mana just got included in #{base.name}" 4 | end 5 | 6 | def self.extended(base) 7 | puts "Mana just extended #{base.name}" 8 | end 9 | 10 | def tap 11 | puts "tapped!" 12 | end 13 | end 14 | 15 | class Land 16 | include Mana # Mana.included(self) == Mana.included(Land) 17 | end 18 | 19 | class Creature 20 | end 21 | 22 | Creature.extend(Mana) # Mana.extended(self) == Mana.extended(Creature) -------------------------------------------------------------------------------- /object_model/14.rb: -------------------------------------------------------------------------------- 1 | 2 | class Bob 3 | end 4 | 5 | b = Bob.new 6 | 7 | Class.new() 8 | 9 | 10 | module Mana 11 | def self.included(base) 12 | base.extend ClassMethods 13 | end 14 | 15 | def tap 16 | puts "tapped!" 17 | end 18 | 19 | module ClassMethods 20 | def class_method 21 | puts "a class method on #{name}" 22 | end 23 | end 24 | end 25 | 26 | class Land 27 | include Mana 28 | end 29 | 30 | swamp = Land.new 31 | swamp.tap 32 | 33 | Land.class_method -------------------------------------------------------------------------------- /object_model/10.rb: -------------------------------------------------------------------------------- 1 | # defined?(Mana) ? Mana : Module.new 2 | 3 | module Mana 4 | def tap 5 | puts "tapped!" 6 | end 7 | 8 | def self.module_method 9 | end 10 | end 11 | 12 | # Mana.new 13 | 14 | # class Land < Mana 15 | # end 16 | 17 | # it is an instance like any other ruby object 18 | def Mana.module_method 19 | puts "weird" 20 | end 21 | 22 | Mana.module_method 23 | 24 | module Mana 25 | def self.another_module_method 26 | puts "strange" 27 | end 28 | end 29 | 30 | Mana.another_module_method -------------------------------------------------------------------------------- /object_model/05.rb: -------------------------------------------------------------------------------- 1 | class Guy 2 | def name 3 | "Jack Daniels" 4 | end 5 | end 6 | 7 | jack = Guy.new 8 | 9 | def jack.sip 10 | puts "Mmm, breakfast of champions." 11 | end 12 | 13 | # metaclass is invisible 14 | puts jack.class 15 | 16 | # but class << is special syntax to get to the metaclass 17 | class << jack 18 | self 19 | end 20 | 21 | # metaclass = class << jack; self; end 22 | # puts metaclass.instance_methods.grep(/sip/).inspect 23 | 24 | fred = Guy.new 25 | metaclass = class << fred; self; end 26 | puts metaclass.instance_methods.grep(/sip/).inspect -------------------------------------------------------------------------------- /meta/method_missing/03.rb: -------------------------------------------------------------------------------- 1 | module ActiveRecord 2 | class Base 3 | def method_missing(method_id, *args, &block) 4 | case method_id 5 | when /^find_all_by_([_a-zA-Z]\w*)$/ 6 | define_method(method_id) do 7 | find(:all, :conditions => ..extracted from args..) 8 | end 9 | when /^find_by_([_a-zA-Z]\w*)$/ 10 | define_method(method_id) do 11 | find(:first, :conditions => ..extracted from args..) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | 18 | Person = Class.new 19 | Person.class_eval <<-STRING 20 | attr_accessor :name 21 | STRING -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Intro to Ruby Meta-Programming 2 | 3 | A collection of notes, codes and various further readings that accompanied the December 12th Hackday event. 4 | 5 | == What is Ruby meta-programming? 6 | 7 | Using one's understanding of the Ruby object model to create methods in the "right place" based on decisions made at runtime. 8 | 9 | Using Ruby's interpreted environment to create code based on runtime decisions. 10 | 11 | == Object Model 12 | 13 | === Objects 14 | 15 | link://gotascii/metaprogramming/blob/master/object_model/01.rb 16 | 17 | * State and the things that can be done to that state define an object. 18 | * Lots of Person objects can be created each with their own state. 19 | * Every Person has same set of methods. 20 | * Adding/changing a method on the Person class has to apply to all person instances. 21 | * Inefficient to put the methods in the instances. 22 | * Objects contain a pointer to its class that has a list of methods. 23 | * In order to call a method, Ruby looks up class then method. 24 | * Draw classes to the right, so first step to find a method is "go right". 25 | 26 | === Self 27 | 28 | link://gotascii/metaprogramming/blob/master/object_model/02.rb 29 | 30 | * Self always references current object. 31 | * What is the "current object"? 32 | * The default receiver of method calls. 33 | * Location of instance variables. 34 | * "Every line of code is executed against a particular self." - Katz 35 | 36 | How does self get set? 37 | 38 | One way is to call a method with an explicit receiver. 39 | 40 | 1. Switch self to receiver 41 | 2. Lookup method is self's class 42 | 3. Invoke the method 43 | 44 | "Go to the right." link://gotascii/metaprogramming/blob/master/object_model/02.rb 45 | 46 | * Super contains a pointer to super class 47 | * Method resolution follows this until it finds the method 48 | 49 | "Go to the right then up." link://gotascii/metaprogramming/blob/master/object_model/03.rb 50 | 51 | === Metaclasses (anonymous, singleton, ...) 52 | 53 | link://gotascii/metaprogramming/blob/master/object_model/04.rb 54 | 55 | * Every object gets its own invisible class, called a metaclass. 56 | * We can get to the class using class << obj 57 | * def has a feature, when called with an object it adds the method to the metaclass. 58 | 59 | link://gotascii/metaprogramming/blob/master/object_model/05.rb 60 | 61 | * Even with the presence of this metaclass, the above rules still apply! 62 | 63 | === Classes 64 | 65 | link://gotascii/metaprogramming/blob/master/object_model/06.rb 66 | 67 | * Classes are instances of class Class. 68 | * Provide a place to store method implementation for instances. 69 | * Class names are constants that point to a class instances. 70 | 71 | link://gotascii/metaprogramming/blob/master/object_model/07.rb 72 | 73 | * Class definition is another way to change self. 74 | * Inside class definition self changes to instance of the class. 75 | * Since classes are just instances, all of the method resolution rules we talked about apply. 76 | 77 | link://gotascii/metaprogramming/blob/master/object_model/08.rb 78 | 79 | * Class methods are methods on a classes metaclass. 80 | * This means there are no "class methods" in ruby! 81 | 82 | link://gotascii/metaprogramming/blob/master/object_model/09.rb 83 | 84 | == Modules 85 | 86 | link://gotascii/metaprogramming/blob/master/object_model/10.rb 87 | 88 | * Can have no instances. 89 | * Can have no subclasses. 90 | * It is an instance of Module. 91 | * Switches self to current module instance, just like class. 92 | * Provides a place to store and share (mixin) methods. 93 | 94 | === Include 95 | 96 | link://gotascii/metaprogramming/blob/master/object_model/11.rb 97 | 98 | * Creates an invisible class that proxies method lookups to the module 99 | * Makes the class the receiver's immediate superclass. 100 | * Include is a private method. 101 | 102 | === Extend 103 | 104 | link://gotascii/metaprogramming/blob/master/object_model/12.rb 105 | 106 | * Adds the invisible class to the receiver's metaclass. 107 | * Extend is a public method. 108 | 109 | === Callbacks & ClassMethods 110 | 111 | link://gotascii/metaprogramming/blob/master/object_model/13.rb 112 | 113 | * Modules can define module methods that are called whenever they are included or used to extended. 114 | 115 | link://gotascii/metaprogramming/blob/master/object_model/14.rb 116 | 117 | * In order to extend a class when you include a module use the ClassMethods technique. 118 | 119 | == Macros 120 | 121 | link://gotascii/metaprogramming/blob/master/meta/macros/01.rb 122 | 123 | * Attr methods are defined on the Module class, can be used whenever. 124 | * Self is a class or module. 125 | * They 'look' like keywords, but are actually class methods used in class definition. 126 | 127 | === Create a macro through a class method 128 | 129 | link://gotascii/metaprogramming/blob/master/meta/macros/02.rb 130 | 131 | * Since self.name is being defined on the class it can then be used as a macro within that class. 132 | 133 | === Create a macro through a module 134 | 135 | link://gotascii/metaprogramming/blob/master/meta/macros/03.rb 136 | 137 | * Person class is being extended with Speech module, all instance level methods on Speech are now class methods on Person. 138 | 139 | link://gotascii/metaprogramming/blob/master/meta/macros/04.rb 140 | 141 | * Speech module now has a hook method 'included' on it, this method will take the class including Speech and call extend ClassMethods on that class, thereby extending Person to have the class method say. 142 | 143 | === Macros defined in a parent class, are inherited on child classes 144 | 145 | link://gotascii/metaprogramming/blob/master/meta/macros/05.rb 146 | 147 | == Blocks 148 | 149 | === Calling a block, two ways 150 | 151 | link://gotascii/metaprogramming/blob/master/meta/blocks/01.rb 152 | 153 | * Blocks are chunks of code between { } or do end. 154 | * Blocks are like the body of code in an anonymous method. 155 | * Blocks can take args just like methods, passed differently using | |. 156 | 157 | === Creating a block 158 | 159 | === Creating a block that takes arguments 160 | 161 | === Iterators 162 | 163 | link://gotascii/metaprogramming/blob/master/meta/blocks/05.rb 164 | 165 | * Iterators are methods that can invoke a block of code. 166 | * Blocks passed to methods have scope remembered before the method is entered. 167 | 168 | === Blocks, Procs, Lambdas, Methods 169 | 170 | * Blocks - Evaluated in the scope in which they’re defined. 171 | * Procs - Objects of class Proc. Like blocks, they are evaluated in the scope where they’re defined. 172 | * Lambdas — Objects of class Proc but subtly different from regular procs, they require the passing of the arguments they were defined with. They’re closures like blocks and procs, and as such they’re evaluated in the scope where they’re defined. 173 | * Methods - Bound to an object, they are evaluated in that object’s scope. They can also be unbound from their scope and rebound to the scope of another object. 174 | 175 | == define_method 176 | 177 | link://gotascii/metaprogramming/blob/master/meta/define_method/01.rb 178 | 179 | * Module#define_method 180 | 181 | === Example with arguments 182 | 183 | link://gotascii/metaprogramming/blob/master/meta/define_method/02.rb 184 | 185 | * Note, what we've done here is dynamically created macros 186 | 187 | === Let's make our own attr_accessor, called aa 188 | 189 | * Define a method called aa on Module, this method takes one argument and dynamically creates a getter and setter method based on the name of that argument. 190 | 191 | == method_missing 192 | 193 | === NoMethodError example 194 | 195 | link://gotascii/metaprogramming/blob/master/meta/method_missing/01.rb 196 | 197 | * Method lookup: Ruby goes into p's class and browses it's instance methods. Since it can't find it there, ruby goes up the ancestral chain into Object and finally Kernal. If it still cannot find the method, Ruby will raise an undefined method exception. 198 | 199 | === Overwriting method_missing example - 02.rb 200 | 201 | link://gotascii/metaprogramming/blob/master/meta/method_missing/02.rb 202 | 203 | * There is an instance method on Kernel named method_missing, this is the last thing Ruby checks before raising an exception. If we overwrite this method in the Person class, we can intercept Ruby while it's doing it's method lookup. 204 | 205 | === ActiveRecord's dynamic find methods 206 | 207 | link://gotascii/metaprogramming/blob/master/meta/method_missing/03.rb 208 | 209 | * ActiveRecord uses method_missing to intercept find_by_* and find_all_by_* calls. In order to reduce the cost of these expensive method missing calls, AR will dynamically define the find_by method after the first time it's called. 210 | 211 | == Further Reading 212 | 213 | http://ruby-metaprogramming.heroku.com 214 | An online meta-programming course. 215 | 216 | http://pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming 217 | Commercial screencasts on meta-programming by Dave Thomas. 218 | 219 | http://pragprog.com/titles/ppmetr/metaprogramming-ruby 220 | A whole book on this stuff. 221 | 222 | http://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self 223 | A concise summary of meta-programming in Ruby by Yehuda Katz. --------------------------------------------------------------------------------