-
Notifications
You must be signed in to change notification settings - Fork 532
Using Tire with Draper
There's many reasons for wanting to augment either the Collection or the Item returned by Tire.
For example:
- Add additional view logic that you don't want to put in a helper
- Reuse view logic between ActiveRecord/Mongoid & Tire.
- Just to add additional methods to the collection or item.
You can do this using Draper which provides View-Models/Decorators.
There's two ways to go about it.
We could define just a ordinary Draper::Decorator
:
class Article < ActiveRecord::Base
include Tire::Model::Search
include Tire::Model::Callbacks
end
class ArticleDecorator < Draper::Decorator
# We can either delegate all methods not defined in our decorator
# or just specific methods that we want to pass on to the Tire::Results::Item instance.
delegate_all
def caption
source.title + '-' + source.description
end
end
We can then use it like this
articles = Article.search(query: 'Draper')
decorated_articles = ArticleDecorator.decorate_collection(articles)
That would give us an instance of Draper::CollectionDecorator
which is the default collection decorator in Draper.
So we could then do:
decorated_articles.first.caption
=> 'Using Tire with Draper - To augment Collection and Item'
# We also still have the methods from Tire::Results::Item at arms length
# since we used delegate_all in our ArticleDecorator.
decorated_articles.first._score
=> '0.30685282'
However, what we cannot do with this approach is to get to any facets or other methods defined on the Tire::Results::Collection
object.
decorated_articles.facets
=> NoMethodError: undefined method `facets' for #<Draper::CollectionDecorator:0x007f836d9888f0>
That's because the default collection decorator does not automatically delegate those methods to the source Tire::Search::Collection
instance. So if we don't need to access those properties, we are fine with this implementation.
We also need to define a Draper::CollectionDecorator
ArticlesDecorator < Draper::CollectionDecorator
# There's no delegate_all here so we'll need to delegate each
# methods we want to pass on to the Tire::Search::Collection
delegate :facets, :total
def some_method_that_deals_with_the_collection
# we can access the Tire::Search::Collection instance via the method `source`.
'something'
end
end
We can now use this collection decorator directly to decorate both the collection and the items. Draper will automatically look for a decorator named ArticleDecorator
, loop through our hits and wrap them with it.
articles = Article.search(query: 'Draper')
decorated_articles = ArticlesDecorator.new(articles)
# We can now access the method we delegated
decorated_articles.facets
=> { hash with the facets }
# Or the ones we defined in our ArticlesDecorator
decorated_articles.some_method_that_deals_with_the_collection
=> 'something'
# Each of our items are decorated with ArticleDecorator
decorated_articles.first.caption
=> 'Using Tire with Draper - To augment Collection and Item'
# We also still have the methods from Tire::Results::Item at arms length
# since we used delegate_all in our ArticleDecorator.
decorated_articles.first._score
=> '0.30685282'
Alternatively
If we don't need to add any methods to the collection, but we do want to access the facets (or other methods), we can define just one collection decorator to use with Tire. We can reuse this collection decorator for multiple Tire enabled models
.
class ResultsDecorator < Draper::CollectionDecorator
delegate :facets
end
# We can then specify which item decorator we want to use for each item.
articles = Article.search(query: 'Draper')
decorated_articles = ResultsDecorator.new(articles, with: ArticleDecorator)
We can now reuse our view models between Tire and ActiveRecord/Mongoid, as long as they adhere to the same interface (attributes).
Sometimes you embed a child node in your documents, as Tire automatically wraps them with the Item
class, you can also decorate them easily.
For example, if we embed a single Article
in a Blog
document, we can use the same ArticleDecorator that we used when interacting with the article.
class BlogDecorator < Draper::Decorator
decorates_association :article, with: ArticleDecorator
end
blogs = Blog.search(query: 'The blog')
decorated_blogs = BlogsDecorator.new(blogs)
decorated_article = decorated_blogs.first.article
# Our embedded Article ´Item´ is now decorated with our ArticleDecorator
decorated_article.caption
=> 'Using Tire with Draper - To augment Collection and Item'
Now, the with:
option is needed since Draper can not perform an automatic lookup here.
For more information on draper, read the Draper readme