Slide 1

Slide 1 text

Meta Programming in Ruby Suraj Shirvankar

Slide 2

Slide 2 text

Ruby Objects • Every thing in ruby is an object except for blocks; • Methods are called by passing messages to objects

Slide 3

Slide 3 text

Monkey patching Allows you to refine a class / Add new methods Not recommended for Classes that you didn't build yourself class String def censor “######” end end “VPN”.sensor => “#####”

Slide 4

Slide 4 text

MethodNotFound class Car ….. ….. end @car = Car.new @car.autodrive NoMethodError: undefined method `autodrive' for Car:Class

Slide 5

Slide 5 text

What happens in the background class Car ….. ….. end The message ‘autodrive’ is sent to the car instance Ruby looks for the method ‘autodrive' and if it doesn't find the ‘autodrive’ method it calls ‘method_missing’ method_missing then throws the error NoMethodError

Slide 6

Slide 6 text

Naive way of implementing dynamic methods def method_missing(method_name, *args, &block) if method_name == “autodrive” “Awaiting google’s implementation” else super end end @car.autodrive => “Awaiting google’s implementation”

Slide 7

Slide 7 text

But Wait ! @car.autodrive => “Awaiting google’s implementation” @car.respond_to? :autodrive => false class Car def respond_to_missing?(method_name, include_private = false) method_name == “autodrive” || super end end

Slide 8

Slide 8 text

Better approach Dynamic method definition

Slide 9

Slide 9 text

class Car define_method(:break) do “Need to repair” end end @car.break => “Need to repair” @car.respond_to?(:break) => true

Slide 10

Slide 10 text

Module_eval Instance_eval class_eval

Slide 11

Slide 11 text

Demo

Slide 12

Slide 12 text

Re-implementing FactoryGirl

Slide 13

Slide 13 text

FactoryBoy

Slide 14

Slide 14 text

Tests first require 'test/unit' require_relative '../lib/factory_boy' class TestFactoryBoy < Test::Unit::TestCase def setup FactoryBoy.define do factory :user do name "test" cat "meow" end end @user = FactoryBoy.build :user end def test_creates_user assert_equal @user.class, User end def test_has_name assert_equal @user.name, "test" end def test_meow assert_equal @user.cat, "meow" end end

Slide 15

Slide 15 text

Implementation module FactoryBoy @factories = {} def self.define(&block) module_eval(&block) end def self.build(klass) instance = const_get(@factories[klass].class_name.capitalize).new @factories[klass].our_methods.each do |key, value| instance.class.class_eval do define_method(key.to_sym) { value } end end instance end def self.factory(klass, &block) instance = @factories[klass] = BaseObject.new(klass.to_s.capitalize) instance.instance_eval(&block) end class BaseObject < BasicObject attr_accessor :our_methods, :class_name def initialize(klass) @our_methods = {} @class_name = klass.to_s end def method_missing(method, *args, &block) @our_methods[method] = args.first end end end

Slide 16

Slide 16 text

https://github.com/h0lyalg0rithm/meta_programming_meetup