Skip to content
This repository has been archived by the owner on Jun 30, 2018. It is now read-only.

Using Tire with Draper

mattiassvedhem edited this page Mar 14, 2013 · 33 revisions

There's many reasons for wanting to augment either the Collection or the Item returned by Tire.

For example:

  1. Add additional view logic that you don't want to put in a helper
  2. Reuse view logic between ActiveRecord/Mongoid & Tire.
  3. 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.

1. If we don't need to access additional methods on the collection

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.name
=> 'The name'

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 method.

2. If we need to access methods on the collection (or define new ones), for example facets.

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

  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::Base
  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).

For more information on draper, read the Draper readme