Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 4 commits
  • 4 files changed
  • 5 comments
  • 1 contributor
Dec 16, 2011
Karel Minarik [FIX] Fixed an error where the `/article/:id` used un-available metho…
…ds on article.author
3910110
Karel Minarik 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
Karel Minarik 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
Karel Minarik 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
10  episode-307/blog-after/app/controllers/articles_controller.rb
... ...
@@ -1,4 +1,14 @@
1 1
 class ArticlesController < ApplicationController
  2
+  rescue_from Tire::Search::SearchRequestFailed do |error|
  3
+    # Indicate incorrect query to the user
  4
+    if error.message =~ /SearchParseException/ && params[:query]
  5
+      flash[:error] = "Sorry, your query '#{params[:query]}' is invalid..."
  6
+    else
  7
+      # ... handle other possible situations ...
  8
+    end
  9
+    redirect_to root_url
  10
+  end
  11
+
2 12
   def index
3 13
     @articles = Article.search(params)
4 14
   end
21  episode-307/blog-after/app/models/article.rb
@@ -8,25 +8,29 @@ class Article < ActiveRecord::Base
8 8
   mapping do
9 9
     indexes :id, type: 'integer'
10 10
     indexes :author_id, type: 'integer'
11  
-    indexes :author_name
  11
+    indexes :author, type: 'object',
  12
+                     properties: {
  13
+                       name: { type: 'multi_field',
  14
+                               fields: { name:  { type: 'string', analyzer: 'snowball' },
  15
+                                         exact: { type: 'string', index: 'not_analyzed' } } } }
12 16
     indexes :name, boost: 10
13 17
     indexes :content # analyzer: 'snowball'
14 18
     indexes :published_at, type: 'date'
15 19
     indexes :comments_count, type: 'integer'
16 20
   end
17 21
   
18  
-  def self.search(params)
  22
+  def self.search(params={})
19 23
     tire.search(page: params[:page], per_page: 2) do
20 24
       query do
21 25
         boolean do
22 26
           must { string params[:query], default_operator: "AND" } if params[:query].present?
23 27
           must { range :published_at, lte: Time.zone.now }
24  
-          must { term :author_id, params[:author_id] } if params[:author_id].present?
25 28
         end
26 29
       end
  30
+      filter :term, 'author.name.exact' => params[:author] if params[:author].present?
27 31
       sort { by :published_at, "desc" } if params[:query].blank?
28 32
       facet "authors" do
29  
-        terms :author_id
  33
+        terms 'author.name.exact'
30 34
       end
31 35
       # raise to_curl
32 36
     end
@@ -34,14 +38,7 @@ def self.search(params)
34 38
   
35 39
   # self.include_root_in_json = false (necessary before Rails 3.1)
36 40
   def to_indexed_json
37  
-    to_json(methods: [:author_name, :comments_count])
  41
+    to_json( include: { comments: { only: [:content, :name] }, author: { only: [:name]} } )
38 42
   end
39 43
   
40  
-  def author_name
41  
-    author.name
42  
-  end
43  
-  
44  
-  def comments_count
45  
-    comments.size
46  
-  end
47 44
 end
10  episode-307/blog-after/app/views/articles/index.html.erb
@@ -12,9 +12,9 @@
12 12
   <ul>
13 13
     <% @articles.facets['authors']['terms'].each do |facet| %>
14 14
       <li>
15  
-        <%= link_to_unless_current Author.find(facet['term']).name, params.merge(author_id: facet['term']) %>
16  
-        <% if params[:author_id] == facet['term'].to_s %>
17  
-          (<%= link_to "remove", author_id: nil %>)
  15
+        <%= link_to_unless_current facet['term'], params.merge(author: facet['term']) %>
  16
+        <% if params[:author] == facet['term'].to_s %>
  17
+          (<%= link_to "remove", params.merge(author: nil) %>)
18 18
         <% else %>
19 19
           (<%= facet['count'] %>)
20 20
         <% end %>
@@ -27,10 +27,10 @@
27 27
 <% @articles.each do |article| %>
28 28
   <h2>
29 29
     <%= link_to article.name, article %>
30  
-    <span class="comments">(<%= pluralize(article.comments_count, 'comment') %>)</span>
  30
+    <span class="comments">(<%= pluralize(article.comments.size, 'comment') %>)</span>
31 31
   </h2>
32 32
   <div class="info">
33  
-    by <%= article.author_name %>
  33
+    by <%= article.author.name %>
34 34
     on <%= article.published_at.to_time.strftime('%b %d, %Y') %>
35 35
   </div>
36 36
   <div class="content"><%= article.content %></div>
2  episode-307/blog-after/app/views/articles/show.html.erb
... ...
@@ -1,6 +1,6 @@
1 1
 <h1><%= @article.name %></h1>
2 2
 
3  
-<p class="author"><em>by <%= @article.author.first_name %> <%= @article.author.last_name %></em></p>
  3
+<p class="author"><em>by <%= @article.author.name %></em></p>
4 4
 
5 5
 <%= simple_format @article.content %>
6 6
 

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?

Karel Minarik
Owner

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

Philippe Vaucher

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

Travis

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

Ches Martin

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