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
, andsuperclass
that 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
self
later
- Needs to 1. find the method and 2. execute with
Techniques
- Open Classes
class
is more like a scope operator than a class declaration- Can use
class
to “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
send
to 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_missing
to 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 returntrue
when a block was passed in as an argument to the method - Each method exposes a callable
yield
method, 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_variables
method
- 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
class
definition,module
definition, anddef
method definition - Exiting a scope, local variables defined are no longer visible
Object#instance_eval
evaluates 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
ArgumentError
when 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.new
instead ofclass
,define_method
instead ofdef
- Shared Scope
- Use
define_method
to share the scope between methods / classes, protecting that variable from being accessed by other objects
- Use
- Context Probe
- Use
instance_eval
to 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}_eval
executes 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 classself
as theobject
- Define a method on a specific object, using
- Class Macros
- instance methods defined on
Class
orModule
that can be called in the class definition. Example isattr_accessor
- instance methods defined on
- Around Alias
- Use
alias
to create a copy of a method, overwrite the original method name and call the new method name
- Use
Code That Writes Code
eval
takes 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.