Ruby hooks
and callbacks

When you call this, Ruby calls that

Be careful

Method Hooks

method_missing

Handle calls to methods that do not already exist

method_missing

class Container
  def package!
    puts 'Packaged'
  end
  def method_missing(method_name)
    if method_name == :shipit!
      puts 'SHIPIT!'
    end
  end
end

box = Container.new
box.package! # => Packaged
box.shipit! # => SHIPIT!
            

Practical uses

activerecord-deprecated_finders

User.find_by_email(email) # Rails 3 deprecated finders available by default
            
vs

User.find_by(:email, email) # Rails 4 without deprecated finders
            
respond_to_missing?

Container.new.respond_to? :shipit! # => false
Container.new.method :shipit! # => NameError: undefined method 'shipit!'

class Container
  def method_missing(method_name)
    if method_name == :shipit!
      puts 'SHIPIT!'
    end
  end
  def respond_to_missing?(method_name, include_private = false)
    method_name == :shipit! ? true : false
  end
end

Container.new.respond_to? :shipit! # => true
Container.new.method :shipit! # => <Method: Container#shipit!>
          
You should define respond_to_missing?
if you define method_missing
method_added

class Container
  def self.method_added(method_name)
    puts "#{method_name} is now available!"
  end

  def shipit!; end # => shipit! is now available!
end
          
method_removed

class Container
  def self.method_removed(method_name)
    puts "#{method_name} was removed"
  end

  def shipit!; end

  remove_method :shipit! # => shipit! was removed
end
          
method_undefined

class Container
  def self.method_undefined(method_name)
    puts "#{method_name} was undefined"
  end

  def shipit!; end

  undef_method :shipit! # => shipit! was undefined
end
          
singleton_method_added

class Container
  def self.singleton_method_added(method_name)
    puts "#{method_name} was added"
  end # => singleton_method_added was added

  def self.shipit!; end # => shipit! was added
end
          
singleton_method_removed

class Container
  def self.singleton_method_removed(method_name)
    puts "#{method_name} was removed"
  end

  def self.shipit!; end
end

class << Container
  remove_method :shipit! # => shipit! was removed
end
          
singleton_method_undefined

class Container
  def self.singleton_method_undefined(method_name)
    puts "#{method_name} was undefined"
  end

  def self.shipit!; end
end

class << Container
  undef_method :shipit! # => shipit! was undefined
end
          

What do we have so far

  • method_missing
  • respond_to_missing?
  • method_added
  • method_removed
  • method_undefined
  • singleton_method_added
  • singleton_method_removed
  • singleton_method_undefined

Class and Module Hooks

inherited

class Container
  def self.inherited(subclass)
    puts "#{subclass} is now inheriting from #{self.name}"
  end
end

class Box < Container; end # => Box is now inheriting from Container
          

Quick intro to Module#include


module Shippable
  def shipit!
    puts 'SHIPIT!'
  end
end

class Container
  include Shippable
end

Container.new.shipit! # => SHIPIT!
            

include vs. extend

include is for adding instance methods
extend is for adding class methods
append_features

module Shippable
  def self.append_features(other)
    puts "The features of #{self} were appended to #{other}"
    super
  end

  def shipit!
    puts 'SHIPIT!'
  end
end

class Container
  include Shippable # => The features of Shippable were appended to Container
end
            
included

module Shippable
  def self.included(other)
    puts "#{other} included the features of #{self}"
  end
end

class Container
  include Shippable # => Container included the features of Shippable
end
            

Practical uses


class HardWorker
  include Sidekiq::Worker

  def perform
    puts "Work!"
  end
end
            
Behind the scenes

module Sidekiq
  module Worker
    def self.included(base)
      base.extend(ClassMethods)
      base.class_attribute :sidekiq_options_hash
      base.class_attribute :sidekiq_retry_in_block
      base.class_attribute :sidekiq_retries_exhausted_block
    end
    module ClassMethods
      def perform_async(*args)
        ...
      end
    end
  end
end
            
ActiveSupport::Concern

module Shippable
  extend ActiveSupport::Concern

  included do
    extend ClassMethods
  end

  module ClassMethods
  end
end
            
You can get some nicer syntax if you extend from ActiveSupport::Concern

include vs. prepend

include

module Shippable
  def shipit!
    puts 'shipit!'
  end
end

class Container
  include Shippable
  def shipit!
    puts 'SHIPIT!'
    super
  end
end

Container.new.shipit! # => SHIPIT!
                      # => shipit!
            
prepend

module Shippable
  def shipit!
    puts 'shipit!'
    super
  end
end

class Container
  prepend Shippable
  def shipit!
    puts 'SHIPIT!'
  end
end

Container.new.shipit! # => shipit!
                      # => SHIPIT!
            
prepend_features

module Shippable
  def self.prepend_features(other)
    puts "The features of #{self} were prepend to #{other}"
    super
  end

  def shipit!
    puts 'SHIPIT!'
  end
end

class Container
  prepend Shippable # => Features of Shippable were prepended to Container
end
            

intro to extend


module Shippable
  def shipit!
    puts 'SHIPIT!'
  end
end

class Container
  extend Shippable
end

Container.shipit! # => SHIPIT!
            
extended

module Shippable
  def self.extended(other)
    puts "#{other} was extended with #{self}"
    super
  end

  def shipit!
    puts 'SHIPIT!'
  end
end

class Container
  extend Shippable # => Container was extended with Shippable
end
            
extend_object

module Shippable
  def self.extend_object(other)
    puts "#{other} was extended with #{self}"
    super
  end

  def shipit!
    puts 'SHIPIT!'
  end
end

class Container
  extend Shippable # => Container was extended with Shippable
end
            
Similar to append_features, and prepend_features
but, works with extend
initialize

class Container
  def initialize
    puts 'Ready to Shipit!'
  end
end

Container.new # => Ready to Shipit!
            
initialize_copy

class Container
  attr_accessor :width

  def initialize
    @width = 4
  end

  def initialize_copy(other)
    other.width = 2
  end
end

a = Container.new
a.width # => 4
b = a.dup
b.width # => 2
            
const_missing

class Container
  def initialize
    SHIPIT
  end

  def self.const_missing(con)
    puts "#{con} was missing"
  end
end

Container.new # => SHIPIT was missing
            
Similar to method_missing but for constants
marshal_dump
marshal_load

class Container
  def marshal_dump
    puts "Dumping Container"
  end

  def marshal_load(data)
    puts "Loading Container"
  end
end

dumped = Marshal.dump Container.new # => Dumping Container
Marshal.load(dumped) # => Loading Container
            
These have to be interchangeable
If you can dump it you also need to be able to load it

What else have we seen

  • inherited
  • include/included/append_features
  • extend/extended/extend_object
  • prepend/prepended/prepend_features
  • initialize
  • initialize_copy
  • const_missing

coercion

Overloading operators


class Container
  def +(other)
    if other.is_a? String
      puts other
    end
  end
end

Container.new + 'shipit!' # => shipit!
            
Pretty impractical right?

Practical uses
of overloading operators


Date.today     # => Mon, 28 Oct 2013
Date.today + 5 # => Sat, 02 Nov 2013
nil.to_i       # => 0
            
Allows us to add dates together really easily

The operators you can overload

* ** % - -@ +@ + / += -= *= /= **= =~ !~ << >> <=> [] []= != == === < > <= >= | ^ & ~

Already overloaded operators

  • * Multiply and Splat
  • & Binary AND and Symbol#to_proc
  • << Bitwise shift and append to Strings and Arrays

The operators you can't overload

|| ||= && &&= and or not = -> . .. ... :: () {}

Other hooks

trace_var

trace_var :$_, proc {|v| puts "$_ is now '#{v}'" }
$_ = 'hello'  # => $_ is now 'hello'
$_ = ' there' # => $_ is now ' there'
            
Signal.trap

❯ irb
2.0.0-p247 :001 > Signal.trap("INT") { |signo| puts Signal.signame(signo) }
2.0.0-p247 :002 > INT
            
rescue

def might_raise
  raise 'shiping exceptions!'
end

begin
  might_raise
rescue
  puts 'I was rescued'
end
            
ensure

def might_raise
  raise 'shiping exceptions!'
end

begin
  might_raise
rescue
  puts 'I was rescued'
ensure
  puts 'And I was able to cleanup'
end
            

end

twitter.com/_aaronackerman_ github.com/aackerman