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)