Join the social network of Tech Nerds, increase skill rank, get work, manage projects...
 
  • Draper decorator (rails refactoring)

    • 0
    • 0
    • 0
    • 0
    • 0
    • 0
    • 0
    • 0
    • 739
    Comment on it

    In this tutorial we will have a look on Draper gem, which helps us decorate our views more like in object oriented approach. Its a great gem and provides us lots of options to decorate single object and collection of objects. We discussed presenter/decorator pattern in my previous post. We looked different aspects of the pattern and discussed the benefits of using this approach. We used rails SimpleDelegator class to create our decorators.  Here we will see how can we use Draper for our decorators.

     

    As per the Draper documentation, decorators are the ideal place to:

     1. format complex data for presentation
     2. common representation of information example: showing full name from first_name and last_name
     3. for formatting html elements example: returning link from url.


    In our application we have a products listing page. Here we have several products with images and additional details like pricing, manufacturer , variants etc. The page is so complex that it has number of methods in view helper modules. Its hard to manage helpers when there is too much logic and methods. Also helpers can not be used in object oriented way. The methods in helpers seem much like utility method. There is no receiver of the method. Since rails mixes the methods in view the method is globally available. Suppose i have two methods with same name in two modules, they get mixed in view.


    For these reasons we can use decorators instead of using view helpers. We have a Product model. With Draper, we create a corresponding ProductDecorator. The decorator wraps the model, and deals with presentational concerns.


    Installing Draper

    gem 'draper'

     

    After draper is installed run the generator for product:

     

    $ rails generate decorator product
          create  app/decorators/product_decorator.rb
          invoke  test_unit
          create    test/decorators/product_decorator_test.rb

     

    It creates a decorators folder inside app directory and one file product_decorator.rb. All decorators we generate inherits from Draper::Decorator.


    Now in our controller we can decorate our model object like this:
     

    # app/controllers/product_controller.rb
    def show
      @product = Product.find(params[:id]).decorate
    end


    Now we can directly write the methods in our decorator class. All methods will be available to the decorated object by calling method on itself (much object oriented way).


    Accessing Helpers:

    Default helpers provided by rails are very useful. You can access these helpers( including the helpers defined in our app) inside decorator by calling h method:

     

    class ProductDecorator < Draper::Decorator
      def description
        h.content_tag(:p, "draper is very nice gem")
      end
    end

    Note: If you want to get rid of h and call helper methods directly, you can include Draper::LazyHelpers in your decorator.

     

    A simple decorator for my product:
     

    class ProductDecorator < Draper::Decorator
      delegate_all
    
      def image
        image_path = avatar.present? ? avatar.url : default_image
        h.image_tag(image_path, class: "avatar")
      end
     
      def seller
        full_name
      end
    
      def seller_website
        add_link(full_name, website_url)
      end
    
      def image_with_url(image, url)
        add_link(image, url)
      end
     
      private
     
      def full_name
         [first_name, last_name].reject(&:blank?).join(" ")
      end
    
      def default_image
        "default.jpg"
      end
    
      def add_link(content, url)
        h.link_to_if(url.present? content, url)
      end
    end


    Accessing the model object:


    Inside decorator you can access the wrapped model instance by object.

    class ProductDecorator < Draper::Decorator
      def status
         object.status
      end
    end


    Decorating Single Object:

    @product = Product.find(params[:id]).decorate

    You can also use a more general decorator to decorate an object directly. Say you have Machine object and you want to decorate it with ProductDecorator. You can do like this:

    @machine = ProductDecorator.new(Machine.first)
    # or
    @machine = ProductDecorator.decorate(Machine.first)


    Adding methods to decorator:

     

    class ProductDecorator < Draper::Decorator
      def handling_charges
        (object.price)*0.1
      end
    
      def price
        super+handling_charges
      end
    end


    Collections:

    Decorating a collection:

    @products = ProductDecorator.decorate_collection(Product.all)
    
    and
    
    @products = Product.cheap.decorate

     

    Adding method to decorated collection OR Decorating the Collection Itself:


    create a collection class that inherits from Draper::CollectionDecorator and save it as products_decorator.rb:

     

    # app/decorators/products_decorator.rb
     

    class ProductsDecorator < Draper::CollectionDecorator
      def page_number
        55
      end
    end
    
    
    @products = ProductsDecorator.new(Product.all)
    # or
    @products = ProductsDecorator.new(Product.all)

     

    We can call page_number method on @products collection

    @products.page_number
    #=> 55

     

    Decorating Associated Objects:

    You can also decorate associated objects automatically by using decorates_association. Suppose our product has many variants, we can use like:

     

    class ProductDecorator < Draper::Decorator
      decorates_association :variants
    end

     

    When ProductDecorator decorate an object of Product, it will automatically decorate the associated variants using VariantDecorator.

     

    Decorated Finders:

    Using decorates_finders in our decorator let use active record finders. We can perform like this:


     

    UserDecorator.first
    #=> #<UserDecorator:0x00000004cd2608 @object=#<User id: 1, email: "ravi@yahoo.com", name: "rav", created_at: "2016-03-31 12:24:40", updated_at: "2016-03-31 12:24:40">, @context={}>
    
    

    It returns decorated objects.


    Deletgate:

    With delegate_all we can delegate the methods to wrapped object if not found in decorator. We can also restrict which methods we want to delegate.


    We have seen some of the strong features of draper decorator gem. There are so many options available with Draper. For more details please go to https://github.com/drapergem/draper

 0 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: