Programming Languages

Metaprogramming in Ruby

Jim Posen - ECE/CS 2014

Ruby is Flexible

  • Rubyists like to claim this
  • What have we seen so far to support this?
    • Dynamic/duck typing
    • Open classes
    • Blocks
    • Anonymous classes

Method definition review

  • Instance methods are defined in classes
  • Methods are callable on instances of the class
class Dog
  def bark
    "woof"
  end
end

dog = Dog.new
dog.bark

Self

  • self is a reference to the object in scope
class Dog
  def speak
    self.bark
  end
end

dog.speak

Methods on classes

  • Certain methods are defined on instances of Class
  • new and superclass for example
  • This is possible because classes are objects
Dog.superclass  # => Animal
Dog.new  # => instance of Dog
Dog.class  # => Class

Class method review

  • We can also define custom methods on classes
def Dog
  def self.type
    :mammal
  end
end

Dog.type  # => :mammal

Let’s deconstruct this

  • In the context of the class definition, what is self?
  • It’s the Dog object

Then that should be equivalent to

def Dog
end

def Dog.type
  :mammal
end

Dog.type  # => :mammal

Taking this a step further

  • We can define methods inside classes, callable on all instances
  • It appears that we can define methods on objects
dog = Dog.new
dog.bark  # => "woof"

def dog.bark
  "meow"
end

dog.bark  # => "meow"

other_dog = Dog.new
dog.bark  # => "woof"

Not quite

  • Object store instance variables
  • Objects do not store methods, only classes can

Eigenclasses

  • You may define methods on an object’s eigenclass
  • Methods defined here are only callable on the object
  • Every object may have one
  • Eigenclasses are also called metaclasses or singleton classes

Connecting with your inner eigenclass

  • You can use class << object to open up that object’s eigenclass
  • Object#singleton_class will return your eigenclass
class Dog
  class << self
    def type
      :mammal
    end
  end
end

class << Dog
  def type
    :mammal
  end
end

The moral of the story

  • We have a new way to add custom functionality
  • Be aware of what self is referring to

define_method

  • We can define methods programmatically
  • Class#define_method takes a block and defines a method on the class
  • This is how we can define Class#attr_accessor
class Class
  def attr_accessor(attr)
    define_method(attr) do
      instance_variable_get "@#{attr}"
    end

    define_method("#{attr}=") do |val|
      instance_variable_set "@#{attr}", val
    end
  end
end

method_missing

  • If no method definition is found, Ruby will try to call method_missing
  • This is your last chance to respond to a “message”

OpenStruct

  • Like a dictionary but has getters and setters
person = OpenStruct.new
person.name    = "John Smith"
person.age     = 70
person.pension = 300

puts person.name     # -> "John Smith"
puts person.age      # -> 70
puts person.address  # -> nil

Let’s define it

class OpenStruct
  def initialize
    @attrs = {}
  end

  def method_missing(method, val = nil)
    if method =~ /^(\w+)=$/
      # Setter
      @attrs[$1] = val
    elsif method =~ /\w+/
      # Getter
      @attrs[method]
    else
      # Die
      super
    end
  end
end

instance_eval

  • Evalutes a code block in the context of an object instance
  • You can access private methods and instance variables
class Dog
  private
  def bark
    "woof"
  end
end

dog = Dog.new
dog.instance_eval { bark }  # => "woof"

Domain Specific Languages

Domain Specific Languages

  • In this class we have seen 3 general purpose programming languages
  • There are many general purpose languages, but there are far more domain specific languages
  • DSLs are small, special purpose languages for a very specific problem
  • Examples are configuration languages, testing frameworks, etc

nginx configuration

server {
    listen          80;
    server_name     domain.com *.domain.com;
    return          301 $scheme://www.domain.com$request_uri;
 }

server {
    listen          80;
    server_name     www.domain.com;

    index           index.html;
    root            /home/domain.com;
}

DSLs in Ruby

  • Do to Ruby’s flexibility, it is easy to implement DSLs with Ruby
  • A widely used example is the testing framework RSpec
feature "Widget management" do
  scenario "User creates a new widget" do
    visit "/widgets/new"

    fill_in "Name", :with => "My Widget"
    click_button "Create Widget"

    expect(page).to have_text("Widget was successfully created.")
  end
end