2012-12-25 18:33:35 -05:00
|
|
|
# Draper: View Models for Rails
|
|
|
|
|
2014-07-07 08:41:14 -04:00
|
|
|
[![TravisCI Build Status](https://travis-ci.org/drapergem/draper.svg?branch=master)](http://travis-ci.org/drapergem/draper)
|
2016-07-09 13:06:20 -04:00
|
|
|
[![Code Climate](https://codeclimate.com/github/drapergem/draper.svg)](https://codeclimate.com/github/drapergem/draper)
|
2019-03-18 09:21:35 -04:00
|
|
|
[![Test Coverage](https://api.codeclimate.com/v1/badges/0d40c43951d516bf6985/test_coverage)](https://codeclimate.com/github/drapergem/draper/test_coverage)
|
2016-07-09 13:06:20 -04:00
|
|
|
[![Inline docs](http://inch-ci.org/github/drapergem/draper.svg?branch=master)](http://inch-ci.org/github/drapergem/draper)
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Draper adds an object-oriented layer of presentation logic to your Rails
|
|
|
|
application.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Without Draper, this functionality might have been tangled up in procedural
|
|
|
|
helpers or adding bulk to your models. With Draper decorators, you can wrap your
|
|
|
|
models with presentation-related logic to organise - and test - this layer of
|
|
|
|
your app much more effectively.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
## Why Use a Decorator?
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Imagine your application has an `Article` model. With Draper, you'd create a
|
|
|
|
corresponding `ArticleDecorator`. The decorator wraps the model, and deals
|
|
|
|
*only* with presentational concerns. In the controller, you decorate the article
|
|
|
|
before handing it off to the view:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
# app/controllers/articles_controller.rb
|
|
|
|
def show
|
|
|
|
@article = Article.find(params[:id]).decorate
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
In the view, you can use the decorator in exactly the same way as you would have
|
|
|
|
used the model. But whenever you start needing logic in the view or start
|
|
|
|
thinking about a helper method, you can implement a method on the decorator
|
|
|
|
instead.
|
2013-01-14 12:36:50 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Let's look at how you could convert an existing Rails helper to a decorator
|
|
|
|
method. You have this existing helper:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
# app/helpers/articles_helper.rb
|
|
|
|
def publication_status(article)
|
|
|
|
if article.published?
|
|
|
|
"Published at #{article.published_at.strftime('%A, %B %e')}"
|
|
|
|
else
|
|
|
|
"Unpublished"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
But it makes you a little uncomfortable. `publication_status` lives in a
|
|
|
|
nebulous namespace spread across all controllers and view. Down the road, you
|
|
|
|
might want to display the publication status of a `Book`. And, of course, your
|
2016-01-02 00:36:15 -05:00
|
|
|
design calls for a slightly different formatting to the date for a `Book`.
|
2013-01-14 12:36:50 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Now your helper method can either switch based on the input class type (poor
|
|
|
|
Ruby style), or you break it out into two methods, `book_publication_status` and
|
|
|
|
`article_publication_status`. And keep adding methods for each publication
|
2013-10-26 18:28:45 -04:00
|
|
|
type...to the global helper namespace. And you'll have to remember all the names. Ick.
|
2013-01-14 12:36:50 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Ruby thrives when we use Object-Oriented style. If you didn't know Rails'
|
|
|
|
helpers existed, you'd probably imagine that your view template could feature
|
|
|
|
something like this:
|
2013-01-14 12:36:50 -05:00
|
|
|
|
|
|
|
```erb
|
|
|
|
<%= @article.publication_status %>
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Without a decorator, you'd have to implement the `publication_status` method in
|
|
|
|
the `Article` model. That method is presentation-centric, and thus does not
|
|
|
|
belong in a model.
|
2013-01-14 12:36:50 -05:00
|
|
|
|
|
|
|
Instead, you implement a decorator:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
# app/decorators/article_decorator.rb
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
2013-01-13 22:27:58 -05:00
|
|
|
delegate_all
|
|
|
|
|
2012-12-25 18:33:35 -05:00
|
|
|
def publication_status
|
|
|
|
if published?
|
|
|
|
"Published at #{published_at}"
|
|
|
|
else
|
|
|
|
"Unpublished"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def published_at
|
2013-04-30 09:10:52 -04:00
|
|
|
object.published_at.strftime("%A, %B %e")
|
2012-12-25 18:33:35 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Within the `publication_status` method we use the `published?` method. Where
|
|
|
|
does that come from? It's a method of the source `Article`, whose methods have
|
|
|
|
been made available on the decorator by the `delegate_all` call above.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
You might have heard this sort of decorator called a "presenter", an "exhibit",
|
|
|
|
a "view model", or even just a "view" (in that nomenclature, what Rails calls
|
|
|
|
"views" are actually "templates"). Whatever you call it, it's a great way to
|
|
|
|
replace procedural helpers like the one above with "real" object-oriented
|
|
|
|
programming.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
Decorators are the ideal place to:
|
2013-01-14 12:36:50 -05:00
|
|
|
* format complex data for user display
|
2013-04-04 17:00:42 -04:00
|
|
|
* define commonly-used representations of an object, like a `name` method that
|
|
|
|
combines `first_name` and `last_name` attributes
|
|
|
|
* mark up attributes with a little semantic HTML, like turning a `url` field
|
|
|
|
into a hyperlink
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
## Installation
|
|
|
|
|
2020-02-05 09:55:02 -05:00
|
|
|
As of version 4.0.0, Draper only officially supports Rails 5.2 / Ruby 2.4 and later. Add Draper to your Gemfile.
|
2017-02-25 09:01:36 -05:00
|
|
|
|
|
|
|
```ruby
|
2017-05-08 10:52:20 -04:00
|
|
|
gem 'draper'
|
2012-12-25 18:33:35 -05:00
|
|
|
```
|
|
|
|
|
2017-02-25 09:01:36 -05:00
|
|
|
After that, run `bundle install` within your app's directory.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
If you're upgrading from a 0.x release, the major changes are outlined [in the
|
|
|
|
wiki](https://github.com/drapergem/draper/wiki/Upgrading-to-1.0).
|
2013-01-14 14:44:09 -05:00
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
## Writing Decorators
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Decorators inherit from `Draper::Decorator`, live in your `app/decorators`
|
|
|
|
directory, and are named for the model that they decorate:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
# app/decorators/article_decorator.rb
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
|
|
|
# ...
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
### Generators
|
|
|
|
|
2017-04-05 14:43:27 -04:00
|
|
|
To create an `ApplicationDecorator` that all generated decorators inherit from, run...
|
|
|
|
|
|
|
|
```
|
|
|
|
rails generate draper:install
|
|
|
|
```
|
|
|
|
|
2013-10-26 18:28:45 -04:00
|
|
|
When you have Draper installed and generate a controller...
|
2013-01-14 12:36:50 -05:00
|
|
|
|
|
|
|
```
|
|
|
|
rails generate resource Article
|
|
|
|
```
|
2013-06-08 02:27:14 -04:00
|
|
|
|
2013-01-14 14:44:30 -05:00
|
|
|
...you'll get a decorator for free!
|
2013-01-14 12:36:50 -05:00
|
|
|
|
|
|
|
But if the `Article` model already exists, you can run...
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
```
|
|
|
|
rails generate decorator Article
|
|
|
|
```
|
|
|
|
|
|
|
|
...to create the `ArticleDecorator`.
|
|
|
|
|
|
|
|
### Accessing Helpers
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Normal Rails helpers are still useful for lots of tasks. Both Rails' provided
|
2013-10-26 18:28:45 -04:00
|
|
|
helpers and those defined in your app can be accessed within a decorator via the `h` method:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
|
|
|
def emphatic
|
2013-01-14 12:36:50 -05:00
|
|
|
h.content_tag(:strong, "Awesome")
|
2012-12-25 18:33:35 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
If writing `h.` frequently is getting you down, you can add...
|
|
|
|
|
|
|
|
```
|
|
|
|
include Draper::LazyHelpers
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
...at the top of your decorator class - you'll mix in a bazillion methods and
|
|
|
|
never have to type `h.` again.
|
2013-10-26 18:28:45 -04:00
|
|
|
|
|
|
|
(*Note*: the `capture` method is only available through `h` or `helpers`)
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
### Accessing the model
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
When writing decorator methods you'll usually need to access the wrapped model.
|
|
|
|
While you may choose to use delegation ([covered below](#delegating-methods))
|
2013-04-30 09:10:52 -04:00
|
|
|
for convenience, you can always use the `object` (or its alias `model`):
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
|
|
|
def published_at
|
2013-04-30 09:10:52 -04:00
|
|
|
object.published_at.strftime("%A, %B %e")
|
2012-12-25 18:33:35 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
## Decorating Objects
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
### Single Objects
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-10-26 18:28:45 -04:00
|
|
|
Ok, so you've written a sweet decorator, now you're going to want to put it into
|
2013-04-04 17:00:42 -04:00
|
|
|
action! A simple option is to call the `decorate` method on your model:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
@article = Article.first.decorate
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
This infers the decorator from the object being decorated. If you want more
|
|
|
|
control - say you want to decorate a `Widget` with a more general
|
|
|
|
`ProductDecorator` - then you can instantiate a decorator directly:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
2012-12-31 14:09:16 -05:00
|
|
|
@widget = ProductDecorator.new(Widget.first)
|
2012-12-25 18:33:35 -05:00
|
|
|
# or, equivalently
|
2012-12-31 14:09:16 -05:00
|
|
|
@widget = ProductDecorator.decorate(Widget.first)
|
2012-12-25 18:33:35 -05:00
|
|
|
```
|
|
|
|
|
|
|
|
### Collections
|
|
|
|
|
2013-10-26 18:28:45 -04:00
|
|
|
#### Decorating Individual Elements
|
|
|
|
|
|
|
|
If you have a collection of objects, you can decorate them all in one fell
|
2013-04-04 17:00:42 -04:00
|
|
|
swoop:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
@articles = ArticleDecorator.decorate_collection(Article.all)
|
2013-01-14 12:36:50 -05:00
|
|
|
```
|
|
|
|
|
|
|
|
If your collection is an ActiveRecord query, you can use this:
|
|
|
|
|
|
|
|
```ruby
|
2012-12-25 18:33:35 -05:00
|
|
|
@articles = Article.popular.decorate
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
*Note:* In Rails 3, the `.all` method returns an array and not a query. Thus you
|
|
|
|
_cannot_ use the technique of `Article.all.decorate` in Rails 3. In Rails 4,
|
|
|
|
`.all` returns a query so this techique would work fine.
|
2013-01-14 12:36:50 -05:00
|
|
|
|
2013-10-26 18:28:45 -04:00
|
|
|
#### Decorating the Collection Itself
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
If you want to add methods to your decorated collection (for example, for
|
|
|
|
pagination), you can subclass `Draper::CollectionDecorator`:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
# app/decorators/articles_decorator.rb
|
|
|
|
class ArticlesDecorator < Draper::CollectionDecorator
|
|
|
|
def page_number
|
|
|
|
42
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# elsewhere...
|
|
|
|
@articles = ArticlesDecorator.new(Article.all)
|
|
|
|
# or, equivalently
|
|
|
|
@articles = ArticlesDecorator.decorate(Article.all)
|
|
|
|
```
|
|
|
|
|
2013-10-26 18:28:45 -04:00
|
|
|
Draper decorates each item by calling the `decorate` method. Alternatively, you can
|
2013-04-04 17:00:42 -04:00
|
|
|
specify a decorator by overriding the collection decorator's `decorator_class`
|
|
|
|
method, or by passing the `:with` option to the constructor.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-01-23 06:07:40 -05:00
|
|
|
#### Using pagination
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Some pagination gems add methods to `ActiveRecord::Relation`. For example,
|
|
|
|
[Kaminari](https://github.com/amatsuda/kaminari)'s `paginate` helper method
|
|
|
|
requires the collection to implement `current_page`, `total_pages`, and
|
|
|
|
`limit_value`. To expose these on a collection decorator, you can delegate to
|
2013-04-30 09:10:52 -04:00
|
|
|
the `object`:
|
2012-12-31 12:44:41 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class PaginatingDecorator < Draper::CollectionDecorator
|
2014-07-01 11:55:08 -04:00
|
|
|
delegate :current_page, :total_pages, :limit_value, :entry_name, :total_count, :offset_value, :last_page?
|
2012-12-31 12:44:41 -05:00
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
The `delegate` method used here is the same as that added by [Active
|
|
|
|
Support](http://api.rubyonrails.org/classes/Module.html#method-i-delegate),
|
2013-04-30 09:10:52 -04:00
|
|
|
except that the `:to` option is not required; it defaults to `:object` when
|
2013-04-04 17:00:42 -04:00
|
|
|
omitted.
|
2013-01-13 22:27:58 -05:00
|
|
|
|
2013-10-26 18:28:45 -04:00
|
|
|
[will_paginate](https://github.com/mislav/will_paginate) needs the following delegations:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
delegate :current_page, :per_page, :offset, :total_entries, :total_pages
|
|
|
|
```
|
2013-01-23 06:29:41 -05:00
|
|
|
|
2016-03-22 17:01:06 -04:00
|
|
|
If needed, you can then set the collection_decorator_class of your CustomDecorator as follows:
|
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
|
|
|
def self.collection_decorator_class
|
|
|
|
PaginatingDecorator
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ArticleDecorator.decorate_collection(@articles.paginate)
|
|
|
|
# => Collection decorated by PaginatingDecorator
|
|
|
|
# => Members decorated by ArticleDecorator
|
|
|
|
```
|
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
### Decorating Associated Objects
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
You can automatically decorate associated models when the primary model is
|
|
|
|
decorated. Assuming an `Article` model has an associated `Author` object:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
|
|
|
decorates_association :author
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
When `ArticleDecorator` decorates an `Article`, it will also use
|
|
|
|
`AuthorDecorator` to decorate the associated `Author`.
|
2013-01-14 12:36:50 -05:00
|
|
|
|
|
|
|
### Decorated Finders
|
|
|
|
|
|
|
|
You can call `decorates_finders` in a decorator...
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
|
|
|
decorates_finders
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
...which allows you to then call all the normal ActiveRecord-style finders on
|
|
|
|
your `ArticleDecorator` and they'll return decorated objects:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
@article = ArticleDecorator.find(params[:id])
|
|
|
|
```
|
|
|
|
|
2019-02-25 11:44:19 -05:00
|
|
|
### Decorated Query Methods
|
|
|
|
By default, Draper will decorate all [QueryMethods](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html)
|
|
|
|
of ActiveRecord.
|
|
|
|
If you're using another ORM, in order to support it, you can tell Draper to use a custom strategy:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
Draper.configure do |config|
|
|
|
|
config.default_query_methods_strategy = :mongoid
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-10-26 18:28:45 -04:00
|
|
|
### When to Decorate Objects
|
2013-04-04 16:49:50 -04:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Decorators are supposed to behave very much like the models they decorate, and
|
|
|
|
for that reason it is very tempting to just decorate your objects at the start
|
|
|
|
of your controller action and then use the decorators throughout. *Don't*.
|
2013-04-04 16:49:50 -04:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Because decorators are designed to be consumed by the view, you should only be
|
|
|
|
accessing them there. Manipulate your models to get things ready, then decorate
|
|
|
|
at the last minute, right before you render the view. This avoids many of the
|
|
|
|
common pitfalls that arise from attempting to modify decorators (in particular,
|
|
|
|
collection decorators) after creating them.
|
2013-04-04 16:49:50 -04:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
To help you make your decorators read-only, we have the `decorates_assigned`
|
|
|
|
method in your controller. It adds a helper method that returns the decorated
|
|
|
|
version of an instance variable:
|
2013-04-04 16:49:50 -04:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
# app/controllers/articles_controller.rb
|
|
|
|
class ArticlesController < ApplicationController
|
|
|
|
decorates_assigned :article
|
|
|
|
|
|
|
|
def show
|
|
|
|
@article = Article.find(params[:id])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
The `decorates_assigned :article` bit is roughly equivalent to
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
def article
|
|
|
|
@decorated_article ||= @article.decorate
|
|
|
|
end
|
|
|
|
helper_method :article
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
This means that you can just replace `@article` with `article` in your views and
|
|
|
|
you'll have access to an ArticleDecorator object instead. In your controller you
|
|
|
|
can continue to use the `@article` instance variable to manipulate the model -
|
|
|
|
for example, `@article.comments.build` to add a new blank comment for a form.
|
2013-04-04 16:49:50 -04:00
|
|
|
|
2017-03-31 10:05:15 -04:00
|
|
|
## Configuration
|
2019-05-31 09:33:41 -04:00
|
|
|
Draper works out the box well, but also provides a hook for you to configure its
|
2017-03-31 10:05:15 -04:00
|
|
|
default functionality. For example, Draper assumes you have a base `ApplicationController`.
|
|
|
|
If your base controller is named something different (e.g. `BaseController`),
|
|
|
|
you can tell Draper to use it by adding the following to an initializer:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
Draper.configure do |config|
|
|
|
|
config.default_controller = BaseController
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2012-12-25 18:33:35 -05:00
|
|
|
## Testing
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Draper supports RSpec, MiniTest::Rails, and Test::Unit, and will add the
|
|
|
|
appropriate tests when you generate a decorator.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
### RSpec
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Your specs are expected to live in `spec/decorators`. If you use a different
|
|
|
|
path, you need to tag them with `type: :decorator`.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
In a controller spec, you might want to check whether your instance variables
|
|
|
|
are being decorated properly. You can use the handy predicate matchers:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
assigns(:article).should be_decorated
|
2013-01-14 12:36:50 -05:00
|
|
|
|
2012-12-25 18:33:35 -05:00
|
|
|
# or, if you want to be more specific
|
|
|
|
assigns(:article).should be_decorated_with ArticleDecorator
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Note that `model.decorate == model`, so your existing specs shouldn't break when
|
|
|
|
you add the decoration.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
#### Spork Users
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
In your `Spork.prefork` block of `spec_helper.rb`, add this:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
require 'draper/test/rspec_integration'
|
|
|
|
```
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2015-07-13 08:16:43 -04:00
|
|
|
#### Custom Draper Controller ViewContext
|
|
|
|
If running tests in an engine setting with a controller other than "ApplicationController," set a custom controller in `spec_helper.rb`
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
config.before(:each, type: :decorator) do |example|
|
|
|
|
Draper::ViewContext.controller = ExampleEngine::CustomRootController.new
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-10-26 18:28:45 -04:00
|
|
|
### Isolated Tests
|
2013-01-18 11:40:57 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
In tests, Draper needs to build a view context to access helper methods. By
|
|
|
|
default, it will create an `ApplicationController` and then use its view
|
|
|
|
context. If you are speeding up your test suite by testing each component in
|
|
|
|
isolation, you can eliminate this dependency by putting the following in your
|
|
|
|
`spec_helper` or similar:
|
2013-01-18 11:40:57 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
Draper::ViewContext.test_strategy :fast
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
In doing so, your decorators will no longer have access to your application's
|
|
|
|
helpers. If you need to selectively include such helpers, you can pass a block:
|
2013-01-18 11:40:57 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
Draper::ViewContext.test_strategy :fast do
|
|
|
|
include ApplicationHelper
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-10-26 18:28:45 -04:00
|
|
|
#### Stubbing Route Helper Functions
|
2013-04-05 10:51:28 -04:00
|
|
|
|
|
|
|
If you are writing isolated tests for Draper methods that call route helper
|
2013-04-05 13:54:49 -04:00
|
|
|
methods, you can stub them instead of needing to require Rails.
|
2013-04-05 10:51:28 -04:00
|
|
|
|
2013-04-05 13:54:49 -04:00
|
|
|
If you are using RSpec, minitest-rails, or the Test::Unit syntax of minitest,
|
|
|
|
you already have access to the Draper `helpers` in your tests since they
|
|
|
|
inherit from `Draper::TestCase`. If you are using minitest's spec syntax
|
|
|
|
without minitest-rails, you can explicitly include the Draper `helpers`:
|
2013-04-05 10:51:28 -04:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
describe YourDecorator do
|
|
|
|
include Draper::ViewHelpers
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
Then you can stub the specific route helper functions you need using your
|
2019-05-31 09:33:41 -04:00
|
|
|
preferred stubbing technique. This examples uses Rspec currently recommended API
|
|
|
|
available in RSpec 3.6+
|
2013-04-05 10:51:28 -04:00
|
|
|
|
|
|
|
```ruby
|
2019-05-31 09:33:41 -04:00
|
|
|
without_partial_double_verification do
|
|
|
|
allow(helpers).to receive(:users_path).and_return('/users')
|
|
|
|
end
|
2013-04-05 10:51:28 -04:00
|
|
|
```
|
|
|
|
|
2019-03-14 11:02:45 -04:00
|
|
|
### View context leakage
|
|
|
|
As mentioned before, Draper needs to build a view context to access helper methods. In MiniTest, the view context is
|
|
|
|
cleared during `before_setup` preventing any view context leakage. In RSpec, the view context is cleared before each
|
|
|
|
`decorator`, `controller`, and `mailer` spec. However, if you use decorators in other types of specs
|
|
|
|
(e.g. `job`), you may still experience the view context leaking from the previous spec. To solve this, add the
|
|
|
|
following to your `spec_helper` for each type of spec you are experiencing the leakage:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
config.before(:each, type: :type) { Draper::ViewContext.clear! }
|
|
|
|
```
|
|
|
|
|
|
|
|
_Note_: The `:type` above is just a placeholder. Replace `:type` with the type of spec you are experiencing
|
|
|
|
the leakage from.
|
|
|
|
|
2012-12-25 18:33:35 -05:00
|
|
|
## Advanced usage
|
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
### Shared Decorator Methods
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
You might have several decorators that share similar needs. Since decorators are
|
|
|
|
just Ruby objects, you can use any normal Ruby technique for sharing
|
|
|
|
functionality.
|
2013-01-14 12:36:50 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
In Rails controllers, common functionality is organized by having all
|
|
|
|
controllers inherit from `ApplicationController`. You can apply this same
|
|
|
|
pattern to your decorators:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
# app/decorators/application_decorator.rb
|
|
|
|
class ApplicationDecorator < Draper::Decorator
|
|
|
|
# ...
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Then modify your decorators to inherit from that `ApplicationDecorator` instead
|
|
|
|
of directly from `Draper::Decorator`:
|
2013-01-14 12:36:50 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < ApplicationDecorator
|
|
|
|
# decorator methods
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
### Delegating Methods
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
When your decorator calls `delegate_all`, any method called on the decorator not
|
2013-04-30 09:10:52 -04:00
|
|
|
defined in the decorator itself will be delegated to the decorated object. This
|
2017-04-03 12:24:01 -04:00
|
|
|
includes calling `super` from within the decorator. A call to `super` from within
|
|
|
|
the decorator will first try to call the method on the parent decorator class. If
|
|
|
|
the method does not exist on the parent decorator class, it will then try to call
|
|
|
|
the method on the decorated `object`. This is a very permissive interface.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
If you want to strictly control which methods are called within views, you can
|
|
|
|
choose to only delegate certain methods from the decorator to the source model:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
2013-01-14 12:36:50 -05:00
|
|
|
delegate :title, :body
|
2012-12-25 18:33:35 -05:00
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-30 09:10:52 -04:00
|
|
|
We omit the `:to` argument here as it defaults to the `object` being decorated.
|
|
|
|
You could choose to delegate methods to other places like this:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
|
|
|
delegate :title, :body
|
2013-01-21 19:18:24 -05:00
|
|
|
delegate :name, :title, to: :author, prefix: true
|
2013-01-14 12:36:50 -05:00
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
From your view template, assuming `@article` is decorated, you could do any of
|
|
|
|
the following:
|
2013-01-14 12:36:50 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
@article.title # Returns the article's `.title`
|
2013-01-21 18:36:13 -05:00
|
|
|
@article.body # Returns the article's `.body`
|
|
|
|
@article.author_name # Returns the article's `author.name`
|
2013-01-14 12:36:50 -05:00
|
|
|
@article.author_title # Returns the article's `author.title`
|
|
|
|
```
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-10-26 18:28:45 -04:00
|
|
|
### Adding Context
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
If you need to pass extra data to your decorators, you can use a `context` hash.
|
|
|
|
Methods that create decorators take it as an option, for example:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
Article.first.decorate(context: {role: :admin})
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
The value passed to the `:context` option is then available in the decorator
|
|
|
|
through the `context` method.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
If you use `decorates_association`, the context of the parent decorator is
|
|
|
|
passed to the associated decorators. You can override this with the `:context`
|
|
|
|
option:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
|
|
|
decorates_association :author, context: {foo: "bar"}
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
or, if you want to modify the parent's context, use a lambda that takes a hash
|
|
|
|
and returns a new hash:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
|
|
|
decorates_association :author,
|
|
|
|
context: ->(parent_context){ parent_context.merge(foo: "bar") }
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
### Specifying Decorators
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
When you're using `decorates_association`, Draper uses the `decorate` method on
|
|
|
|
the associated record(s) to perform the decoration. If you want use a specific
|
|
|
|
decorator, you can use the `:with` option:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
|
|
|
decorates_association :author, with: FancyPersonDecorator
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
For a collection association, you can specify a `CollectionDecorator` subclass,
|
|
|
|
which is applied to the whole collection, or a singular `Decorator` subclass,
|
|
|
|
which is applied to each item individually.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
### Scoping Associations
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
If you want your decorated association to be ordered, limited, or otherwise
|
|
|
|
scoped, you can pass a `:scope` option to `decorates_association`, which will be
|
|
|
|
applied to the collection *before* decoration:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ArticleDecorator < Draper::Decorator
|
|
|
|
decorates_association :comments, scope: :recent
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
### Proxying Class Methods
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
If you want to proxy class methods to the wrapped model class, including when
|
|
|
|
using `decorates_finders`, Draper needs to know the model class. By default, it
|
|
|
|
assumes that your decorators are named `SomeModelDecorator`, and then attempts
|
|
|
|
to proxy unknown class methods to `SomeModel`.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
If your model name can't be inferred from your decorator name in this way, you
|
|
|
|
need to use the `decorates` method:
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class MySpecialArticleDecorator < Draper::Decorator
|
|
|
|
decorates :article
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-01-14 12:36:50 -05:00
|
|
|
This is only necessary when proxying class methods.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2016-07-04 17:07:06 -04:00
|
|
|
Once this association between the decorator and the model is set up, you can call
|
|
|
|
`SomeModel.decorator_class` to access class methods defined in the decorator.
|
|
|
|
If necessary, you can check if your model is decorated with `SomeModel.decorator_class?`.
|
2016-06-14 11:51:01 -04:00
|
|
|
|
2013-10-26 18:28:45 -04:00
|
|
|
### Making Models Decoratable
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Models get their `decorate` method from the `Draper::Decoratable` module, which
|
|
|
|
is included in `ActiveRecord::Base` and `Mongoid::Document` by default. If
|
2016-07-04 17:07:06 -04:00
|
|
|
you're using another ORM, or want to decorate plain old Ruby objects,
|
2013-04-04 17:00:42 -04:00
|
|
|
you can include this module manually.
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2017-09-11 12:35:14 -04:00
|
|
|
### Active Job Integration
|
|
|
|
|
2019-05-31 09:33:41 -04:00
|
|
|
[Active Job](http://edgeguides.rubyonrails.org/active_job_basics.html) allows you to pass ActiveRecord
|
2017-09-11 12:35:14 -04:00
|
|
|
objects to background tasks directly and performs the necessary serialization and deserialization. In
|
|
|
|
order to do this, arguments to a background job must implement [Global ID](https://github.com/rails/globalid).
|
|
|
|
Decorated objects implement Global ID by delegating to the object they are decorating. This means
|
|
|
|
you can pass decorated objects to background jobs, however, the object won't be decorated when it is
|
|
|
|
deserialized.
|
|
|
|
|
2012-12-25 18:33:35 -05:00
|
|
|
## Contributors
|
|
|
|
|
2013-04-04 17:00:42 -04:00
|
|
|
Draper was conceived by Jeff Casimir and heavily refined by Steve Klabnik and a
|
|
|
|
great community of open source
|
|
|
|
[contributors](https://github.com/drapergem/draper/contributors).
|
2012-12-25 18:33:35 -05:00
|
|
|
|
2016-07-09 11:52:45 -04:00
|
|
|
### Current maintainers
|
|
|
|
|
2017-05-08 10:52:20 -04:00
|
|
|
* Cliff Braton (cliff.braton@gmail.com)
|
2016-07-09 11:52:45 -04:00
|
|
|
|
|
|
|
### Historical maintainers
|
2012-12-25 18:33:35 -05:00
|
|
|
|
|
|
|
* Jeff Casimir (jeff@jumpstartlab.com)
|
|
|
|
* Steve Klabnik (steve@jumpstartlab.com)
|
|
|
|
* Vasiliy Ermolovich
|
|
|
|
* Andrew Haines
|
2017-05-08 10:52:20 -04:00
|
|
|
* Sean Linsley
|