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)