Book Notes: 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, and superclass 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:
      1. receiver
      2. receiver class
      3. receiver class included modules
      4. 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

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
  • Dynamic Methods
    • Methods can be defined at runtime with define_method
  • 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
  • Blank Slate
    • Remove all inherited methods with undef_method
    • Fixes any potential method naming conflicts

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 return true 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, and def 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 of class, define_method instead of def
  • Shared Scope
    • Use define_method to share the scope between methods / classes, protecting that variable from being accessed by other objects
  • Context Probe
    • Use instance_eval to access private methods and instance variables
  • 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

Techniques

  • Singleton Methods
    • Define a method on a specific object, using def {object}.{method_name}. This is how class methods work, with class self as the object
  • Class Macros
    • instance methods defined on Class or Module that can be called in the class definition. Example is attr_accessor
  • Around Alias
    • Use alias to create a copy of a method, overwrite the original method name and call the new method name

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.