Pro Yearly is on sale from $80 to $50! »

The Draper gem

9302bbf8e5f4e93a23f7d69bd50ebcd7?s=47 episko
March 20, 2013

The Draper gem

Presented the draper gem during the @genevarb meetup on 20/03/2013

9302bbf8e5f4e93a23f7d69bd50ebcd7?s=128

episko

March 20, 2013
Tweet

Transcript

  1. The Draper gem Give some OOP love to your views

    by Axel Vergult @episko http://www.github.com/episko
  2. Summary ‣ What is our problem? ‣ What is a

    decorator? ‣ Enter Draper ‣ Installation ‣ Use case ‣ Features ‣ When to use it?
  3. What is our problem?

  4. How to deal with an object that can have different

    behaviors depending on the context?
  5. Solution 1: using subclasses class Car def price 10_000 end

    end class CarWithAirConditionning < Car def price super + 1_000 end end class CarWithAirConditionningAndRadio < Car def price super + 1_000 + 200 end end puts CarWithAirConditionning.new.price #=> 11000 puts CarWithAirConditionningAndRadio.new #=> 11200
  6. Cons ‣ A class for each combination ‣ Tight coupling

  7. Solution 2: using mixins module AirConditionning def air_conditionning? @air_conditionning end

    def air_conditionning_price air_conditionning? ? 1_000 : 0 end end module Radio def radio? @radio end def radio_price radio? ? 200 : 0 end end class Car include AirConditionning include Radio def initialize(opts={}) @air_conditionning = opts[:air_conditionning] @radio = opts[:radio] end def price 10_000 + air_conditionning_price + radio_price end end puts Car.new(air_conditionning: true, radio: false).price #=> 11000 puts Car.new(air_conditionning: true, radio: true).price #=> 11200
  8. Cons ‣ We’ve just moved the problem ‣ Now need

    to deal with states everywhere ‣ Breaks single responsibility principle
  9. What is a decorator?

  10. Wikipedia: In object-oriented programming, the decorator pattern is a design

    pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.
  11. Solution 3: using a decorator class Car def price 10_000

    end end module Decorator def initialize(component) @component = component end def method_missing(method, *args) if args.empty? @component.send(method) else @component.send(method, args) end end end class AirConditionning include Decorator def price @component.price + 1_000 end end class Radio include Decorator def price @component.price + 200 end end puts AirConditionning.new(Car.new).price #=> 11000 puts Radio.new(AirConditionning.new(Car.new)).price #=> 11200
  12. Pros ‣ Can be used multiple times on component ‣

    Can be wrapped infinitely ‣ Delegates through all decorators
  13. Enter Draper

  14. Purpose Add an object-oriented layer of presentation logic to your

    Rails application by creating view objects.
  15. How?

  16. Guess what?

  17. By using decorators!

  18. Bring in a data model & Decorate it with view

    specific methods
  19. Installation

  20. # Gemfile gem ‘draper’ # Shell rails generate decorator Product

  21. Creates a ProductDecorator which: • resides in app/decorators • inherits

    from Draper::Decorator • has access to Rails & application specific helpers
  22. Use case

  23. # Decorator class ProductDecorator < Draper::Decorator def price h.number_to_currency(source.price) end

    def buy_link h.link_to "Shut up and take my money!", h.buy_product_path(self) end def updated_at source.updated_at.strftime("%A, %B %e") end end # Controller class ProductsController < ApplicationController ... def show @product = Product.find(1).decorate # or @product = ProductDecorator.new( Product.find(1)) # or even @product = ProductDecorator.decorate( Product.find(1)) end ... end # View <h2><%= @product.price %></h2> ... <p><%= @product.updated_at %></p> ... <p><%= @product.buy_link %></p>
  24. Features

  25. Decorating associations class ProductDecorator < Draper::Decorator # Decorates brand with

    BrandDecorator decorates_association :brand #... end
  26. Decorating collections class ProductsController < ApplicationController def index @products =

    ProductDecorator.decorate_collection(Product.all) # or if ActiveRecord::Relation @products = Product.scoped.decorate end end
  27. Decorated Finders # Decorator class ProductDecorator < Draper::Decorator decorates_finders #...

    end # Controller class ProductsController < ApplicationController def index # Returns collection of decorated models @product = ProductDecorator.find_all_by_id(1..8) end def show # Returns the decorated model @product = ProductDecorator.find(1) end end
  28. Delegating class ProductDecorator < Draper::Decorator # Delegate all non defined

    decorator methods to the model # Can prevent from using source or model delegate_all #... end
  29. Delegating # Decorator class ProductDecorator < Draper::Decorator # Delegate specific

    methods to model delegate :description delegate :name, to: :brand, prefix: true #... end # View <%= @product.description %> <%= @product.brand_name %>
  30. When to use it?

  31. ‣ Bake alternate representation of JSON, XML ‣ Format complex

    data for user display ‣ Define commonly-used representations of an object, like a name method that combines first_name and last_name attributes ‣ Mark up attributes with a little semantic HTML, like turning a url field into a hyperlink