Instantly share code, notes, and snippets.

Embed
What would you like to do?
Ruby Meta Programming
  • Dynamic Dispatch
  • Dynamic Method
  • Ghost Methods
  • Dynamic Proxies
  • Blank Slate
  • Kernel Method
  • Flattening the Scope (aka Nested Lexical Scopes)
  • Context Probe
  • Class Eval (not really a 'spell' more just a demonstration of its usage)
  • Class Macros
  • Around Alias
  • Hook Methods
  • Class Extension Mixin
  • Module Namespace Interpolation
# Dynamic Dispatch
# Allows us to send messages to even private methods
# object.send(message, *arguments)
1.send(:+, 2) # => 3
# Dynamic Method
# Allows us to dynamically create methods
# define_method :method_name { block that becomes method body }
class Foo
define_method :bar do
puts "This is a dynamic method"
end
end
Foo.new.bar # => "This is a dynamic method"
# Dynamic Method
# Alternative example
class Bar
# we have to define this method on `self` (see below comment)
def self.create_method(method)
define_method "my_#{method}" do
puts "Dynamic method called 'my_#{method}'"
end
end
# these methods are executed within the definition of the Bar class
create_method :foo
create_method :bar
create_method :baz
end
Bar.new.my_foo # => "Dynamic method called 'my_foo'"
Bar.new.my_bar # => "Dynamic method called 'my_bar'"
Bar.new.my_baz # => "Dynamic method called 'my_baz'"
# Dynamic Method
# Parse another class for data
class AnotherClass
def get_foo_stuff; end
def get_bar_stuff; end
def get_baz_stuff; end
end
class Baz
def initialize(a_class)
a_class.methods.grep(/^get_(.*)_stuff$/) { Baz.create_method $1 }
end
def self.create_method(method)
define_method "my_#{method}" do
puts "Dynamic method called 'my_#{method}'"
end
end
end
another_class = AnotherClass.new
Baz.new(another_class).my_foo # => "Dynamic method called 'my_foo'"
Baz.new(another_class).my_bar # => "Dynamic method called 'my_bar'"
Baz.new(another_class).my_baz # => "Dynamic method called 'my_baz'"
class Foo
def initialize(bar)
self.class.send(:define_method, bar) { p "hello #{bar}!" }
end
end
foo = Foo.new("world")
foo.world # => "hello world!"
# Ghost Methods
# Utilises `method_missing`
class Hai
def method_missing(method, *args)
puts "You called: #{method}(#{args.join(', ')})"
puts "You also passed a block" if block_given?
end
end
Hai.new.yolo # => You called: yolo()
Hai.new.yolo "a", 123, :c # => You called: yolo(a, 123, c)
Hai.new.yolo(:a, :b, :c) { puts "a block" } # => You called: yolo(a, b, c)
# => You also passed a block
# Dynamic Proxies
# Catching "Ghost Methods" and forwarding them onto another method
# Whilst possibly adding logic around the call.
#
# For example,
# You can provide imaginary methods by utilising `method_missing` to parse
# the incoming message (e.g. `get_name`, `get_age`) and to delegate off to
# another method such as `get(:data_type)` where `:data_type` is `:name` or `:age`.
def method_missing(message, *args, &block)
return get($1.to_sym, *args, &block) if message.to_s =~ /^get_(.*)/
super # if we don't find a match then we'll call the top level `BasicObject#method_missing`
end
# If (after analysis) you discover a performance issue with using `method_missing`
# you can utilise the "Dynamic Method" technique to create a real method after
# the message has been received by `method_missing` the first time.
# Blank Slate
# Prevents issues when using "Dynamic Proxies"
#
# e.g. user calls a method that exists higher up the inheritance chain
# so your `method_missing` doesn't fire because the method does exist.
#
# To work around this issue, make sure your class starts with a "Blank Slate"
# So you remove any methods you don't want to appear at all in the inheritance chain
# by using `undef_method` (there is also `remove_method` which doesn't remove the named
# method from the inheritance chain but just the current class, but that doesn't help us
# fix the "Dynamic Proxy" scenario so we use `undef_method` instead).
#
# For "Dynamic Proxy" we use the parent `method_missing` so we keep that,
# we also might use `respond_to?` so we keep that (although you can remove it if you don't).
# Also the `__` in the below regex pattern is to prevent Ruby from displaying a warning
# about removing 'reserved' methods such as `__id__` and `__send__`
class ImBlank
instance_methods.each do |m|
undef_method m unless m.to_s =~ /^__|method_missing|respond_to?/
end
# rest of your code (such as your "Dynamic Proxy" implementation)
end
# Kernel Method
# Add a method that gives the illusion it's a language keyword
# But really it's just added to the `Kernel` module which all other objects inherit from.
# At the top level of a Ruby program `self` is == `main`.
# `self.class` == `Object` and the `Kernel` sits above it in the hierarchy.
# You can see this by running the following code:
class Foo; end
Foo.ancestors # => [Foo, Object, Kernel, BasicObject]
# So we can see we can add what looks to be a language provided feature like so:
module Kernel
def foobar
puts "I'm not a language keyword, I'm just a fake"
end
end
# Now from any where in our program we can call
foobar # => I'm not a language keyword, I'm just a fake
# Flattening the Scope (aka Nested Lexical Scopes)
# Where you change the code in such a way that it's easier for you to pass variables through "Scope Gates".
# A scope gate is any block, where normally when you enter its scope the variables outside of it become unreachable.
# This happens in: Class definitions, Module definitions, Method definitions
# I'm not sure what the real life examples are of this, but if you ever wonder why some code does the following,
# then maybe it was that they wanted to flatten the scope so they could more easily pass around variables.
# I guess it's better to do it this way than to define a global variable?
#
# In the following code we want to access `my_var` from inside the method (inner scope gate) that's
# inside the class (outer scope gate).
my_var = "abc"
class OuterScopeGate
puts my_var
def inner_scope_gate
puts my_var
end
end
# We fix this by flattening the code into method calls (method *calls* aren't scope gates)
# So we turn the class keyword into a method call using `Class.new`
# We also turn the method inside the class from a keyword into a method call using `define_method`
my_var = "abc"
MyClass = Class.new do
puts "Here is 'my_var' inside my class definition: #{my_var}"
define_method :my_method do
puts "Here is 'my_var' inside my class instance method: #{my_var}"
end
end # => Here is 'my_var' inside my class definition: abc
MyClass.new.my_method # => Here is 'my_var' inside my class instance method: abc
# Context Probe
# Execute a code block in the context of another object using `instance_eval`
class Foo
def initialize
@z = 1
end
end
foo = Foo.new
foo.instance_eval do
puts self # => #<Foo:0x7d15e891>
puts @z # => 1
end
new_value = 2
foo.instance_eval { @z = new_value }
foo.instance_eval { puts @z } # => 2
# There is also `instance_exec` which works the same way but allows passing arguments to the block
class Foo
def initialize
@x, @y = 1, 2
end
end
Foo.new.instance_exec(3) { |arg| (@x + @y) * arg } # => 9
# Evaluate a block in the context of a class
# Similar to re-opening a class but more flexible in that it
# works on any variable that references a class, where as re-opening
# a class requires defining a constant.
# Classic class re-opening style
class String
def m; puts "hello!" end
end
# Class eval style
# The extra code is used to make the example a bit more re-usable/abstracted
def add_method_to_class(the_class)
the_class.class_eval do
def m; puts "hello!" end
end
end
add_method_to_class String
"abc".m # => hello!
# Class Macros are just regular class methods that are only used in a class definition
# e.g. not used from a new instance of the class (only at the time the class is defined)
#
# Below is an example of a Class Macro that alerts users of a publically available class
# that the methods they've been using are now deprecated and they should use the renamed version.
#
# It uses "Dynamic Method" to help performance by creating the old methods again and delegating off
# to the new methods (rather than using `method_missing` which can be quite slow as it has to spend
# time looking up the inheritance chain)
class Foo
def get_a; puts "I'm an A" end
def get_b; puts "I'm an B" end
def get_c; puts "I'm an C" end
# Defining our Class Macro
def self.deprecate(old_method, new_method)
define_method(old_method) do |*args, &block|
puts "Warning: #{old_method} is deprecated! Use #{new_method} instead"
send(new_method, *args, &block) # `self` is assumed when calling `send`
end
end
# Using our Class Macro
deprecate :a, :get_a
deprecate :b, :get_b
deprecate :c, :get_c
end
# Around Alias uses the `alias` keyword to store a copy of the original method under a new name,
# allowing you to redefine the original method name and to delegate off to the previous method implementation
class String
alias :orig_length :length
def length
"Length of string '#{self}' is: #{orig_length}"
end
end
"abc".length
#=> "Length of string 'abc' is: 3"
# Hook Methods are provided by the Ruby language and let you know about certain events
# such as when a class inherits from another class or when a method has been added to an object.
class String
  def self.inherited(subclass)
    puts "#{self} was inherited by #{subclass}"
  end
end
class MyString < String; end # => String was inherited by MyString

There are quite a few hooks which I've listed below.

Method-related hooks

method_missing
method_added
singleton_method_added
method_removed
singleton_method_removed
method_undefined
singleton_method_undefined

Class & Module Hooks

inherited
append_features
included
extend_object
extended
initialize_copy
const_missing

Marshalling Hooks

marshal_dump
marshal_load

Coercion Hooks

coerce
induced_from
to_xxx
# Class Extension Mixin allows you to both `include` and `extend` a class
module MyMixin
def self.included(base) # Hook Method
base.extend(ClassMethods)
end
def a
puts "I'm A (an instance method)"
end
module ClassMethods # "ClassMethods" is a recognised naming pattern
def x
puts "I'm X (a class method)"
end
end
end
class Foo
include MyMixin
end
Foo.x # => I'm X (a class method)
Foo.new.a # => I'm A (an instance method)
type = "baz"
Foo::Bar.const_get(type.capitalize).new # => new instance of Foo::Bar::Baz
@apchamberlain

This comment has been minimized.

apchamberlain commented May 16, 2014

"Dynamic dispatch" doesn't mean what you think it does.

http://en.wikipedia.org/wiki/Dynamic_dispatch

@uzbekjon

This comment has been minimized.

uzbekjon commented Jun 22, 2015

Ruby 2.0 also introduces prepend hook method that behaves similar to include method, but inserts your module before your class in its inheritance tree. I explained it in my "Ruby Metaprogramming" video course in more details.

BTW, I also applied $50 OFF coupon to the link above as a give back to GitHub community ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment