slingshot-rb 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +1 -0
  2. data/README.markdown +276 -50
  3. data/examples/rails-application-template.rb +144 -0
  4. data/examples/slingshot-dsl.rb +272 -102
  5. data/lib/slingshot.rb +13 -0
  6. data/lib/slingshot/client.rb +10 -1
  7. data/lib/slingshot/dsl.rb +17 -1
  8. data/lib/slingshot/index.rb +109 -7
  9. data/lib/slingshot/model/callbacks.rb +23 -0
  10. data/lib/slingshot/model/import.rb +18 -0
  11. data/lib/slingshot/model/indexing.rb +50 -0
  12. data/lib/slingshot/model/naming.rb +30 -0
  13. data/lib/slingshot/model/persistence.rb +34 -0
  14. data/lib/slingshot/model/persistence/attributes.rb +60 -0
  15. data/lib/slingshot/model/persistence/finders.rb +61 -0
  16. data/lib/slingshot/model/persistence/storage.rb +75 -0
  17. data/lib/slingshot/model/search.rb +97 -0
  18. data/lib/slingshot/results/collection.rb +35 -10
  19. data/lib/slingshot/results/item.rb +10 -7
  20. data/lib/slingshot/results/pagination.rb +30 -0
  21. data/lib/slingshot/rubyext/symbol.rb +11 -0
  22. data/lib/slingshot/search.rb +3 -2
  23. data/lib/slingshot/search/facet.rb +8 -6
  24. data/lib/slingshot/search/filter.rb +7 -8
  25. data/lib/slingshot/search/highlight.rb +1 -3
  26. data/lib/slingshot/search/query.rb +4 -0
  27. data/lib/slingshot/search/sort.rb +5 -0
  28. data/lib/slingshot/tasks.rb +88 -0
  29. data/lib/slingshot/version.rb +1 -1
  30. data/slingshot.gemspec +17 -4
  31. data/test/integration/active_model_searchable_test.rb +80 -0
  32. data/test/integration/active_record_searchable_test.rb +193 -0
  33. data/test/integration/highlight_test.rb +1 -1
  34. data/test/integration/index_mapping_test.rb +1 -1
  35. data/test/integration/index_store_test.rb +27 -0
  36. data/test/integration/persistent_model_test.rb +35 -0
  37. data/test/integration/query_string_test.rb +3 -3
  38. data/test/integration/sort_test.rb +2 -2
  39. data/test/models/active_model_article.rb +31 -0
  40. data/test/models/active_model_article_with_callbacks.rb +49 -0
  41. data/test/models/active_model_article_with_custom_index_name.rb +5 -0
  42. data/test/models/active_record_article.rb +12 -0
  43. data/test/models/persistent_article.rb +11 -0
  44. data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
  45. data/test/models/supermodel_article.rb +22 -0
  46. data/test/models/validated_model.rb +11 -0
  47. data/test/test_helper.rb +4 -0
  48. data/test/unit/active_model_lint_test.rb +17 -0
  49. data/test/unit/client_test.rb +4 -0
  50. data/test/unit/configuration_test.rb +4 -0
  51. data/test/unit/index_test.rb +240 -17
  52. data/test/unit/model_callbacks_test.rb +90 -0
  53. data/test/unit/model_import_test.rb +71 -0
  54. data/test/unit/model_persistence_test.rb +400 -0
  55. data/test/unit/model_search_test.rb +289 -0
  56. data/test/unit/results_collection_test.rb +69 -7
  57. data/test/unit/results_item_test.rb +8 -14
  58. data/test/unit/rubyext_hash_test.rb +19 -0
  59. data/test/unit/search_facet_test.rb +25 -7
  60. data/test/unit/search_filter_test.rb +3 -0
  61. data/test/unit/search_query_test.rb +11 -0
  62. data/test/unit/search_sort_test.rb +8 -0
  63. data/test/unit/search_test.rb +14 -0
  64. data/test/unit/slingshot_test.rb +38 -0
  65. metadata +133 -26
@@ -1,23 +1,27 @@
1
- # **Slingshot** is a rich and comfortable Ruby client for the
1
+ # **Slingshot** provides rich and comfortable Ruby API for the
2
2
  # [_ElasticSearch_](http://www.elasticsearch.org/) search engine/database.
3
3
  #
4
- # It provides the API for the main _ElasticSearch_ features, and this document
5
- # will walk you through them.
4
+ # _ElasticSearch_ is a scalable, distributed, cloud-ready, highly-available
5
+ # full-text search engine and database, communicating by JSON over RESTful HTTP,
6
+ # based on [Lucene](http://lucene.apache.org/), written in Java.
6
7
  #
7
8
  # <img src="http://github.com/favicon.ico" style="position:relative; top:2px">
8
9
  # _Slingshot_ is open source, and you can download or clone the source code
9
10
  # from <https://github.com/karmi/slingshot>.
10
11
  #
11
- # _ElasticSearch_ is a scalable, distributed, highly-available,
12
- # RESTful database communicating by JSON over HTTP, based on [Lucene](http://lucene.apache.org/),
13
- # written in Java. It manages to be very simple and very powerful at the same time.
14
- #
15
12
  # By following these instructions you should have the search running
16
13
  # on a sane operation system in less then 10 minutes.
17
14
 
15
+ # Note, that this file can be executed directly:
16
+ #
17
+ # ruby examples/slingshot-dsl.rb
18
+ #
19
+
20
+
18
21
  #### Installation
19
22
 
20
- # Install Slingshot with Rubygems.
23
+ # Install _Slingshot_ with Rubygems.
24
+
21
25
  #
22
26
  # gem install slingshot-rb
23
27
  #
@@ -27,31 +31,37 @@ require 'slingshot'
27
31
  #### Prerequisites
28
32
 
29
33
  # You'll need a working and running _ElasticSearch_ server. Thankfully, that's easy.
30
- ( puts <<-"INSTALL" ; exit(1) ) unless RestClient.get('http://localhost:9200') rescue false
31
- [!] You don’t appear to have ElasticSearch installed. Please install and launch it with the following commands.
32
- curl -k -L -o elasticsearch-0.15.2.tar.gz http://github.com/downloads/elasticsearch/elasticsearch/elasticsearch-0.15.2.tar.gz
33
- tar -zxvf elasticsearch-0.15.2.tar.gz
34
- ./elasticsearch-0.15.2/bin/elasticsearch -f
35
- INSTALL
34
+ ( puts <<-"INSTALL" ; exit(1) ) unless (RestClient.get('http://localhost:9200') rescue false)
35
+
36
+ [ERROR] You don’t appear to have ElasticSearch installed. Please install and launch it with the following commands:
36
37
 
37
- ### Simple Usage
38
+ curl -k -L -o elasticsearch-0.16.0.tar.gz http://github.com/downloads/elasticsearch/elasticsearch/elasticsearch-0.16.0.tar.gz
39
+ tar -zxvf elasticsearch-0.16.0.tar.gz
40
+ ./elasticsearch-0.16.0/bin/elasticsearch -f
41
+ INSTALL
38
42
 
39
- #### Storing and indexing documents
43
+ ### Storing and indexing documents
40
44
 
41
45
  # Let's initialize an index named “articles”.
46
+ #
42
47
  Slingshot.index 'articles' do
43
48
  # To make sure it's fresh, let's delete any existing index with the same name.
49
+ #
44
50
  delete
45
51
  # And then, let's create it.
52
+ #
46
53
  create
47
54
 
48
- # We want to store and index some articles with title and tags. Simple Hashes are OK.
55
+ # We want to store and index some articles with `title`, `tags` and `published_on` properties.
56
+ # Simple Hashes are OK.
57
+ #
49
58
  store :title => 'One', :tags => ['ruby'], :published_on => '2011-01-01'
50
59
  store :title => 'Two', :tags => ['ruby', 'python'], :published_on => '2011-01-02'
51
60
  store :title => 'Three', :tags => ['java'], :published_on => '2011-01-02'
52
61
  store :title => 'Four', :tags => ['ruby', 'php'], :published_on => '2011-01-03'
53
62
 
54
63
  # We force refreshing the index, so we can query it immediately.
64
+ #
55
65
  refresh
56
66
  end
57
67
 
@@ -60,34 +70,87 @@ end
60
70
 
61
71
  Slingshot.index 'articles' do
62
72
  # To do so, just pass a Hash containing the specified mapping to the `Index#create` method.
73
+ #
63
74
  create :mappings => {
64
- # Specify for which type of documents this mapping should be used (`article` in this case).
75
+
76
+ # Specify for which type of documents this mapping should be used.
77
+ # (The documents must provide a `type` method or property then.)
78
+ #
65
79
  :article => {
66
80
  :properties => {
81
+
67
82
  # Specify the type of the field, whether it should be analyzed, etc.
83
+ #
68
84
  :id => { :type => 'string', :index => 'not_analyzed', :include_in_all => false },
69
- # Set the boost or analyzer settings for the field, et cetera. The _ElasticSearch_ guide
70
- # has [more information](http://elasticsearch.org/guide/reference/mapping/index.html).
71
- :title => { :type => 'string', :boost => 2.0, :analyzer => 'snowball' },
85
+
86
+ # Set the boost or analyzer settings for the field, ... The _ElasticSearch_ guide
87
+ # has [more information](http://elasticsearch.org/guide/reference/mapping/index.html)
88
+ # about this. Proper mapping is key to efficient and effective search.
89
+ # But don't fret about getting the mapping right the first time, you won't.
90
+ # In most cases, the default mapping is just fine for prototyping.
91
+ #
92
+ :title => { :type => 'string', :analyzer => 'snowball', :boost => 2.0 },
72
93
  :tags => { :type => 'string', :analyzer => 'keyword' },
73
- :content => { :type => 'string', :analyzer => 'snowball' }
94
+ :content => { :type => 'string', :analyzer => 'czech' }
74
95
  }
75
96
  }
76
97
  }
77
98
  end
78
99
 
100
+ #### Bulk Storage
101
+
102
+ # Of course, we may have large amounts of data, and adding them to the index one by one really isn't the best idea.
103
+ # We can use _ElasticSearch's_ [bulk storage](http://www.elasticsearch.org/guide/reference/api/bulk.html)
104
+ # for importing the data.
105
+
106
+ # So, for demonstration purposes, let's suppose we have a plain collection of hashes to store.
107
+ #
108
+ articles = [
109
+
110
+ # Notice that such objects must have an `id` property!
111
+ #
112
+ { :id => '1', :title => 'one', :tags => ['ruby'], :published_on => '2011-01-01' },
113
+ { :id => '2', :title => 'two', :tags => ['ruby', 'python'], :published_on => '2011-01-02' },
114
+ { :id => '3', :title => 'three', :tags => ['java'], :published_on => '2011-01-02' },
115
+ { :id => '4', :title => 'four', :tags => ['ruby', 'php'], :published_on => '2011-01-03' }
116
+ ]
117
+
118
+ # We can just push them into the index in one go.
119
+ #
120
+ Slingshot.index 'articles' do
121
+ import articles
122
+ end
123
+
124
+ # Of course, we can easily manipulate the documents before storing them in the index.
125
+ #
126
+ Slingshot.index 'articles' do
127
+ delete
128
+
129
+ # ... by just passing a block to the `import` method. The collection will
130
+ # be available in the block argument.
131
+ #
132
+ import articles do |documents|
133
+
134
+ # We will capitalize every _title_ and return the manipulated collection
135
+ # back to the `import` method.
136
+ #
137
+ documents.map { |document| document.update(:title => document[:title].capitalize) }
138
+ end
79
139
 
140
+ refresh
141
+ end
80
142
 
81
- #### Searching
143
+ ### Searching
82
144
 
83
- # With the documents indexed and stored in the _ElasticSearch_ database, we want to search for them.
145
+ # With the documents indexed and stored in the _ElasticSearch_ database, we can search them, finally.
84
146
  #
85
147
  # Slingshot exposes the search interface via simple domain-specific language.
86
148
 
87
149
 
88
- ##### Simple Query String Searches
150
+ #### Simple Query String Searches
89
151
 
90
152
  # We can do simple searches, like searching for articles containing “One” in their title.
153
+ #
91
154
  s = Slingshot.search('articles') do
92
155
  query do
93
156
  string "title:One"
@@ -96,23 +159,45 @@ end
96
159
 
97
160
  # The results:
98
161
  # * One [tags: ruby]
162
+ #
99
163
  s.results.each do |document|
100
164
  puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
101
165
  end
102
166
 
167
+ # Or, we can search for articles published between January, 1st and January, 2nd.
168
+ #
169
+ s = Slingshot.search('articles') do
170
+ query do
171
+ string "published_on:[2011-01-01 TO 2011-01-02]"
172
+ end
173
+ end
174
+
175
+ # The results:
176
+ # * One [published: 2011-01-01]
177
+ # * Two [published: 2011-01-02]
178
+ # * Three [published: 2011-01-02]
179
+ #
180
+ s.results.each do |document|
181
+ puts "* #{ document.title } [published: #{document.published_on}]"
182
+ end
183
+
103
184
  # Of course, we may write the blocks in shorter notation.
185
+ # Local variables from outer scope are passed down the chain.
104
186
 
105
187
  # Let's search for articles whose titles begin with letter “T”.
106
- s = Slingshot.search('articles') { query { string "title:T*" } }
188
+ #
189
+ q = "title:T*"
190
+ s = Slingshot.search('articles') { query { string q } }
107
191
 
108
192
  # The results:
109
193
  # * Two [tags: ruby, python]
110
194
  # * Three [tags: java]
195
+ #
111
196
  s.results.each do |document|
112
197
  puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
113
198
  end
114
199
 
115
- # We can use any valid [Lucene query syntax](http://lucene.apache.org/java/3_0_3/queryparsersyntax.html)
200
+ # In fact, we can use any valid [Lucene query syntax](http://lucene.apache.org/java/3_0_3/queryparsersyntax.html)
116
201
  # for the query string queries.
117
202
 
118
203
  # For debugging, we can display the JSON which is being sent to _ElasticSearch_.
@@ -122,8 +207,8 @@ end
122
207
  puts "", "Query:", "-"*80
123
208
  puts s.to_json
124
209
 
125
- # Or better, we may display a complete `curl` command, so we can execute it in terminal
126
- # to see the raw output, tweak params and debug any problems.
210
+ # Or better, we may display a complete `curl` command to recreate the request in terminal,
211
+ # so we can see the naked response, tweak request parameters and meditate on problems.
127
212
  #
128
213
  # curl -X POST "http://localhost:9200/articles/_search?pretty=true" \
129
214
  # -d '{"query":{"query_string":{"query":"title:T*"}}}'
@@ -131,8 +216,11 @@ puts s.to_json
131
216
  puts "", "Try the query in Curl:", "-"*80
132
217
  puts s.to_curl
133
218
 
134
- # For debugging more complex situations, you can enable logging, so requests and responses
135
- # will be logged using the `curl`-based format.
219
+
220
+ ### Logging
221
+
222
+ # For debugging more complex situations, we can enable logging, so requests and responses
223
+ # will be logged using this `curl`-friendly format.
136
224
 
137
225
  Slingshot.configure do
138
226
 
@@ -141,37 +229,62 @@ Slingshot.configure do
141
229
  #
142
230
  # # 2011-04-24 11:34:01:150 [CREATE] ("articles")
143
231
  # #
144
- # curl -X POST "http://localhost:9200/articles" -d '{
145
- #
146
- # }'
232
+ # curl -X POST "http://localhost:9200/articles"
147
233
  #
148
234
  # # 2011-04-24 11:34:01:152 [200]
149
235
  #
150
236
  logger 'elasticsearch.log'
151
237
 
152
- # For debugging, you can switch to the _debug_ level, which will log the complete JSON responses.
238
+ # For debugging, we can switch to the _debug_ level, which will log the complete JSON responses.
153
239
  #
154
- # That's very convenient if you want to post a recreation of some problem or solution to the mailing list, IRC, etc.
240
+ # That's very convenient if we want to post a recreation of some problem or solution
241
+ # to the mailing list, IRC channel, etc.
155
242
  #
156
243
  logger 'elasticsearch.log', :level => 'debug'
157
244
 
158
- # Note that you can pass any [`IO`](http://www.ruby-doc.org/core/classes/IO.html)-compatible Ruby object as a logging device.
245
+ # Note that we can pass any [`IO`](http://www.ruby-doc.org/core/classes/IO.html)-compatible Ruby object as a logging device.
159
246
  #
160
247
  logger STDERR
161
248
  end
162
249
 
163
- ##### Other Types of Queries
250
+ ### Configuration
164
251
 
165
- # Well, query strings are convenient for simple searches, but
166
- # we may want to define our queries more expressively.
252
+ # As we have just seen with logging, we can configure various parts of _Slingshot_.
167
253
  #
168
- # For instance when we're searching for articles with specific _tags_.
254
+ Slingshot.configure do
255
+
256
+ # First of all, we can configure the URL for _ElasticSearch_.
257
+ #
258
+ url "http://search.example.com"
259
+
260
+ # Second, we may want to wrap the result items in our own class.
261
+ #
262
+ class MySpecialWrapper; end
263
+ wrapper MySpecialWrapper
264
+
265
+ # Finally, we can reset one or all configuration settings to their defaults.
266
+ #
267
+ reset
169
268
 
170
- # Let's suppose we want to search for articles tagged “ruby” _or_ “python”.
171
- # That's a great excuse to use a [_terms_](http://elasticsearch.org/guide/reference/query-dsl/terms-query.html)
172
- # query.
269
+ end
270
+
271
+
272
+ ### Complex Searching
273
+
274
+ #### Other Types of Queries
275
+
276
+ # Query strings are convenient for simple searches, but we may want to define our queries more expressively,
277
+ # using the _ElasticSearch_ [Query DSL](http://www.elasticsearch.org/guide/reference/query-dsl/index.html).
278
+ #
173
279
  s = Slingshot.search('articles') do
280
+
281
+ # Let's suppose we want to search for articles with specific _tags_, in our case “ruby” _or_ “python”.
282
+ #
174
283
  query do
284
+
285
+ # That's a great excuse to use a [_terms_](http://elasticsearch.org/guide/reference/query-dsl/terms-query.html)
286
+ # query.
287
+ #
175
288
  terms :tags, ['ruby', 'python']
176
289
  end
177
290
  end
@@ -181,14 +294,18 @@ end
181
294
  # * Two [tags: ruby, python]
182
295
  # * One [tags: ruby]
183
296
  # * Four [tags: ruby, php]
297
+ #
184
298
  s.results.each do |document|
185
299
  puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
186
300
  end
187
301
 
188
- # What if we wanted to search for articles tagged both “ruby” _and_ “python”.
189
- # That's a great excuse to specify `minimum_match` for the query.
302
+ # What if we wanted to search for articles tagged both “ruby” _and_ “python”?
303
+ #
190
304
  s = Slingshot.search('articles') do
191
305
  query do
306
+
307
+ # That's a great excuse to specify `minimum_match` for the query.
308
+ #
192
309
  terms :tags, ['ruby', 'python'], :minimum_match => 2
193
310
  end
194
311
  end
@@ -196,21 +313,22 @@ end
196
313
  # The search, as expected, returns one article, tagged with _both_ “ruby” and “python”:
197
314
  #
198
315
  # * Two [tags: ruby, python]
316
+ #
199
317
  s.results.each do |document|
200
318
  puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
201
319
  end
202
320
 
203
321
  # _ElasticSearch_ supports many types of [queries](http://www.elasticsearch.org/guide/reference/query-dsl/).
204
322
  #
205
- # Eventually, _Slingshot_ will support all of them.
206
- # So far, only these are supported:
323
+ # Eventually, _Slingshot_ will support all of them. So far, only these are supported:
207
324
  #
208
325
  # * [string](http://www.elasticsearch.org/guide/reference/query-dsl/query-string-query.html)
209
326
  # * [term](http://elasticsearch.org/guide/reference/query-dsl/term-query.html)
210
327
  # * [terms](http://elasticsearch.org/guide/reference/query-dsl/terms-query.html)
211
328
  # * [all](http://www.elasticsearch.org/guide/reference/query-dsl/match-all-query.html)
329
+ # * [ids](http://www.elasticsearch.org/guide/reference/query-dsl/ids-query.html)
212
330
 
213
- ##### Faceted Search
331
+ #### Faceted Search
214
332
 
215
333
  # _ElasticSearch_ makes it trivial to retrieve complex aggregated data from our index/database,
216
334
  # so called [_facets_](http://www.elasticsearch.org/guide/reference/api/search/facets/index.html).
@@ -220,147 +338,191 @@ end
220
338
 
221
339
  #
222
340
  s = Slingshot.search 'articles' do
341
+
223
342
  # We will search for articles whose title begins with letter “T”,
343
+ #
224
344
  query { string 'title:T*' }
225
345
 
226
- # and retrieve their counts, “bucketed” by their `tags`.
346
+ # and retrieve the counts “bucketed” by `tags`.
347
+ #
227
348
  facet 'tags' do
228
349
  terms :tags
229
350
  end
230
351
  end
231
352
 
232
353
  # As we see, our query has found two articles, and if you recall our articles from above,
233
- # _Two_ is tagged with “ruby” and “python”, _Three_ is tagged with “java”. So the counts
234
- # won't surprise us:
354
+ # _Two_ is tagged with “ruby” and “python”, while _Three_ is tagged with “java”.
355
+ #
235
356
  # Found 2 articles: Three, Two
236
- # Counts:
237
- # -------
357
+ #
358
+ # The counts shouldn't surprise us:
359
+ #
360
+ # Counts by tag:
361
+ # -------------------------
238
362
  # ruby 1
239
363
  # python 1
240
364
  # java 1
365
+ #
241
366
  puts "Found #{s.results.count} articles: #{s.results.map(&:title).join(', ')}"
242
- puts "Counts based on tags:", "-"*25
367
+ puts "Counts by tag:", "-"*25
243
368
  s.results.facets['tags']['terms'].each do |f|
244
369
  puts "#{f['term'].ljust(10)} #{f['count']}"
245
370
  end
246
371
 
247
- # These counts are based on the scope of our current query (called `main` in _ElasticSearch_).
372
+ # These counts are based on the scope of our current query.
248
373
  # What if we wanted to display aggregated counts by `tags` across the whole database?
249
374
 
250
375
  #
251
376
  s = Slingshot.search 'articles' do
377
+
378
+ # Let's repeat the search for “T”...
379
+ #
252
380
  query { string 'title:T*' }
253
381
 
254
382
  facet 'global-tags' do
255
- # That's where the `global` scope for a facet comes in.
383
+
384
+ # ...but set the `global` scope for the facet in this case.
385
+ #
256
386
  terms :tags, :global => true
257
387
  end
258
388
 
259
- # As you can see, we can even combine facets scoped to the current query
260
- # with global facets — we'll just use a different name.
389
+ # We can even _combine_ facets scoped to the current query
390
+ # with globally scoped facets — we'll just use a different name.
391
+ #
261
392
  facet 'current-tags' do
262
393
  terms :tags
263
394
  end
264
395
  end
265
396
 
266
397
  # Aggregated results for the current query are the same as previously:
398
+ #
267
399
  # Current query facets:
268
400
  # -------------------------
269
401
  # ruby 1
270
402
  # python 1
271
403
  # java 1
404
+ #
272
405
  puts "Current query facets:", "-"*25
273
406
  s.results.facets['current-tags']['terms'].each do |f|
274
407
  puts "#{f['term'].ljust(10)} #{f['count']}"
275
408
  end
276
409
 
277
- # As we see, aggregated results for the global scope include also
410
+ # On the other hand, aggregated results for the global scope include also
278
411
  # tags for articles not matched by the query, such as “java” or “php”:
412
+ #
279
413
  # Global facets:
280
414
  # -------------------------
281
415
  # ruby 3
282
416
  # python 1
283
417
  # php 1
284
418
  # java 1
419
+ #
285
420
  puts "Global facets:", "-"*25
286
421
  s.results.facets['global-tags']['terms'].each do |f|
287
422
  puts "#{f['term'].ljust(10)} #{f['count']}"
288
423
  end
289
424
 
290
- # _ElasticSearch_ supports many types of advanced facets.
425
+ # _ElasticSearch_ supports many advanced types of facets, such as those for computing statistics or geographical distance.
291
426
  #
292
- # Eventually, _Slingshot_ will support all of them.
293
- # So far, only these are supported:
427
+ # Eventually, _Slingshot_ will support all of them. So far, only these are supported:
294
428
  #
295
429
  # * [terms](http://www.elasticsearch.org/guide/reference/api/search/facets/terms-facet.html)
296
430
  # * [date](http://www.elasticsearch.org/guide/reference/api/search/facets/date-histogram-facet.html)
297
431
 
298
- # The real power of facets lies in their combination with
299
- # [filters](http://elasticsearch.org/guide/reference/api/search/filter.html).
300
-
301
- # Filters enable us to restrict the returned documents, for example to a specific category,
302
- # but to calculate the facet counts based on the original query.
303
- # The immediate use case for that is the „faceted navigation“.
432
+ # We have seen that _ElasticSearch_ facets enable us to fetch complex aggregations from our data.
433
+ #
434
+ # They are frequently used for another feature, „faceted navigation“.
435
+ # We can be combine query and facets with
436
+ # [filters](http://elasticsearch.org/guide/reference/api/search/filter.html),
437
+ # so the returned documents are restricted by certain criteria — for example to a specific category —,
438
+ # but the aggregation calculations are still based on the original query.
304
439
 
305
440
 
306
- ##### Filtered Search
441
+ #### Filtered Search
307
442
 
308
443
  # So, let's make our search a bit more complex. Let's search for articles whose titles begin
309
444
  # with letter “T”, again, but filter the results, so only the articles tagged “ruby”
310
445
  # are returned.
446
+ #
311
447
  s = Slingshot.search 'articles' do
312
448
 
313
- # We use the same **query** as before.
449
+ # We will use just the same **query** as before.
450
+ #
314
451
  query { string 'title:T*' }
315
452
 
316
- # And add a _terms_ **filter** based on tags.
453
+ # But we will add a _terms_ **filter** based on tags.
454
+ #
317
455
  filter :terms, :tags => ['ruby']
318
456
 
319
457
  # And, of course, our facet definition.
458
+ #
320
459
  facet('tags') { terms :tags }
321
460
 
322
461
  end
323
462
 
324
- # We see that only the article _Two_ (tagged “ruby” and “python”) was returned,
463
+ # We see that only the article _Two_ (tagged “ruby” and “python”) is returned,
325
464
  # _not_ the article _Three_ (tagged “java”):
326
465
  #
327
466
  # * Two [tags: ruby, python]
467
+ #
328
468
  s.results.each do |document|
329
469
  puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
330
470
  end
331
471
 
332
- # However, count for article _Three_'s tags, “java”, _is_ in fact included in facets:
472
+ # The _count_ for article _Three_'s tags, “java”, on the other hand, _is_ in fact included:
333
473
  #
334
- # Counts based on tags:
474
+ # Counts by tag:
335
475
  # -------------------------
336
476
  # ruby 1
337
477
  # python 1
338
478
  # java 1
339
- puts "Counts based on tags:", "-"*25
479
+ #
480
+ puts "Counts by tag:", "-"*25
340
481
  s.results.facets['tags']['terms'].each do |f|
341
482
  puts "#{f['term'].ljust(10)} #{f['count']}"
342
483
  end
343
484
 
485
+ #### Sorting
344
486
 
345
- ##### Sorting
487
+ # By default, the results are sorted according to their relevancy.
488
+ #
489
+ s = Slingshot.search('articles') { query { string 'tags:ruby' } }
346
490
 
347
- # By default, the results are sorted according to their relevancy
348
- # (available as the `_score` property).
491
+ s.results.each do |document|
492
+ puts "* #{ document.title } " +
493
+ "[tags: #{document.tags.join(', ')}; " +
494
+
495
+ # The score is available as the `_score` property.
496
+ #
497
+ "score: #{document._score}]"
498
+ end
499
+
500
+ # The results:
501
+ #
502
+ # * One [tags: ruby; score: 0.30685282]
503
+ # * Four [tags: ruby, php; score: 0.19178301]
504
+ # * Two [tags: ruby, python; score: 0.19178301]
349
505
 
350
506
  # But, what if we want to sort the results based on some other criteria,
351
507
  # such as published date or product price? We can do that.
508
+ #
352
509
  s = Slingshot.search 'articles' do
353
- # We search for articles tagged “ruby”.
510
+
511
+ # We will search for articles tagged “ruby”, again, ...
512
+ #
354
513
  query { string 'tags:ruby' }
355
514
 
356
- # And sort them by their `title`, in descending order.
515
+ # ... but will sort them by their `title`, in descending order.
516
+ #
357
517
  sort { title 'desc' }
358
518
  end
359
519
 
360
520
  # The results:
521
+ #
361
522
  # * Two
362
523
  # * One
363
524
  # * Four
525
+ #
364
526
  s.results.each do |document|
365
527
  puts "* #{ document.title }"
366
528
  end
@@ -368,13 +530,19 @@ end
368
530
  # Of course, it's possible to combine more fields in the sorting definition.
369
531
 
370
532
  s = Slingshot.search 'articles' do
371
- # We will just get all articles for this case.
533
+
534
+ # We will just get all articles in this case.
535
+ #
372
536
  query { all }
373
537
 
374
538
  sort do
375
- # We will sort the results by their `published_on` property in ascending (default) order,
539
+
540
+ # We will sort the results by their `published_on` property in _ascending_ order (the default),
541
+ #
376
542
  published_on
377
- # and by their `title` property, in descending order.
543
+
544
+ # and by their `title` property, in _descending_ order.
545
+ #
378
546
  title 'desc'
379
547
  end
380
548
  end
@@ -384,32 +552,33 @@ end
384
552
  # * Two (Published on: 2011-01-02)
385
553
  # * Three (Published on: 2011-01-02)
386
554
  # * Four (Published on: 2011-01-03)
555
+ #
387
556
  s.results.each do |document|
388
557
  puts "* #{ document.title.ljust(10) } (Published on: #{ document.published_on })"
389
558
  end
390
559
 
391
- ##### Highlighting
560
+ #### Highlighting
392
561
 
393
- # Often, you want to highlight the snippets matching your query in the
394
- # displayed results.
562
+ # Often, we want to highlight the snippets matching our query in the displayed results.
395
563
  # _ElasticSearch_ provides rich
396
564
  # [highlighting](http://www.elasticsearch.org/guide/reference/api/search/highlighting.html)
397
- # features, and Slingshot makes them trivial to use.
398
- #
399
- # Let's suppose that we want to highlight terms of our query.
565
+ # features, and _Slingshot_ makes them trivial to use.
400
566
  #
401
567
  s = Slingshot.search 'articles' do
568
+
402
569
  # Let's search for documents containing word “Two” in their titles,
403
570
  query { string 'title:Two' }
404
571
 
405
572
  # and instruct _ElasticSearch_ to highlight relevant snippets.
573
+ #
406
574
  highlight :title
407
575
  end
408
576
 
409
577
  # The results:
410
- # Title: Two, highlighted title: <em>Two</em>
578
+ # Title: Two; Highlighted: <em>Two</em>
579
+ #
411
580
  s.results.each do |document|
412
- puts "Title: #{ document.title }, highlighted title: #{document.highlight.title}"
581
+ puts "Title: #{ document.title }; Highlighted: #{document.highlight.title}"
413
582
  end
414
583
 
415
584
  # We can configure many options for highlighting, such as:
@@ -417,31 +586,32 @@ end
417
586
  s = Slingshot.search 'articles' do
418
587
  query { string 'title:Two' }
419
588
 
420
- # • specifying the fields to highlight
589
+ # • specify the fields to highlight
590
+ #
421
591
  highlight :title, :body
422
592
 
423
- # • specifying their options
593
+ # • specify their individual options
594
+ #
424
595
  highlight :title, :body => { :number_of_fragments => 0 }
425
596
 
426
- # • or specifying global highlighting options, such as the wrapper tag
597
+ # • or specify global highlighting options, such as the wrapper tag
598
+ #
427
599
  highlight :title, :body, :options => { :tag => '<strong class="highlight">' }
428
600
  end
429
601
 
430
602
 
431
-
432
- #### What's next?
603
+ ### ActiveModel Integration
433
604
 
434
605
  # As you can see, [_Slingshot_](https://github.com/karmi/slingshot) supports the
435
606
  # main features of _ElasticSearch_ in Ruby.
436
607
  #
437
- # It allows you to create and delete indices, add documents, search them, retrieve facets, highlight the results,
438
- # and comes with usable logging facility.
608
+ # It allows you to create and delete indices, add documents, search them, retrieve the facets, highlight the results,
609
+ # and comes with a usable logging facility.
439
610
  #
440
611
  # Of course, the holy grail of any search library is easy, painless integration with your Ruby classes, and,
441
612
  # most importantly, with ActiveRecord/ActiveModel classes.
442
613
  #
443
- # _Slingshot_ already provides such integration, though in experimental mode.
444
- # Check out the tests in the
445
- # [`activemodel`](https://github.com/karmi/slingshot/blob/activemodel/test/integration/active_model_searchable_test.rb) branch.
614
+ # Please, check out the [README](https://github.com/karmi/slingshot/tree/master#readme) file for instructions
615
+ # how to include _Slingshot_-based search in your models..
446
616
  #
447
617
  # Send any feedback via Github issues, or ask questions in the [#elasticsearch](irc://irc.freenode.net/#elasticsearch) IRC channel.