Book Notes: Metaprogramming Ruby
Book:
Metaprogramming Ruby
- What is metaprogramming?
- Writing code that writes code, the simplest description
- Writing code that manipulates language constructs at runtime
- Introspection
- Query about an objects methods
- Can add / remove / overwrite existing methods
The Object Model
- All classes ultimately inherit from Object, which inherits from BasicObject
- Constants
- Unique reference to a scope
- Constants can be duplicated as long as they are within different module scopes - akin to folders / files are modules / constants
- Class names are nothing but Constants
- Modules are basically packages of methods
- Classes are like Modules, except with
new,allocate, andsuperclassthat allow for hierarchal structures - Objects are just a collection of instance methods and a reference to a class
- The methods of an object live within the object’s class
- A class is just an instance of
Class - How do method calls work?
- Needs to 1. find the method and 2. execute with
self - The receiver is the object a method is called on
- Lookup - The method chain is as follows:
- receiver
- receiver class
- receiver class included modules
- Repeat 2 & 3 with the superclass as receiver
- Execution
- Every line of Ruby code is executed inside an object called the current object
- When a method call is executed, a reference to the receiver is saved to be used as
selflater
- Needs to 1. find the method and 2. execute with
Techniques
- Open Classes
classis more like a scope operator than a class declaration- Can use
classto “re-open” a class and add / redefine methods - Use with caution - hard to track down origin of bugs, monkey-patching is usually a code smell
Methods
Techniques
- Dynamic Dispatch
- Use
sendto dynamically call a method when calling the method directly may not be possible with dot notation
- Use
- Dynamic Methods
- Methods can be defined at runtime with
define_method
- Methods can be defined at runtime with
- Dynamic Proxy
- Override
method_missingto capture method calls and respond appropriately - Creates “Ghost methods”, which won’t be seen through introspection
- Can override
responds_to?to make Ghost methods discoverable
- Override
- Blank Slate
- Remove all inherited methods with
undef_method - Fixes any potential method naming conflicts
- Remove all inherited methods with
Blocks
- Powerful for controlling scope
- Part of the “callable objects” family, which includes procs and lambdas
- Each method exposes a callable
block_given?method, which will returntruewhen a block was passed in as an argument to the method - Each method exposes a callable
yieldmethod, which will execute the block passed to the method - Closures
- Blocks contain both code and a set of variable bindings
- When you define a block, it grabs the bindings in scope at that moment
- Can introspectively look at variables with the
Kernel#local_variablesmethod
- Scope
- As soon as you enter a new scope, previous bindings are replaced by a new set, opposite of Java and C#
- Scope gates include
classdefinition,moduledefinition, anddefmethod definition - Exiting a scope, local variables defined are no longer visible
Object#instance_evalevaluates a block within the scope of an object- A Proc is a block converted into an object, since blocks are not objects themselves
- The
&converts a Proc to a block. It can capture a block passed into a method. If the variable does not use the&operator, it is a Proc - Difference between Procs and Lambdas
return- Lambdas simply returns from the lambda
- Procs return from the scope where the proc was defined
- Arity
- Lambdas are less tolerant than procs when it comes to arguments
- Procs will usually raise an
ArgumentErrorwhen called with the wrong arity - Lambdas will fill empty arguments with
nil
Techniques
- Flat Scope
- Circumvent scope gates by dynamically defining classes, modules, and methods
- Use
Class.newinstead ofclass,define_methodinstead ofdef
- Shared Scope
- Use
define_methodto share the scope between methods / classes, protecting that variable from being accessed by other objects
- Use
- Context Probe
- Use
instance_evalto access private methods and instance variables
- Use
- Clean Rooms
- An empty environment (read: Object) to evaluate blocks within
Class Definitions
- In a class or module definition, the class / module itself takes the role of
self {class, module}_evalexecutes a block within the context of the class / module- Class Instance Variables are only accessible by the class (since they are defined with the class as
self), not by an object instance or a subclass - Class Variables can be accessed by subclasses and instance methods, which belong to the class hierarchy. These are defined by
@@{name} - Eigenclasses are the “UFOs” of the Ruby world
- Each instance of a class (including instances of
Class) gets its own special “copy” of the class, called an eigenclass - This allows these classes to be flexible (add / remove methods) without affecting other instances of the base class
- Eigenclasses exist in the method lookup chain, and exist as the class for each instance of a class or module
- The superclass of the eigenclass of an object is the object’s class
- The superclass of the eigenclass of a class is the eigenclass of the class’s superclass
- Each instance of a class (including instances of
Techniques
- Singleton Methods
- Define a method on a specific object, using
def {object}.{method_name}. This is how class methods work, with classselfas theobject
- Define a method on a specific object, using
- Class Macros
- instance methods defined on
ClassorModulethat can be called in the class definition. Example isattr_accessor
- instance methods defined on
- Around Alias
- Use
aliasto create a copy of a method, overwrite the original method name and call the new method name
- Use
Code That Writes Code
evaltakes a string of code and executes the string- Dangerous! Arbitrary code injection abound
Techniques
- Hook Methods
- Methods called when events such as inheriting and including occur
- Includes
Class#inherited,Module#included,Module#method_added,Module#method_undefined,Module#method_removed, etc.