Decorators in ruby

Tamás Michelberger

SSP

@tmichelberger

What are decorators?

[A decorator] allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.

source: Wikipedia

Basic example

class Coffee
  def will_keep_you_rolling_for
    2.hours
  end
end

class ExtraLarge
  def initialize(coffee) @coffee = coffee end

  def will_keep_you_rolling_for
    @coffee.will_keep_you_rolling_for + 2.hours
  end
end

class SuperStrong
  def initialize(coffee) @coffee = coffee end

  def will_keep_you_rolling_for
    @coffee.will_keep_you_rolling_for * 2
  end
end

SuperStrong.new(ExtraLarge.new(Coffee.new)).will_keep_you_rolling_for
#=> 8

DElegators are transparent

Usually only a few methods are added and/or modified in a decorator. What to do with the other methods?

Class.new(ActiveRecord::Base).instance_methods.size
=> 386

Delegating every method by hand is almost impossible.

STANDARD LIBRARY TO THE RESCUE!

DELEGATION is Your best friend

Forwardable

SimpleDelagator

DelegateClass

Forwardable

Person = Struct.new(:first_name, :last_name)

class PersonWithFullName
  extend Forwardable

  def initialize(person)
    @person = person
  end

  def_delegators :@person, :first_name, :last_name

  def full_name
    "#{first_name} #{last_name}"
  end
end

SImpleDElegator

Person = Struct.new(:first_name, :last_name)

class PersonWithScrambledName < SimpleDelegator
  def scrambled_name
    "#{scramble(first_name)} #{scramble(last_name)}"
  end

  private

  def scramble(str)
    str.chars.shuffle.join
  end
end

person_decorator = PersonWithScrambledName.new(Person.new('John', 'Doe'))
person_decorator.scrambled_name #=> "onhJ Doe"
person_decorator.__getobj__ #=> #<struct Person first_name="John", last_name="Doe">

DelegateClass

Person = Struct.new(:first_name, :last_name)

class SayHiForMe < DelegateClass(Person)
  def say_hi
    "Hi, I'm #{first_name}"
  end
end

SayHiForMe.new(Person.new('John', 'Doe')).say_hi
=> "Hi, I'm John"

SayHiForMe.public_instance_methods
=> [:say_hi, :__getobj__, :__setobj__, :first_name, :first_name=, 
    :last_name, :last_name=, ...]

WHere does it come in handy?

  • Layering orthogonal functionality
  • Caching
  • Backing views (presenters or view models)
  • and many more...

Thanks!

Questions?

Made with Slides.com