Skip to content
haines edited this page Jan 23, 2013 · 8 revisions

A lot has changed in Draper 1.0, both under the hood and in the API. You can check out the change log or the compare view to see the extent of the changes, but if you just want to get up and running, here's a quick guide to how to upgrade from Draper 0.x.

Renaming

Draper::Base is now Draper::Decorator

You'll need to update your decorators' superclass (don't forget that you can have an ApplicationDecorator and inherit from that!).

Draper::DecoratorEnumerableProxy is now Draper::CollectionDecorator

You might not have been inheriting from this class yourself, but doing so is a great way to add methods to your decorated collections (e.g. for pagination).

Draper::ModelSupport is now Draper::Decoratable

It's included by default into ActiveRecord::Base and Mongoid::Document, so if your models inherit from one of those, you're already set. If you want to decorate plain old Ruby objects, you should include this module manually to get a decorate instance method.

Delegation

Decorators don't automatically delegate missing methods to the model

To regain this behavior you need to add delegate_all to the top of your decorator. Newly-generated decorators will have this by default.

Removed method security (allows, denies, and denies_all)

If you were previously controlling which methods were delegated using allows, denies, or denies_all, these methods have now been removed. Now, to control delegation don't add the delegate_all. For denies_all-style behavior, that's it, you're done!

To bring some delegation back into play, you can use the standard Module.delegate from Active Support. We've overridden it to make to: :source the default, so you can just write delegate :foo, :bar where you might have had allows :foo, :bar. Unfortunately, if you were using denies, you're out of luck (unless you override method_missing yourself) - try switching to a whitelist approach instead.

Delegating class methods - decorates is now optional

delegate_all will also try to send missing class methods to the model class. If you follow the naming convention of SomeModelDecorator decorating SomeModel, Draper infers the model class from the decorator class and you don't need to put decorates :some_model at the top any more. If you don't follow this convention, you'll still need the decorates if you want the class methods to be delegated.

Decorating things

Don't use Decorator.decorate on a collection

Previously, when you did ProductDecorator.decorate(Product.all), Draper guessed that what you actually wanted was a collection of ProductDecorators. Now, you need to tell it explicitly by doing ProductDecorator.decorate_collection(Product.all).

Note that for Active Record queries you can do Product.where(color: "red").decorate instead. However, in Rails 3 all returns an array, not a query, so Product.all.decorate won't work (but you can instead do Product.scoped.decorate or Product.popular.decorate). Since Rails 4, all returns a query so Product.all.decorate will work.

Decorator.decorate is the same as Decorator.new

Without the collection-decorating magic, Decorator.decorate is now a simple alias for Decorator.new. Even more tersely, though, you can use the decorate instance method on models - Product.find(params[:id]).decorate.

Decorated finders are now opt-in

Draper no longer decorates ProductDecorator.find(id) and other finder methods out of the box. Other methods of decorating found objects (e.g. Product.find(id).decorate) should be preferred, but if you want to get this functionality back you can add decorates_finders to your decorator definition.

Referencing the model

No more automatically-named accessor

A ProductDecorator used to magically get a product method that returned the underlying model. That isn't generated any more - it's now just called source (and aliased to model). You can get your product method back by adding an alias_method :product, :source.

Using collection decorators

Delegating to the source

A DecoratedEnumerableProxy proxied missing methods to the source collection much like a regular decorator. However, this could lead to inconsistent state if the method modified the collection (e.g. build) because the collection of decorators was not updated to match.

The new CollectionDecorator is designed to avoid these inconsistencies by never delegating methods to the source - all array methods are delegated to the decorated collection, not the source collection. However, sometimes you do need access to the underlying collection (e.g. when using pagination gems that add methods to ActiveRecord::Relation). In this case you can access the protected source method in a custom subclass of CollectionDecorator (e.g. a PaginatingDecorator). See the README for examples.

This change will break your application if you were previously doing something like this:

@article = Article.find(params[:id]).decorate

# ArticleDecorator decorates_association :comments
@article.comments.build
# => NoMethodError

We would suggest that you avoid using decorators in this way - they are designed to interact with the view, not to be modified in the controller. Make sure decoration is the last thing that happens before you hand off to the view.

Storing extra data

Options aren't saved, use context

Decorators are now fussy about the options you give them. In 0.18 you could save extra contextual information by doing Decorator.decorate(model, role: "admin") and then accessing it through the saved options hash.

In 1.0 you should use the :context option: Decorator.decorate(model, context: {role: "admin"}). The hash is then available through the context method, as you might expect.