Decorator Pattern


# Wrapper objects
# Implement original object's interface
# Delegate methods to the original object
  

class Burger
  def cost
    50
  end
end
  

class BurgerWithCheese < Burger
  def cost
    60 # +10 for cheese
  end
end
  

class LargeBurger < Burger
  def cost
    65 # +15 for large
  end
end
  

class LargeBurgerWithCheese < Burger
  def cost
    75 # +15 (for large) + 10 (for cheese)
  end
end
  

class JumboBurgerWithCheese < Burger
  def cost
    90 # +15 (2xlarge) + 10 (cheese)
  end
end
  

Modules


module Cheese
  def cost
    super + 10
  end
end
  

module Large
  def cost
    super + 15
  end
end
  

burger = Burger.new
burger.extend(Cheese)
burger.extend(Large)
p burger.cost #=> 75
  

class JumboBurger < Burger
  def cost
    80 # + 30 (2xlarge)
  end
end
  

burger = Burger.new
burger.extend(Large)
p burger.cost #=> 65

burger.extend(Large)
p burger.cost #=> 65
  

Let's try decorators


class LargeBurger
  def initialize(burger)
    @burger = burger
  end
end
  

class LargeBurger
  def cost
    @burger.cost + 15
  end
end
  

class BurgerWithCheese
  def initialize(burger)
    @burger = burger
  end
end
  

class BurgerWithCheese
  def cost
    @burger.cost + 10
  end
end
  

burger = Burger.new
large_burger = LargeBurger.new(burger)
  

jumbo_burger = LargeBurger.new(large_burger)
jumbo_cheese_burger = BurgerWithCheese.new(jumbo_burger)
  

SimpleDelegator


class BurgerDecorator < SimpleDelegator
  def initialize(burger)
    @burger = buger
    super(@burger)
  end
end
  

class LargeBurger < BurgerDecorator
  def cost
    super + 15
  end
end
  

class BurgerWithCheese < BurgerDecorator
  def cost
    super + 10
  end
end
  

Presenter pattern


class BasePresenter < SimpleDelegator
  def initialize(object, context)
    @model, @context = object, context
    super(@model)
  end
end
  

class BasePresenter
  def h # helper method
    @context
  end
end
  

class PostPresenter < BasePresenter
  def author
    h.link_to super, h.user_path(super)
  end
end
  

class PostsController
  def show
    post = Post.find params[:id]
    @post = PostPresenter.new(post)
  end
end
  

Another approach


module ApplicationHelper
  def present(object)
    klass = "#{object.class}Presenter".constantize
    presenter = klass.new(object, self)
    yield(presenter) if block_given?
    presenter
  end
end
  

- present(@post) do |post|
  %h1= post.title
  %p.author= post.author
  

Draper

gem install draper

class PostsController
  def show
    @post = Post.find(params[:id]).decorate
  end
end
  

class PostsController
  def index
    @posts = Post.all.decorate
  end
end