Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: ryanb/railscasts-episodes
base: master
head fork: karmi/railscasts-episodes
compare: master
Checking mergeability… Don't worry, you can still create the pull request.
  • 4 commits
  • 4 files changed
  • 5 commit comments
  • 1 contributor
Commits on Dec 16, 2011
@karmi karmi [FIX] Fixed an error where the `/article/:id` used un-available metho…
…ds on
@karmi karmi Changed the Article#to_indexed_json to include data from associations
In the published version, special methods `Article#author_name` and `Article#comments_count` were added,
so we can display some data about authorship and comments from associations.

This would be less then ideal in a bigger codebase, and, moreover, Elasticsearch & Tire make it
trivial to support this use case.

The only thing we need to do is to `include` the selected information from the associated models
in the `Article#to_indexed_json` declaration.

In this way, the information about:

* Name of the article author
* Name of commenters and bodies of their comments

is included in the index. This has two benefits:

1. It's **searchable**! :) We can search within the comments in the same way as we do within articles.
2. We don't have to add special methods, and can revert the HTML layer to the original state.
@karmi karmi Changed Article mapping so that we can search & facet against author …

Based on previous refactoring, we're now addding a specific mapping for the `author` property,
so that it's analyzed as "multi field" property.

This means it's:

1. Split into tokens the usual way search engine works, with the snowball analyzer.
2. Left "as is", without any modification, as an "exact" sub-property.

This illustrates one of the usual use-cases for multi field properties: you want to search
against them as usual, but you also want to use their literal, unmodified value for aggregations.

This allows us to get rid of database calls (`Author.find(facet['term'])`) in the view layer,
and use information already in the index for displaying the faceted navigation sidebar.

Notice we facet against the `` field, and that we have changed the mechanics
of limiting results based on user's choice in right-hand sidebar: instead of query, we are
using filter, again on the `` field.

This means, the **query** is used to limit the results, but **faceted navigation** still displays
the distribution of results among authors, when you limit the results to a specific author,
because a **filter** is used; facets are bound only by queries, not filters.

See <> for more information.
@karmi karmi Added an example of how to intercept exceptions due to incorrect user…
… queries

When the users enters an incorrect query, _Tire_ raises a `Tire::Search::SearchRequestFailed` exception.

It is unfortunate to propagate the raw exception to the page, and display the “Something went wrong” page
in this case -- it's better to display the default result set, or the search form, and indicate to the
user her query was incorrect.

This commit contains a trivial implementation via the _Rails'_ `rescue_from` method.

See <>.
10 episode-307/blog-after/app/controllers/articles_controller.rb
@@ -1,4 +1,14 @@
class ArticlesController < ApplicationController
+ rescue_from Tire::Search::SearchRequestFailed do |error|
+ # Indicate incorrect query to the user
+ if error.message =~ /SearchParseException/ && params[:query]
+ flash[:error] = "Sorry, your query '#{params[:query]}' is invalid..."
+ else
+ # ... handle other possible situations ...
+ end
+ redirect_to root_url
+ end
def index
@articles =
21 episode-307/blog-after/app/models/article.rb
@@ -8,25 +8,29 @@ class Article < ActiveRecord::Base
mapping do
indexes :id, type: 'integer'
indexes :author_id, type: 'integer'
- indexes :author_name
+ indexes :author, type: 'object',
+ properties: {
+ name: { type: 'multi_field',
+ fields: { name: { type: 'string', analyzer: 'snowball' },
+ exact: { type: 'string', index: 'not_analyzed' } } } }
indexes :name, boost: 10
indexes :content # analyzer: 'snowball'
indexes :published_at, type: 'date'
indexes :comments_count, type: 'integer'
- def
+ def{}) params[:page], per_page: 2) do
query do
boolean do
must { string params[:query], default_operator: "AND" } if params[:query].present?
must { range :published_at, lte: }
- must { term :author_id, params[:author_id] } if params[:author_id].present?
+ filter :term, '' => params[:author] if params[:author].present?
sort { by :published_at, "desc" } if params[:query].blank?
facet "authors" do
- terms :author_id
+ terms ''
# raise to_curl
@@ -34,14 +38,7 @@ def
# self.include_root_in_json = false (necessary before Rails 3.1)
def to_indexed_json
- to_json(methods: [:author_name, :comments_count])
+ to_json( include: { comments: { only: [:content, :name] }, author: { only: [:name]} } )
- def author_name
- end
- def comments_count
- comments.size
- end
10 episode-307/blog-after/app/views/articles/index.html.erb
@@ -12,9 +12,9 @@
<% @articles.facets['authors']['terms'].each do |facet| %>
- <%= link_to_unless_current Author.find(facet['term']).name, params.merge(author_id: facet['term']) %>
- <% if params[:author_id] == facet['term'].to_s %>
- (<%= link_to "remove", author_id: nil %>)
+ <%= link_to_unless_current facet['term'], params.merge(author: facet['term']) %>
+ <% if params[:author] == facet['term'].to_s %>
+ (<%= link_to "remove", params.merge(author: nil) %>)
<% else %>
(<%= facet['count'] %>)
<% end %>
@@ -27,10 +27,10 @@
<% @articles.each do |article| %>
<%= link_to, article %>
- <span class="comments">(<%= pluralize(article.comments_count, 'comment') %>)</span>
+ <span class="comments">(<%= pluralize(article.comments.size, 'comment') %>)</span>
<div class="info">
- by <%= article.author_name %>
+ by <%= %>
on <%= article.published_at.to_time.strftime('%b %d, %Y') %>
<div class="content"><%= article.content %></div>
2  episode-307/blog-after/app/views/articles/show.html.erb
@@ -1,6 +1,6 @@
<h1><%= %></h1>
-<p class="author"><em>by <%= %> <%= %></em></p>
+<p class="author"><em>by <%= %></em></p>
<%= simple_format @article.content %>

Showing you all comments on commits in this comparison.


would there be any changes to the "mapping" area?... can we still use author_name, comments_count there?


Hi, no, this must be changed: see subsequent commit 03c45c3.


Aren't you supposed to remove/refactor :comments_count too?


What does this part do? exact: { type: 'string', index: 'not_analyzed' }


@ecefx He explains in the commit message -- multi_field is used to index the author two ways:

  1. With snowball, you could search articles for "smith" and find those written by Bob Smith.
  2. With no analysis, so "Bob Smith" is indexed as-is, thus you can aggregate (such as faceting) by the exact name. The keys name and exact are arbitrary, you can choose them to refer to each defined multi-field meaningfully.
Something went wrong with that request. Please try again.