slingshot-rb 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.