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
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ rdoc/
6
6
  coverage/
7
7
  scratch/
8
8
  examples/*.html
9
+ *.log
data/README.markdown CHANGED
@@ -4,26 +4,32 @@ Slingshot
4
4
  ![Slingshot](https://github.com/karmi/slingshot/raw/master/slingshot.png)
5
5
 
6
6
  _Slingshot_ is a Ruby client for the [ElasticSearch](http://www.elasticsearch.org/) search engine/database.
7
- It aims to provide rich and comfortable Ruby API in the form of a simple domain-specific language.
8
7
 
9
8
  _ElasticSearch_ is a scalable, distributed, cloud-ready, highly-available,
10
- RESTful database communicating by JSON over HTTP, based on [Lucene](http://lucene.apache.org/),
11
- written in Java. It manages to be very simple to use and very powerful at the same time.
9
+ full-text search engine and database, communicating by JSON over RESTful HTTP,
10
+ based on [Lucene](http://lucene.apache.org/), written in Java.
11
+
12
+ This document provides just a brief overview of _Slingshot's_ features. Be sure to check out also
13
+ the extensive documentation at <http://karmi.github.com/slingshot/> if you're interested.
12
14
 
13
15
  Installation
14
16
  ------------
15
17
 
16
18
  First, you need a running _ElasticSearch_ server. Thankfully, it's easy. Let's define easy:
17
19
 
18
- $ curl -k -L -o elasticsearch-0.15.2.tar.gz http://github.com/downloads/elasticsearch/elasticsearch/elasticsearch-0.15.2.tar.gz
19
- $ tar -zxvf elasticsearch-0.15.2.tar.gz
20
- $ ./elasticsearch-0.15.2/bin/elasticsearch -f
20
+ $ curl -k -L -o elasticsearch-0.16.0.tar.gz http://github.com/downloads/elasticsearch/elasticsearch/elasticsearch-0.16.0.tar.gz
21
+ $ tar -zxvf elasticsearch-0.16.0.tar.gz
22
+ $ ./elasticsearch-0.16.0/bin/elasticsearch -f
23
+
24
+ OK. Easy. On a Mac, you can also use _Homebrew_:
25
+
26
+ $ brew install elasticsearch
21
27
 
22
- OK, easy. Now, install the gem via Rubygems:
28
+ OK. Let's install the gem via Rubygems:
23
29
 
24
30
  $ gem install slingshot-rb
25
31
 
26
- or from source:
32
+ Of course, you can install it from the source as well:
27
33
 
28
34
  $ git clone git://github.com/karmi/slingshot.git
29
35
  $ cd slingshot
@@ -33,17 +39,20 @@ or from source:
33
39
  Usage
34
40
  -----
35
41
 
36
- Currently, you can use _Slingshot_ via the DSL (eg. by extending your class with it).
37
- Plans for full ActiveModel integration (and other convenience layers) are in progress
38
- (see the [`activemodel`](https://github.com/karmi/slingshot/compare/activemodel) branch).
42
+ _Slingshot_ exposes easy-to-use domain specific language for fluent communication with _ElasticSearch_.
39
43
 
40
- To kick the tires, require the gem in an IRB session or a Ruby script
41
- (note that you can just run the full example from [`examples/dsl.rb`](https://github.com/karmi/slingshot/blob/master/examples/dsl.rb)):
44
+ It also blends with your [ActiveModel](https://github.com/rails/rails/tree/master/activemodel)
45
+ classes for convenient usage in Rails applications.
46
+
47
+ To test-drive the core _ElasticSearch_ functionality, let's require the gem:
42
48
 
43
49
  require 'rubygems'
44
50
  require 'slingshot'
45
51
 
46
- First, let's create an index named `articles` and store/index some documents:
52
+ Please note that you can copy these snippets from the much more extensive and heavily annotated file
53
+ in [examples/slingshot-dsl.rb](http://karmi.github.com/slingshot/).
54
+
55
+ OK. Let's create an index named `articles` and store/index some documents:
47
56
 
48
57
  Slingshot.index 'articles' do
49
58
  delete
@@ -57,8 +66,9 @@ First, let's create an index named `articles` and store/index some documents:
57
66
  refresh
58
67
  end
59
68
 
60
- We can also create the
61
- index with specific [mapping](http://www.elasticsearch.org/guide/reference/api/admin-indices-create-index.html):
69
+ We can also create the index with custom
70
+ [mapping](http://www.elasticsearch.org/guide/reference/api/admin-indices-create-index.html)
71
+ for a specific document type:
62
72
 
63
73
  Slingshot.index 'articles' do
64
74
  create :mappings => {
@@ -73,9 +83,32 @@ index with specific [mapping](http://www.elasticsearch.org/guide/reference/api/a
73
83
  }
74
84
  end
75
85
 
76
- Now, let's query the database.
86
+ Of course, we may have large amounts of data, and it may be impossible or impractical to add them to the index
87
+ one by one. We can use _ElasticSearch's_ [bulk storage](http://www.elasticsearch.org/guide/reference/api/bulk.html):
88
+
89
+ articles = [
90
+ { :id => '1', :title => 'one' },
91
+ { :id => '2', :title => 'two' },
92
+ { :id => '3', :title => 'three' }
93
+ ]
94
+
95
+ Slingshot.index 'bulk' do
96
+ import articles
97
+ end
98
+
99
+ We can also easily manipulate the documents before storing them in the index, by passing a block to the
100
+ `import` method:
101
+
102
+ Slingshot.index 'bulk' do
103
+ import articles do |documents|
104
+
105
+ documents.each { |document| document[:title].capitalize! }
106
+ end
107
+ end
108
+
109
+ OK. Now, let's go search all the data.
77
110
 
78
- We are searching for articles whose `title` begins with letter “T”, sorted by `title` in `descending` order,
111
+ We will be searching for articles whose `title` begins with letter “T”, sorted by `title` in `descending` order,
79
112
  filtering them for ones tagged “ruby”, and also retrieving some [_facets_](http://www.elasticsearch.org/guide/reference/api/search/facets/)
80
113
  from the database:
81
114
 
@@ -97,6 +130,9 @@ from the database:
97
130
  end
98
131
  end
99
132
 
133
+ (Of course, we may also page the results with `from` and `size` query options, retrieve only specific fields
134
+ or highlight content matching our query, etc.)
135
+
100
136
  Let's display the results:
101
137
 
102
138
  s.results.each do |document|
@@ -128,70 +164,260 @@ count for articles tagged 'php' is excluded, since they don't match the current
128
164
  # python 1
129
165
  # java 1
130
166
 
131
- We can display the full query JSON:
167
+ If configuring the search payload with a block somehow feels too weak for you, you can simply pass
168
+ a Ruby `Hash` (or JSON string) with the query declaration to the `search` method:
169
+
170
+ Slingshot.search 'articles', :query => { :fuzzy => { :title => 'Sour' } }
171
+
172
+ If this sounds like a great idea to you, you are probably able to write your application
173
+ using just `curl`, `sed` and `awk`.
174
+
175
+ We can display the full query JSON for close inspection:
132
176
 
133
177
  puts s.to_json
134
178
  # {"facets":{"current-tags":{"terms":{"field":"tags"}},"global-tags":{"global":true,"terms":{"field":"tags"}}},"query":{"query_string":{"query":"title:T*"}},"filter":{"terms":{"tags":["ruby"]}},"sort":[{"title":"desc"}]}
135
179
 
136
- Or, better, we can display the corresponding `curl` command for easy debugging:
180
+ Or, better, we can display the corresponding `curl` command to recreate and debug the request in the terminal:
137
181
 
138
182
  puts s.to_curl
139
183
  # curl -X POST "http://localhost:9200/articles/_search?pretty=true" -d '{"facets":{"current-tags":{"terms":{"field":"tags"}},"global-tags":{"global":true,"terms":{"field":"tags"}}},"query":{"query_string":{"query":"title:T*"}},"filter":{"terms":{"tags":["ruby"]}},"sort":[{"title":"desc"}]}'
140
184
 
141
- Since `curl` is the crucial debugging tool in _ElasticSearch_ land, we can log every search query in `curl` format:
185
+ However, we can simply log every search query (and other requests) in this `curl`-friendly format:
142
186
 
143
187
  Slingshot.configure { logger 'elasticsearch.log' }
144
188
 
189
+ When you set the log level to _debug_:
145
190
 
146
- Features
147
- --------
191
+ Slingshot.configure { logger 'elasticsearch.log', :level => 'debug' }
192
+
193
+ the JSON responses are logged as well. This is not a great idea for production environment,
194
+ but it's priceless when you want to paste a complicated transaction to the mailing list or IRC channel.
195
+
196
+ The _Slingshot_ DSL tries hard to provide a strong Ruby-like API for the main _ElasticSearch_ features.
197
+
198
+ By default, _Slingshot_ wraps the results collection in a enumerable `Results::Collection` class,
199
+ and result items in a `Results::Item` class, which looks like a child of `Hash` and `Openstruct`,
200
+ for smooth iterating and displaying the results.
201
+
202
+ You may wrap the result items in your own class by setting the `Slingshot.configuration.wrapper`
203
+ property. Your class must take a `Hash` of attributes on initialization.
204
+
205
+ If that seems like a great idea to you, there's a big chance you already have such class, and one would bet
206
+ it's an `ActiveRecord` or `ActiveModel` class, containing model of your Rails application.
207
+
208
+ Fortunately, _Slingshot_ makes blending _ElasticSearch_ features into your models trivially possible.
209
+
210
+
211
+ ActiveModel Integration
212
+ -----------------------
213
+
214
+ Let's suppose you have an `Article` class in your Rails application. To make it searchable with
215
+ _Slingshot_, you just `include` it:
216
+
217
+ class Article < ActiveRecord::Base
218
+ include Slingshot::Model::Search
219
+ include Slingshot::Model::Callbacks
220
+ end
221
+
222
+ When you now save a record:
223
+
224
+ Article.create :title => "I Love ElasticSearch",
225
+ :content => "...",
226
+ :author => "Captain Nemo",
227
+ :published_on => Time.now
228
+
229
+ it is automatically added into the index, because of the included callbacks. The document attributes
230
+ are indexed exactly as when you call the `Article#to_json` method.
231
+
232
+ Now you can search the records:
233
+
234
+ Article.search 'love'
235
+
236
+ OK. Often, this is where the game stops. Not here.
237
+
238
+ First of all, you may use the full query DSL, as explained above, with filters, sorting,
239
+ advanced facet aggregation, highlighting, etc:
240
+
241
+ q = 'love'
242
+ Article.search do
243
+ query { string q }
244
+ facet('timeline') { date :published_on, :interval => 'month' }
245
+ sort { published_on 'desc' }
246
+ end
247
+
248
+ Dynamic mapping is a godsend when you're prototyping.
249
+ For serious usage, though, you'll definitely want to define a custom mapping for your model:
250
+
251
+ class Article < ActiveRecord::Base
252
+ include Slingshot::Model::Search
253
+ include Slingshot::Model::Callbacks
254
+
255
+ mapping do
256
+ indexes :id, :type => 'string', :analyzed => false
257
+ indexes :title, :type => 'string', :analyzer => 'snowball', :boost => 100
258
+ indexes :content, :type => 'string', :analyzer => 'snowball'
259
+ indexes :author, :type => 'string', :analyzer => 'keyword'
260
+ indexes :published_on, :type => 'date', :include_in_all => false
261
+ end
262
+ end
148
263
 
149
- Currently, _Slingshot_ supports main features of the _ElasticSearch_ [Search API](http://www.elasticsearch.org/guide/reference/api/search/request-body.html) and it's [Query DSL](http://www.elasticsearch.org/guide/reference/query-dsl/). In present, it allows you to:
264
+ In this case, _only_ the defined model attributes are indexed when adding to the index.
150
265
 
151
- * Create, delete and refresh the index
152
- * Create the index with specific [mapping](http://www.elasticsearch.org/guide/reference/api/admin-indices-create-index.html)
153
- * Store a document in the index
154
- * [Query](https://github.com/karmi/slingshot/blob/master/examples/dsl.rb) the index with the `query_string`, `term`, `terms` and `match_all` types of queries
155
- * [Sort](http://elasticsearch.org/guide/reference/api/search/sort.html) the results by `fields`
156
- * [Filter](http://elasticsearch.org/guide/reference/query-dsl/) the results
157
- * Retrieve the _terms_ and _date histogram_ types of [facets](http://www.elasticsearch.org/guide/reference/api/search/facets/index.html) (other types are high priority)
158
- * [Highlight](http://www.elasticsearch.org/guide/reference/api/search/highlighting.html) matching fields
159
- * Return just specific `fields` from documents
160
- * Page the results with `from` and `size` query options
161
- * Log the `curl`-equivalent of requests and response JSON
266
+ When you want tight grip on how your model attributes are added to the index, just
267
+ provide the `to_indexed_json` method yourself:
162
268
 
163
- See the [`examples/slingshot-dsl.rb`](blob/master/examples/slingshot-dsl.rb) file for the full, working examples.
269
+ class Article < ActiveRecord::Base
270
+ include Slingshot::Model::Search
271
+ include Slingshot::Model::Callbacks
164
272
 
165
- _Slingshot_ wraps the results in a enumerable `Results::Collection` class, and every result in a `Results::Item` class,
166
- which looks like a child of `Hash` and `Openstruct`, for smooth iterating and displaying the results.
273
+ def to_indexed_json
274
+ names = author.split(/\W/)
275
+ last_name = names.pop
276
+ first_name = names.join
167
277
 
168
- You may wrap the result items in your own class just by setting the `Configuration.wrapper` property,
169
- supposed your class takes a hash of attributes upon initialization, in ActiveModel/ActiveRecord manner.
170
- Please see the files `test/models/article.rb` and `test/unit/results_collection_test.rb` for details.
278
+ {
279
+ :title => title,
280
+ :content => content,
281
+ :author => {
282
+ :first_name => first_name,
283
+ :last_name => last_name
284
+ }
285
+ }.to_json
286
+ end
287
+
288
+ end
289
+
290
+ Note that _Slingshot_-enhanced models are fully compatible with [`will_paginate`](https://github.com/mislav/will_paginate),
291
+ so you can pass any parameters to the `search` method in the controller, as usual:
292
+
293
+ @articles = Article.search params[:q], :page => (params[:page] || 1)
294
+
295
+ OK. Chances are, you have lots of records stored in the underlying database. How will you get them to _ElasticSearch_? Easy:
296
+
297
+ Article.index.import Article.all
298
+
299
+ However, this way, all your records are loaded into memory, serialized into JSON,
300
+ and sent down the wire to _ElasticSearch_. Not practical, you say? You're right.
301
+
302
+ Provided your model implements some sort of _pagination_ — and it probably does, for so much data —,
303
+ you can just run:
304
+
305
+ Article.import
306
+
307
+ In this case, the `Article.paginate` method is called, and your records are sent to the index
308
+ in chunks of 1000. If that number doesn't suit you, just provide a better one:
309
+
310
+ Article.import :per_page => 100
311
+
312
+ Any other parameters you provide to the `import` method are passed down to the `paginate` method.
313
+
314
+ Are we saying you have to fiddle with this thing in a `rails console` or silly Ruby scripts? No.
315
+ Just call the included _Rake_ task on the commandline:
316
+
317
+ $ rake environment slingshot:import CLASS='Article'
318
+
319
+ You can also force-import the data by deleting the index first (and creating it with mapping
320
+ provided by the `mapping` block in your model):
321
+
322
+ $ rake environment slingshot:import CLASS='Article' FORCE=true
323
+
324
+ When you'll spend more time with _ElasticSearch_, you'll notice how
325
+ [index aliases](http://www.elasticsearch.org/guide/reference/api/admin-indices-aliases.html)
326
+ are the best idea since the invention of inverted index.
327
+ You can index your data into a fresh index (and possibly update an alias if everything's fine):
328
+
329
+ $ rake environment slingshot:import CLASS='Article' INDEX='articles-2011-05'
330
+
331
+ If you're the type who has no time for long introductions, you can generate a fully working
332
+ example Rails application, with an `ActiveRecord` model and a search form, to play with:
333
+
334
+ $ rails new searchapp -m https://github.com/karmi/slingshot/raw/master/examples/rails-application-template.rb
335
+
336
+ OK. All this time we have been talking about `ActiveRecord` models, since
337
+ it is a reasonable Rails' default for the storage layer.
338
+
339
+ But what if you use another database such as [MongoDB](http://www.mongodb.org/),
340
+ another object mapping library, such as [Mongoid](http://mongoid.org/)?
341
+
342
+ Well, things stay mostly the same:
343
+
344
+ class Article
345
+ include Mongoid::Document
346
+ field :title, :type => String
347
+ field :content, :type => String
348
+
349
+ include Slingshot::Model::Search
350
+ include Slingshot::Model::Callbacks
351
+
352
+ # Let's use a different index name so stuff doesn't get mixed up
353
+ #
354
+ index_name 'mongo-articles'
355
+
356
+ # These Mongo guys sure do some funky stuff with their IDs
357
+ # in +serializable_hash+, let's fix it.
358
+ #
359
+ def to_indexed_json
360
+ self.to_json
361
+ end
362
+
363
+ end
364
+
365
+ Article.create :title => 'I Love ElasticSearch'
366
+
367
+ Article.search 'love'
368
+
369
+ That's kinda nice. But there's more.
370
+
371
+ _Slingshot_ implements not only _searchable_ features, but also _persistence_ features.
372
+
373
+ This means that you can use a _Slingshot_ model **instead of** your database, not just
374
+ for searching your database. Why would you like to do that?
375
+
376
+ Well, because you're tired of database migrations and lots of hand-holding with your
377
+ database to store stuff like `{ :name => 'Slingshot', :tags => [ 'ruby', 'search' ] }`.
378
+ Because what you need is to just dump a JSON-representation of your data into a database and
379
+ load it back when needed.
380
+ Because you've noticed that _searching_ your data is a much more effective way of retrieval
381
+ then constructing elaborate database query conditions.
382
+ Because you have _lots_ of data and want to use _ElasticSearch's_
383
+ advanced distributed features.
384
+
385
+ To use the persistence features, you have to include the `Slingshot::Persistence` module
386
+ in your class and define the properties (analogous to the way you do with CouchDB- or MongoDB-based models):
387
+
388
+ class Article
389
+ include Slingshot::Model::Persistence
390
+ include Slingshot::Model::Search
391
+ include Slingshot::Model::Callbacks
392
+
393
+ validates_presence_of :title, :author
394
+
395
+ property :title
396
+ property :author
397
+ property :content
398
+ property :published_on
399
+
400
+ end
171
401
 
402
+ Of course, not all validations or `ActionPack` helpers will be available to your models,
403
+ but if you can live with that, you've just got a schema-free, highly-scalable storage
404
+ and retrieval engine for your data.
172
405
 
173
406
  Todo, Plans & Ideas
174
407
  -------------------
175
408
 
176
- _Slingshot_ is already used in production by its authors. Nevertheless, it's not finished yet.
409
+ _Slingshot_ is already used in production by its authors. Nevertheless, it's not considered finished yet.
177
410
 
178
- The todos and plans are vast, and the most important are listed below, in the order of importance:
411
+ There are todos, plans and ideas, some of which are listed below, in the order of importance:
179
412
 
180
- * Seamless _ActiveModel_ compatibility for easy usage in _Rails_ applications (this also means nearly full _ActiveRecord_ compatibility). See the ongoing work in the [`activemodel`](https://github.com/karmi/slingshot/compare/activemodel) branch
181
- * Seamless [will_paginate](https://github.com/mislav/will_paginate) compatibility for easy pagination. Already [implemented](https://github.com/karmi/slingshot/commit/e1351f6) on the `activemodel` branch
182
- * [Mapping](http://www.elasticsearch.org/guide/reference/mapping/) definition for models
183
413
  * Proper RDoc annotations for the source code
184
- * Dual interface: allow to simply pass queries/options for _ElasticSearch_ as a Hash in any method
185
414
  * [Histogram](http://www.elasticsearch.org/guide/reference/api/search/facets/histogram-facet.html) facets
186
415
  * [Statistical](http://www.elasticsearch.org/guide/reference/api/search/facets/statistical-facet.html) facets
187
416
  * [Geo Distance](http://www.elasticsearch.org/guide/reference/api/search/facets/geo-distance-facet.html) facets
188
417
  * [Index aliases](http://www.elasticsearch.org/guide/reference/api/admin-indices-aliases.html) management
189
418
  * [Analyze](http://www.elasticsearch.org/guide/reference/api/admin-indices-analyze.html) API support
190
- * [Bulk](http://www.elasticsearch.org/guide/reference/api/bulk.html) API
191
419
  * Embedded webserver to display statistics and to allow easy searches
192
- * Seamless support for [auto-updating _river_ index](http://www.elasticsearch.org/guide/reference/river/couchdb.html) for _CouchDB_ `_changes` feed
193
420
 
194
- The full ActiveModel integration is planned for the 1.0 release.
195
421
 
196
422
  Other Clients
197
423
  -------------
@@ -0,0 +1,144 @@
1
+ # ===================================================================================================================
2
+ # Template for generating a no-frills Rails application with support for ElasticSearch full-text search via Slingshot
3
+ # ===================================================================================================================
4
+ #
5
+ # This file creates a basic Rails application with support for ElasticSearch full-text via the Slingshot gem
6
+ #
7
+ # Run it like this:
8
+ #
9
+ # rails new searchapp -m https://github.com/karmi/slingshot/raw/master/examples/rails-application-template.rb
10
+ #
11
+
12
+ run "rm public/index.html"
13
+ run "rm public/images/rails.png"
14
+ run "touch tmp/.gitignore log/.gitignore vendor/.gitignore"
15
+
16
+ git :init
17
+ git :add => '.'
18
+ git :commit => "-m 'Initial commit: Clean application'"
19
+
20
+ puts
21
+ say_status "Rubygems", "Adding Rubygems into Gemfile...\n", :yellow
22
+ puts '-'*80, ''
23
+
24
+ gem 'slingshot-rb', :git => 'https://github.com/karmi/slingshot.git', :branch => 'activemodel'
25
+ gem 'will_paginate', '~>3.0.pre'
26
+ git :add => '.'
27
+ git :commit => "-m 'Added gems'"
28
+
29
+ puts
30
+ say_status "Rubygems", "Installing Rubygems...", :yellow
31
+
32
+ puts
33
+ puts "********************************************************************************"
34
+ puts " Running `bundle install`. Let's watch a movie!"
35
+ puts "********************************************************************************", ""
36
+
37
+ run "bundle install"
38
+
39
+ puts
40
+ say_status "Model", "Adding search support into the Article model...", :yellow
41
+ puts '-'*80, ''
42
+
43
+ generate :scaffold, "Article title:string content:text published_on:date"
44
+ route "root :to => 'articles#index'"
45
+ rake "db:migrate"
46
+
47
+ git :add => '.'
48
+ git :commit => "-m 'Added the Article resource'"
49
+
50
+ run "rm -f app/models/article.rb"
51
+ file 'app/models/article.rb', <<-CODE
52
+ class Article < ActiveRecord::Base
53
+ include Slingshot::Model::Search
54
+ include Slingshot::Model::Callbacks
55
+ end
56
+ CODE
57
+
58
+ initializer 'slingshot.rb', <<-CODE
59
+ Slingshot.configure do
60
+ logger STDERR
61
+ end
62
+ CODE
63
+
64
+ git :commit => "-a -m 'Added Slingshot support into the Article class and an initializer'"
65
+
66
+ puts
67
+ say_status "Controller", "Adding controller action, route, and neccessary HTML for search...", :yellow
68
+ puts '-'*80, ''
69
+
70
+ gsub_file 'app/controllers/articles_controller.rb', %r{# GET /articles/1$}, <<-CODE
71
+ # GET /articles/search
72
+ def search
73
+ @articles = Article.search params[:q]
74
+
75
+ render :action => "index"
76
+ end
77
+
78
+ # GET /articles/1
79
+ CODE
80
+
81
+ gsub_file 'app/views/articles/index.html.erb', %r{<h1>Listing articles</h1>}, <<-CODE
82
+ <h1>Listing articles</h1>
83
+
84
+ <%= form_tag search_articles_path, :method => 'get' do %>
85
+ <%= label_tag :query %>
86
+ <%= text_field_tag :q, params[:q] %>
87
+ <%= submit_tag :search %>
88
+ <% end %>
89
+
90
+ <hr>
91
+ CODE
92
+
93
+ gsub_file 'app/views/articles/index.html.erb', %r{<%= link_to 'New Article', new_article_path %>}, <<-CODE
94
+ <%= link_to 'New Article', new_article_path %>
95
+ <%= link_to 'Back', articles_path if params[:q] %>
96
+ CODE
97
+
98
+ gsub_file 'config/routes.rb', %r{resources :articles}, <<-CODE
99
+ resources :articles do
100
+ collection { get :search }
101
+ end
102
+ CODE
103
+
104
+ git :commit => "-a -m 'Added Slingshot support into the frontend of application'"
105
+
106
+ puts
107
+ say_status "Database", "Seeding the database with data...", :yellow
108
+ puts '-'*80, ''
109
+
110
+ run "rm -f db/seeds.rb"
111
+ file 'db/seeds.rb', <<-CODE
112
+ contents = [
113
+ 'Lorem ipsum dolor sit amet.',
114
+ 'Consectetur adipisicing elit, sed do eiusmod tempor incididunt.',
115
+ 'Labore et dolore magna aliqua.',
116
+ 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.',
117
+ 'Excepteur sint occaecat cupidatat non proident.'
118
+ ]
119
+
120
+ puts "Deleting all articles..."
121
+ Article.delete_all
122
+
123
+ puts "Creating articles:"
124
+ %w[ One Two Three Four Five ].each_with_index do |title, i|
125
+ Article.create :title => title, :content => contents[i], :published_on => i.days.ago.utc
126
+ end
127
+ CODE
128
+
129
+ rake "db:seed"
130
+
131
+ git :add => "db/seeds.rb"
132
+ git :commit => "-m 'Added database seeding script'"
133
+
134
+ puts
135
+ say_status "Index", "Indexing database...", :yellow
136
+ puts '-'*80, ''
137
+
138
+ rake "environment slingshot:import CLASS='Article' FORCE=true"
139
+
140
+ puts "", "="*80
141
+ say_status "DONE", "\e[1mStarting the application. Open http://localhost:3000 and search for something...\e[0m", :yellow
142
+ puts "="*80, ""
143
+
144
+ run "rails server"