Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
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 article.author
3910110
@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.
ee1f6f3
@karmi karmi Changed Article mapping so that we can search & facet against author …
…names

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 `author.name.exact` 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 `author.name.exact` 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 <http://www.elasticsearch.org/guide/reference/api/search/facets/index.html> for more information.
03c45c3
@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 <http://github.com/karmi/tire/issues/164#issuecomment-3096189>.
a2e4574
View
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 = Article.search(params)
end
View
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'
end
- def self.search(params)
+ def self.search(params={})
tire.search(page: 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: Time.zone.now }
- must { term :author_id, params[:author_id] } if params[:author_id].present?
end
end
+ filter :term, 'author.name.exact' => params[:author] if params[:author].present?
sort { by :published_at, "desc" } if params[:query].blank?
facet "authors" do
- terms :author_id
+ terms 'author.name.exact'
end
# raise to_curl
end
@@ -34,14 +38,7 @@ def self.search(params)
# 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]} } )
end
- def author_name
- author.name
- end
-
- def comments_count
- comments.size
- end
end
View
10 episode-307/blog-after/app/views/articles/index.html.erb
@@ -12,9 +12,9 @@
<ul>
<% @articles.facets['authors']['terms'].each do |facet| %>
<li>
- <%= 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| %>
<h2>
<%= link_to article.name, article %>
- <span class="comments">(<%= pluralize(article.comments_count, 'comment') %>)</span>
+ <span class="comments">(<%= pluralize(article.comments.size, 'comment') %>)</span>
</h2>
<div class="info">
- by <%= article.author_name %>
+ by <%= article.author.name %>
on <%= article.published_at.to_time.strftime('%b %d, %Y') %>
</div>
<div class="content"><%= article.content %></div>
View
2  episode-307/blog-after/app/views/articles/show.html.erb
@@ -1,6 +1,6 @@
<h1><%= @article.name %></h1>
-<p class="author"><em>by <%= @article.author.first_name %> <%= @article.author.last_name %></em></p>
+<p class="author"><em>by <%= @article.author.name %></em></p>
<%= simple_format @article.content %>

Showing you all comments on commits in this comparison.

@ipranay

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

@karmi

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

@Silex

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

@9mm

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

@ches

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