Join the social network of Tech Nerds, increase skill rank, get work, manage projects...
 
  • Rails code patterns Decorator/Presenter (Refacotring)

    • 0
    • 0
    • 0
    • 0
    • 1
    • 0
    • 0
    • 0
    • 565
    Comment on it

    In my previous article i explained when we should refactor code and how to refactor. Lets take our discussion further to decorator/presenter pattern. Decorator/Presenter pattern have been overly used in rails community. There is a thin line between decorator and presenter.  I started my efforts to understand the idea behind this. I read few blogs/articles which explained the details. I have gathered my findings here in an effort to help others. I am thankful to the techies who wrote such nice blogs and explained in details.

     

    While building ruby on rails application we put all of the business logic in our models. However, some of the logic may not be directly related to the models or may be required in our views or some controllers. These fat codes clutter the models and put extra burdens. Here comes the decorator pattern to rescue. We can use decorators to extract fat codes from models.

     

    The idea of decorators seem to come from excessive methods being used in our view helpers. Helpers are modules which keeps our views related methods. However helpers are not object oriented. We have to pass the object in the helper methods. Keeping logic in views was never a good idea. For example: calling active record queries in views

     

    A decorator is a class that adds functionality to a specific object by wrapping it, without affecting other instances of that class. Decorator takes object in its constructor. Using decorator we can encapsulate methods intended for use by the views. This way we can keep the models separate but still access the properties.

    Sometimes a presenter is a decorator. A presenter is a class that adds presentation functionality to another class object.


    We can define decorators using couple of ways, lets use rails SimpleDelegator class:

    class Decorator < SimpleDelegator
    end
    
    class Product
      def price
        500
      end
    end
    
    product = Product.new
    car.price #=> 500
    
    now lets decorate our product object using decorator class we created:
    
    decorated_product = Decorator.new(product)
    decorated_product.price #=> 500

     

    NOTE: Have you noticed, we called "price" method on decorated object. The method was not defined in Decorator class though. So decorator delegates the undefined methods to the decorated object.

     

    We can define price method in our decorator:

    class Decorator < SimpleDelegator
      def price
        super + 100
      end
    end
    
    
    now lets decorate our product object using decorator class we created:
    
    decorated_product = Decorator.new(product)
    decorated_product.price #=> 600
    decorated_product.class
    #=> Decorator
    
    
    product.price #=> 500
    product.class
    #=> Product


    Note: What we noticed, decorators do not modify the objects, it adds functionality by wrapping the object inside.

     

    Let's take a good example to see in details. Suppose we order a product from some e-commerce website. The buyer have options to take insurance on a product by taking some insurance plan. The product price changes with the insurance plan.  
     

    class ProductDecorator < SimpleDelegator
      def coverage
        "0%"
      end
    end
    
    class WithPlatinumInsuranceDecorator < SimpleDelegator
      def price
        price = super
        price+(price*0.2)
      end
    
      def coverage
        "40%"
      end
    end
    
    class WithGoldInsuranceDecorator < SimpleDelegator
      def price
        price = super
        price+(price*0.4)
      end
    
      def coverage
        "60%"
      end
    end
    
    class WithDiamondInsuranceDecorator < SimpleDelegator
      def price
        price = super
        price+(price*0.6)
      end
    
      def coverage
        "80%"
      end
    end
    
    
    platinum_pro = WithPlatinumInsuranceDecorator.new(product)
    gold_pro = WithGoldInsuranceDecorator.new(product)
    diamond_pro = WithDiamondInsuranceDecorator.new(product)
    
    platinum_pro.price
    #=> 600.0
    
    gold_pro.price
    #=> 700.0
    
    diamond_pro.price
    #=> 800.0
    
    product.price
    #=> 500.0


    NOTE: In above example we could have used inheritance to create subclasses instead of using multiple decorators. However, there are some downside for using inheritance in this particular example. First, the requirement is much related to presenter/view then ActiveRecord classes. There will be a tight coupling between classes, also any changes in superclass internals will affect subclasses.


    Let's create a method to accept collection in decorator:

    class Decorator < SimpleDelegator
      def self.wrap_collection(collection)
        collection.map do |obj|
            new obj
        end
      end
    
      def price
        super + 100
      end
    end
    
    
    products = 1.upto(5).map{|f| Product.new}
     => [#<Product:0x007feed80d4d90>, #<Product:0x007feed80d4d68>, #<Product:0x007feed80d4d40>, #<Product:0x007feed80d4d18>, #<Product:0x007feed80d4cf0>]
    products.first.class
    #=> Product
    
    
    decorators = Decorator.wrap_collection(products)
     => [#<Product:0x007feed80d4d90>, #<Product:0x007feed80d4d68>, #<Product:0x007feed80d4d40>, #<Product:0x007feed80d4d18>, #<Product:0x007feed80d4cf0>]
    
    decorators.first.class
    #=> Decorator


    NOTE: We can use multiple decorators by wrapping each other. Decorator encapsulate the decorated object without affecting it and adds new methods.

     

    For further details please visit:  https://bibwild.wordpress.com/2012/12/19/the-simplest-rails-decorator-implementation-that-just-might-work/

 1 Comment(s)

Sign In
                           OR                           
                           OR                           
Register

Sign up using

                           OR                           
Forgot Password
Fill out the form below and instructions to reset your password will be emailed to you:
Reset Password
Fill out the form below and reset your password: