load_balanced_tire 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/.gitignore +14 -0
  2. data/.travis.yml +29 -0
  3. data/Gemfile +4 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.markdown +760 -0
  6. data/Rakefile +78 -0
  7. data/examples/rails-application-template.rb +249 -0
  8. data/examples/tire-dsl.rb +876 -0
  9. data/lib/tire.rb +55 -0
  10. data/lib/tire/alias.rb +296 -0
  11. data/lib/tire/configuration.rb +30 -0
  12. data/lib/tire/dsl.rb +43 -0
  13. data/lib/tire/http/client.rb +62 -0
  14. data/lib/tire/http/clients/curb.rb +61 -0
  15. data/lib/tire/http/clients/faraday.rb +71 -0
  16. data/lib/tire/http/response.rb +27 -0
  17. data/lib/tire/index.rb +361 -0
  18. data/lib/tire/logger.rb +60 -0
  19. data/lib/tire/model/callbacks.rb +40 -0
  20. data/lib/tire/model/import.rb +26 -0
  21. data/lib/tire/model/indexing.rb +128 -0
  22. data/lib/tire/model/naming.rb +100 -0
  23. data/lib/tire/model/percolate.rb +99 -0
  24. data/lib/tire/model/persistence.rb +71 -0
  25. data/lib/tire/model/persistence/attributes.rb +143 -0
  26. data/lib/tire/model/persistence/finders.rb +66 -0
  27. data/lib/tire/model/persistence/storage.rb +69 -0
  28. data/lib/tire/model/search.rb +307 -0
  29. data/lib/tire/results/collection.rb +114 -0
  30. data/lib/tire/results/item.rb +86 -0
  31. data/lib/tire/results/pagination.rb +54 -0
  32. data/lib/tire/rubyext/hash.rb +8 -0
  33. data/lib/tire/rubyext/ruby_1_8.rb +7 -0
  34. data/lib/tire/rubyext/symbol.rb +11 -0
  35. data/lib/tire/search.rb +188 -0
  36. data/lib/tire/search/facet.rb +74 -0
  37. data/lib/tire/search/filter.rb +28 -0
  38. data/lib/tire/search/highlight.rb +37 -0
  39. data/lib/tire/search/query.rb +186 -0
  40. data/lib/tire/search/scan.rb +114 -0
  41. data/lib/tire/search/script_field.rb +23 -0
  42. data/lib/tire/search/sort.rb +25 -0
  43. data/lib/tire/tasks.rb +135 -0
  44. data/lib/tire/utils.rb +17 -0
  45. data/lib/tire/version.rb +22 -0
  46. data/test/fixtures/articles/1.json +1 -0
  47. data/test/fixtures/articles/2.json +1 -0
  48. data/test/fixtures/articles/3.json +1 -0
  49. data/test/fixtures/articles/4.json +1 -0
  50. data/test/fixtures/articles/5.json +1 -0
  51. data/test/integration/active_model_indexing_test.rb +51 -0
  52. data/test/integration/active_model_searchable_test.rb +114 -0
  53. data/test/integration/active_record_searchable_test.rb +446 -0
  54. data/test/integration/boolean_queries_test.rb +43 -0
  55. data/test/integration/count_test.rb +34 -0
  56. data/test/integration/custom_score_queries_test.rb +88 -0
  57. data/test/integration/dis_max_queries_test.rb +68 -0
  58. data/test/integration/dsl_search_test.rb +22 -0
  59. data/test/integration/explanation_test.rb +44 -0
  60. data/test/integration/facets_test.rb +259 -0
  61. data/test/integration/filtered_queries_test.rb +66 -0
  62. data/test/integration/filters_test.rb +63 -0
  63. data/test/integration/fuzzy_queries_test.rb +20 -0
  64. data/test/integration/highlight_test.rb +64 -0
  65. data/test/integration/index_aliases_test.rb +122 -0
  66. data/test/integration/index_mapping_test.rb +43 -0
  67. data/test/integration/index_store_test.rb +96 -0
  68. data/test/integration/index_update_document_test.rb +111 -0
  69. data/test/integration/mongoid_searchable_test.rb +309 -0
  70. data/test/integration/percolator_test.rb +111 -0
  71. data/test/integration/persistent_model_test.rb +130 -0
  72. data/test/integration/prefix_query_test.rb +43 -0
  73. data/test/integration/query_return_version_test.rb +70 -0
  74. data/test/integration/query_string_test.rb +52 -0
  75. data/test/integration/range_queries_test.rb +36 -0
  76. data/test/integration/reindex_test.rb +46 -0
  77. data/test/integration/results_test.rb +39 -0
  78. data/test/integration/scan_test.rb +56 -0
  79. data/test/integration/script_fields_test.rb +38 -0
  80. data/test/integration/sort_test.rb +36 -0
  81. data/test/integration/text_query_test.rb +39 -0
  82. data/test/models/active_model_article.rb +31 -0
  83. data/test/models/active_model_article_with_callbacks.rb +49 -0
  84. data/test/models/active_model_article_with_custom_document_type.rb +7 -0
  85. data/test/models/active_model_article_with_custom_index_name.rb +7 -0
  86. data/test/models/active_record_models.rb +122 -0
  87. data/test/models/article.rb +15 -0
  88. data/test/models/mongoid_models.rb +97 -0
  89. data/test/models/persistent_article.rb +11 -0
  90. data/test/models/persistent_article_in_namespace.rb +12 -0
  91. data/test/models/persistent_article_with_casting.rb +28 -0
  92. data/test/models/persistent_article_with_defaults.rb +11 -0
  93. data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
  94. data/test/models/supermodel_article.rb +17 -0
  95. data/test/models/validated_model.rb +11 -0
  96. data/test/test_helper.rb +93 -0
  97. data/test/unit/active_model_lint_test.rb +17 -0
  98. data/test/unit/configuration_test.rb +74 -0
  99. data/test/unit/http_client_test.rb +76 -0
  100. data/test/unit/http_response_test.rb +49 -0
  101. data/test/unit/index_alias_test.rb +275 -0
  102. data/test/unit/index_test.rb +894 -0
  103. data/test/unit/logger_test.rb +125 -0
  104. data/test/unit/model_callbacks_test.rb +116 -0
  105. data/test/unit/model_import_test.rb +71 -0
  106. data/test/unit/model_persistence_test.rb +528 -0
  107. data/test/unit/model_search_test.rb +913 -0
  108. data/test/unit/results_collection_test.rb +281 -0
  109. data/test/unit/results_item_test.rb +162 -0
  110. data/test/unit/rubyext_test.rb +66 -0
  111. data/test/unit/search_facet_test.rb +153 -0
  112. data/test/unit/search_filter_test.rb +42 -0
  113. data/test/unit/search_highlight_test.rb +46 -0
  114. data/test/unit/search_query_test.rb +301 -0
  115. data/test/unit/search_scan_test.rb +113 -0
  116. data/test/unit/search_script_field_test.rb +26 -0
  117. data/test/unit/search_sort_test.rb +50 -0
  118. data/test/unit/search_test.rb +499 -0
  119. data/test/unit/tire_test.rb +126 -0
  120. data/tire.gemspec +90 -0
  121. metadata +549 -0
data/Rakefile ADDED
@@ -0,0 +1,78 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ task :default => :test
5
+
6
+ require 'rake/testtask'
7
+ Rake::TestTask.new(:test) do |test|
8
+ test.libs << 'lib' << 'test'
9
+ test.test_files = FileList['test/unit/*_test.rb', 'test/integration/*_test.rb']
10
+ test.verbose = true
11
+ # test.warning = true
12
+ end
13
+
14
+ namespace :test do
15
+ Rake::TestTask.new(:unit) do |test|
16
+ test.libs << 'lib' << 'test'
17
+ test.test_files = FileList["test/unit/*_test.rb"]
18
+ test.verbose = true
19
+ end
20
+ Rake::TestTask.new(:integration) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.test_files = FileList["test/integration/*_test.rb"]
23
+ test.verbose = true
24
+ end
25
+ end
26
+
27
+ # Generate documentation
28
+ begin
29
+ require 'rdoc'
30
+ require 'rdoc/task'
31
+ Rake::RDocTask.new do |rdoc|
32
+ rdoc.rdoc_dir = 'rdoc'
33
+ rdoc.title = "Tire"
34
+ rdoc.rdoc_files.include('README.markdown')
35
+ rdoc.rdoc_files.include('lib/**/*.rb')
36
+ end
37
+ rescue LoadError
38
+ task :rdoc do
39
+ abort "[!] RDoc gem is not available."
40
+ end
41
+ end
42
+
43
+ # Generate coverage reports
44
+ begin
45
+ require 'rcov/rcovtask'
46
+ Rcov::RcovTask.new do |test|
47
+ test.libs << 'test'
48
+ test.rcov_opts = ['--exclude', 'gems/*']
49
+ test.pattern = 'test/**/*_test.rb'
50
+ test.verbose = true
51
+ end
52
+ rescue LoadError
53
+ task :rcov do
54
+ abort "[!] RCov gem is not available."
55
+ end
56
+ end
57
+
58
+ namespace :web do
59
+
60
+ desc "Update the Github website"
61
+ task :update => :generate do
62
+ current_branch = `git branch --no-color`.split("\n").select { |line| line =~ /^\* / }.to_s.gsub(/\* (.*)/, '\1')
63
+ (puts "Unable to determine current branch"; exit(1) ) unless current_branch
64
+ system "git checkout web"
65
+ system "cp examples/tire-dsl.html index.html"
66
+ system "git add index.html && git co -m 'Updated Tire website'"
67
+ system "git push origin web:gh-pages -f"
68
+ system "git checkout #{current_branch}"
69
+ end
70
+
71
+ desc "Generate the Rocco documentation page"
72
+ task :generate do
73
+ system "rocco examples/tire-dsl.rb"
74
+ html = File.read('examples/tire-dsl.html').gsub!(/>tire\-dsl\.rb</, '>tire.rb<')
75
+ File.open('examples/tire-dsl.html', 'w') { |f| f.write html }
76
+ system "open examples/tire-dsl.html"
77
+ end
78
+ end
@@ -0,0 +1,249 @@
1
+ # ===================================================================================================================
2
+ # Template for generating a no-frills Rails application with support for ElasticSearch full-text search via Tire
3
+ # ===================================================================================================================
4
+ #
5
+ # This file creates a basic, fully working Rails application with support for ElasticSearch full-text search
6
+ # via the Tire gem [http://github.com/karmi/tire].
7
+ #
8
+ # You DON'T NEED ELASTICSEARCH INSTALLED, it is installed and launched automatically by this script.
9
+ #
10
+ # Requirements
11
+ # ------------
12
+ #
13
+ # * Git
14
+ # * Ruby >= 1.8.7
15
+ # * Rubygems
16
+ # * Rails >= 3.0.7
17
+ # * Sun Java 6 (for ElasticSearch)
18
+ #
19
+ #
20
+ # Usage
21
+ # -----
22
+ #
23
+ # $ rails new tired -m https://github.com/karmi/tire/raw/master/examples/rails-application-template.rb
24
+ #
25
+ # ===================================================================================================================
26
+
27
+ require 'rubygems'
28
+
29
+ begin
30
+ require 'restclient'
31
+ rescue LoadError
32
+ puts "\n"
33
+ say_status "ERROR", "Rubygem 'rest-client' not installed\n", :red
34
+ puts '-'*80
35
+ say_status "", "gem install rest-client"
36
+ puts "\n"
37
+
38
+ if yes?("Should I install it for you?", :bold)
39
+ say_status "gem", "install rest-client", :yellow
40
+ system "gem install rest-client"
41
+ else
42
+ exit(1)
43
+ end
44
+ end
45
+
46
+ at_exit do
47
+ pid = File.read("#{destination_root}/tmp/pids/elasticsearch.pid") rescue nil
48
+ if pid
49
+ say_status "Stop", "ElasticSearch", :yellow
50
+ run "kill #{pid}"
51
+ end
52
+ end
53
+
54
+ run "rm public/index.html"
55
+ run "rm public/images/rails.png"
56
+ run "touch tmp/.gitignore log/.gitignore vendor/.gitignore"
57
+
58
+ run "rm -f .gitignore"
59
+ file ".gitignore", <<-END.gsub(/ /, '')
60
+ .DS_Store
61
+ log/*.log
62
+ tmp/**/*
63
+ config/database.yml
64
+ db/*.sqlite3
65
+ vendor/elasticsearch-0.19.0/
66
+ END
67
+
68
+ git :init
69
+ git :add => '.'
70
+ git :commit => "-m 'Initial commit: Clean application'"
71
+
72
+ unless (RestClient.get('http://localhost:9200') rescue false)
73
+ COMMAND = <<-COMMAND.gsub(/^ /, '')
74
+ curl -k -L -# -o elasticsearch-0.19.0.tar.gz \
75
+ "http://github.com/downloads/elasticsearch/elasticsearch/elasticsearch-0.19.0.tar.gz"
76
+ tar -zxf elasticsearch-0.19.0.tar.gz
77
+ rm -f elasticsearch-0.19.0.tar.gz
78
+ ./elasticsearch-0.19.0/bin/elasticsearch -p #{destination_root}/tmp/pids/elasticsearch.pid
79
+ COMMAND
80
+
81
+ puts "\n"
82
+ say_status "ERROR", "ElasticSearch not running!\n", :red
83
+ puts '-'*80
84
+ say_status '', "It appears that ElasticSearch is not running on this machine."
85
+ say_status '', "Is it installed? Do you want me to install it for you with this command?\n\n"
86
+ COMMAND.each_line { |l| say_status '', "$ #{l}" }
87
+ puts
88
+ say_status '', "(To uninstall, just remove the generated application directory.)"
89
+ puts '-'*80, ''
90
+
91
+ if yes?("Install ElasticSearch?", :bold)
92
+ puts
93
+ say_status "Install", "ElasticSearch", :yellow
94
+
95
+ commands = COMMAND.split("\n")
96
+ exec = commands.pop
97
+ inside("vendor") do
98
+ commands.each { |command| run command }
99
+ run "(#{exec})" # Launch ElasticSearch in subshell
100
+ end
101
+ end
102
+ end
103
+
104
+ puts
105
+ say_status "Rubygems", "Adding Rubygems into Gemfile...\n", :yellow
106
+ puts '-'*80, ''; sleep 1
107
+
108
+ gem 'tire'
109
+ gem 'will_paginate', '~> 3.0'
110
+
111
+ git :add => '.'
112
+ git :commit => "-m 'Added gems'"
113
+
114
+ puts
115
+ say_status "Rubygems", "Installing Rubygems...", :yellow
116
+ puts '-'*80, ''
117
+
118
+ puts "********************************************************************************"
119
+ puts " Running `bundle install`. Let's watch a movie!"
120
+ puts "********************************************************************************", ""
121
+
122
+ run "bundle install"
123
+
124
+ puts
125
+ say_status "Model", "Adding the Article resource...", :yellow
126
+ puts '-'*80, ''; sleep 1
127
+
128
+ generate :scaffold, "Article title:string content:text published_on:date"
129
+ route "root :to => 'articles#index'"
130
+ rake "db:migrate"
131
+
132
+ git :add => '.'
133
+ git :commit => "-m 'Added the Article resource'"
134
+
135
+ puts
136
+ say_status "Database", "Seeding the database with data...", :yellow
137
+ puts '-'*80, ''; sleep 0.25
138
+
139
+ run "rm -f db/seeds.rb"
140
+ file 'db/seeds.rb', <<-CODE
141
+ contents = [
142
+ 'Lorem ipsum dolor sit amet.',
143
+ 'Consectetur adipisicing elit, sed do eiusmod tempor incididunt.',
144
+ 'Labore et dolore magna aliqua.',
145
+ 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.',
146
+ 'Excepteur sint occaecat cupidatat non proident.'
147
+ ]
148
+
149
+ puts "Deleting all articles..."
150
+ Article.delete_all
151
+
152
+ puts "Creating articles..."
153
+ %w[ One Two Three Four Five ].each_with_index do |title, i|
154
+ Article.create :title => title, :content => contents[i], :published_on => i.days.ago.utc
155
+ end
156
+ CODE
157
+
158
+ rake "db:seed"
159
+
160
+ git :add => "db/seeds.rb"
161
+ git :commit => "-m 'Added database seeding script'"
162
+
163
+ puts
164
+ say_status "Model", "Adding search support into the Article model...", :yellow
165
+ puts '-'*80, ''; sleep 1
166
+
167
+ run "rm -f app/models/article.rb"
168
+ file 'app/models/article.rb', <<-CODE
169
+ class Article < ActiveRecord::Base
170
+ include Tire::Model::Search
171
+ include Tire::Model::Callbacks
172
+ end
173
+ CODE
174
+
175
+ initializer 'tire.rb', <<-CODE
176
+ Tire.configure do
177
+ logger STDERR
178
+ end
179
+ CODE
180
+
181
+ git :commit => "-a -m 'Added Tire support into the Article class and an initializer'"
182
+
183
+ puts
184
+ say_status "Controller", "Adding controller action, route, and HTML for search...", :yellow
185
+ puts '-'*80, ''; sleep 1
186
+
187
+ gsub_file 'app/controllers/articles_controller.rb', %r{# GET /articles/1$}, <<-CODE
188
+ # GET /articles/search
189
+ def search
190
+ @articles = Article.search params[:q]
191
+
192
+ render :action => "index"
193
+ end
194
+
195
+ # GET /articles/1
196
+ CODE
197
+
198
+ gsub_file 'app/views/articles/index.html.erb', %r{<h1>Listing articles</h1>}, <<-CODE
199
+ <h1>Listing articles</h1>
200
+
201
+ <%= form_tag search_articles_path, :method => 'get' do %>
202
+ <%= label_tag :query %>
203
+ <%= text_field_tag :q, params[:q] %>
204
+ <%= submit_tag :search %>
205
+ <% end %>
206
+
207
+ <hr>
208
+ CODE
209
+
210
+ gsub_file 'app/views/articles/index.html.erb', %r{<%= link_to 'New Article', new_article_path %>}, <<-CODE
211
+ <%= link_to 'New Article', new_article_path %>
212
+ <%= link_to 'Back', articles_path if params[:q] %>
213
+ CODE
214
+
215
+ gsub_file 'config/routes.rb', %r{resources :articles}, <<-CODE
216
+ resources :articles do
217
+ collection { get :search }
218
+ end
219
+ CODE
220
+
221
+ git :commit => "-a -m 'Added Tire support into the frontend of application'"
222
+
223
+ puts
224
+ say_status "Index", "Indexing the database...", :yellow
225
+ puts '-'*80, ''; sleep 0.5
226
+
227
+ rake "environment tire:import CLASS='Article' FORCE=true"
228
+
229
+ puts
230
+ say_status "Git", "Details about the application:", :yellow
231
+ puts '-'*80, ''
232
+
233
+ run "git log --reverse --pretty=format:'%Cblue%h%Creset | %s'"
234
+
235
+ if (begin; RestClient.get('http://localhost:3000'); rescue Errno::ECONNREFUSED; false; rescue Exception; true; end)
236
+ puts "\n"
237
+ say_status "ERROR", "Some other application is running on port 3000!\n", :red
238
+ puts '-'*80
239
+
240
+ port = ask("Please provide free port:", :bold)
241
+ else
242
+ port = '3000'
243
+ end
244
+
245
+ puts "", "="*80
246
+ say_status "DONE", "\e[1mStarting the application. Open http://localhost:#{port}\e[0m", :yellow
247
+ puts "="*80, ""
248
+
249
+ run "rails server --port=#{port}"
@@ -0,0 +1,876 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # **Tire** provides rich and comfortable Ruby API for the
4
+ # [_ElasticSearch_](http://www.elasticsearch.org/) search engine/database.
5
+ #
6
+ # _ElasticSearch_ is a scalable, distributed, cloud-ready, highly-available
7
+ # full-text search engine and database, communicating by JSON over RESTful HTTP,
8
+ # based on [Lucene](http://lucene.apache.org/), written in Java.
9
+ #
10
+ # <img src="http://github.com/favicon.ico" style="position:relative; top:2px">
11
+ # _Tire_ is open source, and you can download or clone the source code
12
+ # from <https://github.com/karmi/tire>.
13
+ #
14
+ # By following these instructions you should have the search running
15
+ # on a sane operating system in less then 10 minutes.
16
+
17
+ # Note, that this file can be executed directly:
18
+ #
19
+ # ruby -I lib examples/tire-dsl.rb
20
+ #
21
+
22
+
23
+ #### Installation
24
+
25
+ # Install _Tire_ with _Rubygems_:
26
+
27
+ #
28
+ # gem install tire
29
+ #
30
+ require 'rubygems'
31
+
32
+ # _Tire_ uses the [_multi_json_](https://github.com/intridea/multi_json) gem as a generic JSON library.
33
+ # We want to use the [_yajl-ruby_](https://github.com/brianmario/yajl-ruby) gem in its full on mode here.
34
+ #
35
+ require 'yajl/json_gem'
36
+
37
+ # Now, let's require the _Tire_ gem itself, and we're ready to go.
38
+ #
39
+ require 'tire'
40
+
41
+ #### Prerequisites
42
+
43
+ # We'll need a working and running _ElasticSearch_ server, of course. Thankfully, that's easy.
44
+ ( puts <<-"INSTALL" ; exit(1) ) unless (RestClient.get('http://localhost:9200') rescue false)
45
+
46
+ [ERROR] You don’t appear to have ElasticSearch installed. Please install and launch it with the following commands:
47
+
48
+ curl -k -L -o elasticsearch-0.19.0.tar.gz http://github.com/downloads/elasticsearch/elasticsearch/elasticsearch-0.19.0.tar.gz
49
+ tar -zxvf elasticsearch-0.19.0.tar.gz
50
+ ./elasticsearch-0.19.0/bin/elasticsearch -f
51
+ INSTALL
52
+
53
+ ### Storing and indexing documents
54
+
55
+ # Let's initialize an index named “articles”.
56
+ #
57
+ Tire.index 'articles' do
58
+ # To make sure it's fresh, let's delete any existing index with the same name.
59
+ #
60
+ delete
61
+ # And then, let's create it.
62
+ #
63
+ create
64
+
65
+ # We want to store and index some articles with `title`, `tags` and `published_on` properties.
66
+ # Simple Hashes are OK. The default type is „document”.
67
+ #
68
+ store :title => 'One', :tags => ['ruby'], :published_on => '2011-01-01'
69
+ store :title => 'Two', :tags => ['ruby', 'python'], :published_on => '2011-01-02'
70
+
71
+ # We usually want to set a specific _type_ for the document in _ElasticSearch_.
72
+ # Simply setting a `type` property is OK.
73
+ #
74
+ store :type => 'article',
75
+ :title => 'Three',
76
+ :tags => ['java'],
77
+ :published_on => '2011-01-02'
78
+
79
+ # We may want to wrap your data in a Ruby class, and use it when storing data.
80
+ # The contract required of such a class is very simple.
81
+ #
82
+ class Article
83
+
84
+ #
85
+ attr_reader :title, :tags, :published_on
86
+ def initialize(attributes={})
87
+ @attributes = attributes
88
+ @attributes.each_pair { |name,value| instance_variable_set :"@#{name}", value }
89
+ end
90
+
91
+ # It must provide a `type`, `_type` or `document_type` method for propper mapping.
92
+ #
93
+ def type
94
+ 'article'
95
+ end
96
+
97
+ # And it must provide a `to_indexed_json` method for conversion to JSON.
98
+ #
99
+ def to_indexed_json
100
+ @attributes.to_json
101
+ end
102
+ end
103
+
104
+ # Note: Since our class takes a Hash of attributes on initialization, we may even
105
+ # wrap the results in instances of this class; we'll see how to do that further below.
106
+ #
107
+ article = Article.new :title => 'Four',
108
+ :tags => ['ruby', 'php'],
109
+ :published_on => '2011-01-03'
110
+
111
+ # Let's store the `article`, now.
112
+ #
113
+ store article
114
+
115
+ # And let's „force refresh“ the index, so we can query it immediately.
116
+ #
117
+ refresh
118
+ end
119
+
120
+ # We may want to define a specific [mapping](http://www.elasticsearch.org/guide/reference/api/admin-indices-create-index.html)
121
+ # for the index.
122
+
123
+ Tire.index 'articles' do
124
+ # To do so, let's just pass a Hash containing the specified mapping to the `Index#create` method.
125
+ #
126
+ create :mappings => {
127
+
128
+ # Let's specify for which _type_ of documents this mapping should be used:
129
+ # „article”, in our case.
130
+ #
131
+ :article => {
132
+ :properties => {
133
+
134
+ # Let's specify the type of the field, whether it should be analyzed, ...
135
+ #
136
+ :id => { :type => 'string', :index => 'not_analyzed', :include_in_all => false },
137
+
138
+ # ... set the boost or analyzer settings for the field, etc. The _ElasticSearch_ guide
139
+ # has [more information](http://elasticsearch.org/guide/reference/mapping/index.html).
140
+ # Don't forget, that proper mapping is key to efficient and effective search.
141
+ # But don't fret about getting the mapping right the first time, you won't.
142
+ # In most cases, the default, dynamic mapping is just fine for prototyping.
143
+ #
144
+ :title => { :type => 'string', :analyzer => 'snowball', :boost => 2.0 },
145
+ :tags => { :type => 'string', :analyzer => 'keyword' },
146
+ :content => { :type => 'string', :analyzer => 'czech' }
147
+ }
148
+ }
149
+ }
150
+ end
151
+
152
+ #### Bulk Indexing
153
+
154
+ # Of course, we may have large amounts of data, and adding them to the index one by one really isn't the best idea.
155
+ # We can use _ElasticSearch's_ [bulk API](http://www.elasticsearch.org/guide/reference/api/bulk.html)
156
+ # for importing the data.
157
+
158
+ # So, for demonstration purposes, let's suppose we have a simple collection of hashes to store.
159
+ #
160
+ articles = [
161
+
162
+ # Notice that such objects must have an `id` property!
163
+ #
164
+ { :id => '1', :type => 'article', :title => 'one', :tags => ['ruby'], :published_on => '2011-01-01' },
165
+
166
+ # And, of course, they should contain the `type` property for the mapping to work!
167
+ #
168
+ { :id => '2', :type => 'article', :title => 'two', :tags => ['ruby', 'python'], :published_on => '2011-01-02' },
169
+ { :id => '3', :type => 'article', :title => 'three', :tags => ['java'], :published_on => '2011-01-02' },
170
+ { :id => '4', :type => 'article', :title => 'four', :tags => ['ruby', 'php'], :published_on => '2011-01-03' }
171
+ ]
172
+
173
+ # We can just push them into the index in one go.
174
+ #
175
+ Tire.index 'articles' do
176
+ import articles
177
+ end
178
+
179
+ # Of course, we can easily manipulate the documents before storing them in the index.
180
+ #
181
+ Tire.index 'articles' do
182
+ delete
183
+
184
+ # ... by passing a block to the `import` method. The collection will
185
+ # be available in the block argument.
186
+ #
187
+ import articles do |documents|
188
+
189
+ # We will capitalize every _title_ and return the manipulated collection
190
+ # back to the `import` method.
191
+ #
192
+ documents.map { |document| document.update(:title => document[:title].capitalize) }
193
+ end
194
+
195
+ refresh
196
+ end
197
+
198
+ ### Searching
199
+
200
+ # With the documents indexed and stored in the _ElasticSearch_ database, we can search them, finally.
201
+ #
202
+ # _Tire_ exposes the search interface via simple domain-specific language.
203
+
204
+ #### Simple Query String Searches
205
+
206
+ # We can do simple searches, like searching for articles containing “One” in their title.
207
+ #
208
+ s = Tire.search('articles') do
209
+ query do
210
+ string "title:one"
211
+ end
212
+ end
213
+
214
+ # The results:
215
+ # * One [tags: ruby]
216
+ #
217
+ s.results.each do |document|
218
+ puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
219
+ end
220
+
221
+ # Or, we can search for articles published between January, 1st and January, 2nd.
222
+ #
223
+ s = Tire.search('articles') do
224
+ query do
225
+ string "published_on:[2011-01-01 TO 2011-01-02]"
226
+ end
227
+ end
228
+
229
+ # The results:
230
+ # * One [published: 2011-01-01]
231
+ # * Two [published: 2011-01-02]
232
+ # * Three [published: 2011-01-02]
233
+ #
234
+ s.results.each do |document|
235
+ puts "* #{ document.title } [published: #{document.published_on}]"
236
+ end
237
+
238
+ # Notice, that we can access local variables from the _enclosing scope_.
239
+ # (Of course, we may write the blocks in shorter notation.)
240
+
241
+ # We will define the query in a local variable named `q`...
242
+ #
243
+ q = "title:T*"
244
+ # ... and we can use it inside the `query` block.
245
+ #
246
+ s = Tire.search('articles') { query { string q } }
247
+
248
+ # The results:
249
+ # * Two [tags: ruby, python]
250
+ # * Three [tags: java]
251
+ #
252
+ s.results.each do |document|
253
+ puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
254
+ end
255
+
256
+ # Often, we need to access variables or methods defined in the _outer scope_.
257
+ # To do that, we have to use a slight variation of the DSL.
258
+ #
259
+
260
+ # Let's assume we have a plain Ruby class, named `Article`.
261
+ #
262
+ class Article
263
+
264
+ # We will define the query in a class method...
265
+ #
266
+ def self.q
267
+ "title:T*"
268
+ end
269
+
270
+ # ... and wrap the _Tire_ search method in another one.
271
+ def self.search
272
+
273
+ # Notice how we pass the `search` object around as a block argument.
274
+ #
275
+ Tire.search('articles') do |search|
276
+
277
+ # And we pass the query object in a similar matter.
278
+ #
279
+ search.query do |query|
280
+
281
+ # Which means we can access the `q` class method.
282
+ #
283
+ query.string self.q
284
+ end
285
+ end.results
286
+ end
287
+ end
288
+
289
+ # We may use any valid [Lucene query syntax](http://lucene.apache.org/java/3_0_3/queryparsersyntax.html)
290
+ # for the `query_string` queries.
291
+
292
+ # For debugging our queries, we can display the JSON which is being sent to _ElasticSearch_.
293
+ #
294
+ # {"query":{"query_string":{"query":"title:T*"}}}
295
+ #
296
+ puts "", "Query:", "-"*80
297
+ puts s.to_json
298
+
299
+ # Or better yet, we may display a complete `curl` command to recreate the request in terminal,
300
+ # so we can see the naked response, tweak request parameters and meditate on problems.
301
+ #
302
+ # curl -X POST "http://localhost:9200/articles/_search?pretty=true" \
303
+ # -d '{"query":{"query_string":{"query":"title:T*"}}}'
304
+ #
305
+ puts "", "Try the query in Curl:", "-"*80
306
+ puts s.to_curl
307
+
308
+
309
+ ### Logging
310
+
311
+ # For debugging more complex situations, we can enable logging, so requests and responses
312
+ # will be logged using this `curl`-friendly format.
313
+
314
+ Tire.configure do
315
+
316
+ # By default, at the _info_ level, only the `curl`-format of request and
317
+ # basic information about the response will be logged:
318
+ #
319
+ # # 2011-04-24 11:34:01:150 [CREATE] ("articles")
320
+ # #
321
+ # curl -X POST "http://localhost:9200/articles"
322
+ #
323
+ # # 2011-04-24 11:34:01:152 [200]
324
+ #
325
+ logger 'elasticsearch.log'
326
+
327
+ # For debugging, we can switch to the _debug_ level, which will log the complete JSON responses.
328
+ #
329
+ # That's very convenient if we want to post a recreation of some problem or solution
330
+ # to the mailing list, IRC channel, etc.
331
+ #
332
+ logger 'elasticsearch.log', :level => 'debug'
333
+
334
+ # Note that we can pass any [`IO`](http://www.ruby-doc.org/core/classes/IO.html)-compatible Ruby object as a logging device.
335
+ #
336
+ logger STDERR
337
+ end
338
+
339
+ ### Configuration
340
+
341
+ # As we have just seen with logging, we can configure various parts of _Tire_.
342
+ #
343
+ Tire.configure do
344
+
345
+ # First of all, we can configure the URL for _ElasticSearch_.
346
+ #
347
+ url "http://search.example.com"
348
+
349
+ # Second, we may want to wrap the result items in our own class, for instance
350
+ # the `Article` class set above.
351
+ #
352
+ wrapper Article
353
+
354
+ # Finally, we can reset one or all configuration settings to their defaults.
355
+ #
356
+ reset :url
357
+ reset
358
+
359
+ end
360
+
361
+
362
+ ### Complex Searching
363
+
364
+ # Query strings are convenient for simple searches, but we may want to define our queries more expressively,
365
+ # using the _ElasticSearch_ [Query DSL](http://www.elasticsearch.org/guide/reference/query-dsl/index.html).
366
+ #
367
+ s = Tire.search('articles') do
368
+
369
+ # Let's suppose we want to search for articles with specific _tags_, in our case “ruby” _or_ “python”.
370
+ #
371
+ query do
372
+
373
+ # That's a great excuse to use a [_terms_](http://elasticsearch.org/guide/reference/query-dsl/terms-query.html)
374
+ # query.
375
+ #
376
+ terms :tags, ['ruby', 'python']
377
+ end
378
+ end
379
+
380
+ # The search, as expected, returns three articles, all tagged “ruby” — among other tags:
381
+ #
382
+ # * Two [tags: ruby, python]
383
+ # * One [tags: ruby]
384
+ # * Four [tags: ruby, php]
385
+ #
386
+ s.results.each do |document|
387
+ puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
388
+ end
389
+
390
+ # What if we wanted to search for articles tagged both “ruby” _and_ “python”?
391
+ #
392
+ s = Tire.search('articles') do
393
+ query do
394
+
395
+ # That's a great excuse to specify `minimum_match` for the query.
396
+ #
397
+ terms :tags, ['ruby', 'python'], :minimum_match => 2
398
+ end
399
+ end
400
+
401
+ # The search, as expected, returns one article, tagged with _both_ “ruby” and “python”:
402
+ #
403
+ # * Two [tags: ruby, python]
404
+ #
405
+ s.results.each do |document|
406
+ puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
407
+ end
408
+
409
+ #### Boolean Queries
410
+
411
+ # Quite often, we need complex queries with boolean logic.
412
+ # Instead of composing long query strings such as `tags:ruby OR tags:java AND NOT tags:python`,
413
+ # we can use the [_bool_](http://www.elasticsearch.org/guide/reference/query-dsl/bool-query.html)
414
+ # query.
415
+
416
+ s = Tire.search('articles') do
417
+ query do
418
+
419
+ # In _Tire_, we can build `bool` queries declaratively, as usual.
420
+ boolean do
421
+
422
+ # Let's define a `should` (`OR`) query for _ruby_,
423
+ #
424
+ should { string 'tags:ruby' }
425
+
426
+ # as well as for _java_,
427
+ should { string 'tags:java' }
428
+
429
+ # while defining a `must_not` (`AND NOT`) query for _python_.
430
+ must_not { string 'tags:python' }
431
+ end
432
+ end
433
+ end
434
+
435
+ # The search returns these documents:
436
+ #
437
+ # * One [tags: ruby]
438
+ # * Three [tags: java]
439
+ # * Four [tags: ruby, php]
440
+ #
441
+ s.results.each do |document|
442
+ puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
443
+ end
444
+
445
+ # The best thing about `boolean` queries is that we can very easily save these partial queries as Ruby blocks,
446
+ # to mix and reuse them later, since we can call the `boolean` method multiple times.
447
+ #
448
+
449
+ # Let's define the query for the _tags_ property,
450
+ #
451
+ tags_query = lambda do |boolean|
452
+ boolean.should { string 'tags:ruby' }
453
+ boolean.should { string 'tags:java' }
454
+ end
455
+
456
+ # ... and a query for the _published_on_ property.
457
+ published_on_query = lambda do |boolean|
458
+ boolean.must { string 'published_on:[2011-01-01 TO 2011-01-02]' }
459
+ end
460
+
461
+ # Now, we can use the `tags_query` on its own.
462
+ #
463
+ Tire.search('articles') { query { boolean &tags_query } }
464
+
465
+ # Or, we can combine it with the `published_on` query.
466
+ #
467
+ Tire.search('articles') do
468
+ query do
469
+ boolean &tags_query
470
+ boolean &published_on_query
471
+ end
472
+ end
473
+
474
+ # _ElasticSearch_ supports many types of [queries](http://www.elasticsearch.org/guide/reference/query-dsl/).
475
+ #
476
+ # Eventually, _Tire_ will support all of them. So far, only these are supported:
477
+ #
478
+ # * [string](http://www.elasticsearch.org/guide/reference/query-dsl/query-string-query.html)
479
+ # * [text](http://www.elasticsearch.org/guide/reference/query-dsl/text-query.html)
480
+ # * [term](http://elasticsearch.org/guide/reference/query-dsl/term-query.html)
481
+ # * [terms](http://elasticsearch.org/guide/reference/query-dsl/terms-query.html)
482
+ # * [bool](http://www.elasticsearch.org/guide/reference/query-dsl/bool-query.html)
483
+ # * [custom_score](http://www.elasticsearch.org/guide/reference/query-dsl/custom-score-query.html)
484
+ # * [fuzzy](http://www.elasticsearch.org/guide/reference/query-dsl/fuzzy-query.html)
485
+ # * [all](http://www.elasticsearch.org/guide/reference/query-dsl/match-all-query.html)
486
+ # * [ids](http://www.elasticsearch.org/guide/reference/query-dsl/ids-query.html)
487
+
488
+ #### Faceted Search
489
+
490
+ # _ElasticSearch_ makes it trivial to retrieve complex aggregated data from our index/database,
491
+ # so called [_facets_](http://www.elasticsearch.org/guide/reference/api/search/facets/index.html).
492
+
493
+ # Let's say we want to display article counts for every tag in the database.
494
+ # For that, we'll use a _terms_ facet.
495
+
496
+ #
497
+ s = Tire.search 'articles' do
498
+
499
+ # We will search for articles whose title begins with letter “T”,
500
+ #
501
+ query { string 'title:T*' }
502
+
503
+ # and retrieve the counts “bucketed” by `tags`.
504
+ #
505
+ facet 'tags' do
506
+ terms :tags
507
+ end
508
+ end
509
+
510
+ # As we see, our query has found two articles, and if you recall our articles from above,
511
+ # _Two_ is tagged with “ruby” and “python”, while _Three_ is tagged with “java”.
512
+ #
513
+ # Found 2 articles: Three, Two
514
+ #
515
+ # The counts shouldn't surprise us:
516
+ #
517
+ # Counts by tag:
518
+ # -------------------------
519
+ # ruby 1
520
+ # python 1
521
+ # java 1
522
+ #
523
+ puts "Found #{s.results.count} articles: #{s.results.map(&:title).join(', ')}"
524
+ puts "Counts by tag:", "-"*25
525
+ s.results.facets['tags']['terms'].each do |f|
526
+ puts "#{f['term'].ljust(10)} #{f['count']}"
527
+ end
528
+
529
+ # These counts are based on the scope of our current query.
530
+ # What if we wanted to display aggregated counts by `tags` across the whole database?
531
+
532
+ #
533
+ s = Tire.search 'articles' do
534
+
535
+ # Let's repeat the search for “T”...
536
+ #
537
+ query { string 'title:T*' }
538
+
539
+ facet 'global-tags', :global => true do
540
+
541
+ # ...but set the `global` scope for the facet in this case.
542
+ #
543
+ terms :tags
544
+ end
545
+
546
+ # We can even _combine_ facets scoped to the current query
547
+ # with globally scoped facets — we'll just use a different name.
548
+ #
549
+ facet 'current-tags' do
550
+ terms :tags
551
+ end
552
+ end
553
+
554
+ # Aggregated results for the current query are the same as previously:
555
+ #
556
+ # Current query facets:
557
+ # -------------------------
558
+ # ruby 1
559
+ # python 1
560
+ # java 1
561
+ #
562
+ puts "Current query facets:", "-"*25
563
+ s.results.facets['current-tags']['terms'].each do |f|
564
+ puts "#{f['term'].ljust(10)} #{f['count']}"
565
+ end
566
+
567
+ # On the other hand, aggregated results for the global scope include also
568
+ # tags for articles not matched by the query, such as “java” or “php”:
569
+ #
570
+ # Global facets:
571
+ # -------------------------
572
+ # ruby 3
573
+ # python 1
574
+ # php 1
575
+ # java 1
576
+ #
577
+ puts "Global facets:", "-"*25
578
+ s.results.facets['global-tags']['terms'].each do |f|
579
+ puts "#{f['term'].ljust(10)} #{f['count']}"
580
+ end
581
+
582
+ # _ElasticSearch_ supports many advanced types of facets, such as those for computing statistics or geographical distance.
583
+ #
584
+ # Eventually, _Tire_ will support all of them. So far, only these are supported:
585
+ #
586
+ # * [terms](http://www.elasticsearch.org/guide/reference/api/search/facets/terms-facet.html)
587
+ # * [date](http://www.elasticsearch.org/guide/reference/api/search/facets/date-histogram-facet.html)
588
+ # * [range](http://www.elasticsearch.org/guide/reference/api/search/facets/range-facet.html)
589
+ # * [histogram](http://www.elasticsearch.org/guide/reference/api/search/facets/histogram-facet.html)
590
+ # * [statistical](http://www.elasticsearch.org/guide/reference/api/search/facets/statistical-facet.html)
591
+ # * [terms_stats](http://www.elasticsearch.org/guide/reference/api/search/facets/terms-stats-facet.html)
592
+ # * [query](http://www.elasticsearch.org/guide/reference/api/search/facets/query-facet.html)
593
+
594
+ # We have seen that _ElasticSearch_ facets enable us to fetch complex aggregations from our data.
595
+ #
596
+ # They are frequently used for another feature, „faceted navigation“.
597
+ # We can be combine query and facets with
598
+ # [filters](http://elasticsearch.org/guide/reference/api/search/filter.html),
599
+ # so the returned documents are restricted by certain criteria — for example to a specific category —,
600
+ # but the aggregation calculations are still based on the original query.
601
+
602
+
603
+ #### Filtered Search
604
+
605
+ # So, let's make our search a bit more complex. Let's search for articles whose titles begin
606
+ # with letter “T”, again, but filter the results, so only the articles tagged “ruby”
607
+ # are returned.
608
+ #
609
+ s = Tire.search 'articles' do
610
+
611
+ # We will use just the same **query** as before.
612
+ #
613
+ query { string 'title:T*' }
614
+
615
+ # But we will add a _terms_ **filter** based on tags.
616
+ #
617
+ filter :terms, :tags => ['ruby']
618
+
619
+ # And, of course, our facet definition.
620
+ #
621
+ facet('tags') { terms :tags }
622
+
623
+ end
624
+
625
+ # We see that only the article _Two_ (tagged “ruby” and “python”) is returned,
626
+ # _not_ the article _Three_ (tagged “java”):
627
+ #
628
+ # * Two [tags: ruby, python]
629
+ #
630
+ s.results.each do |document|
631
+ puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
632
+ end
633
+
634
+ # The _count_ for article _Three_'s tags, “java”, on the other hand, _is_ in fact included:
635
+ #
636
+ # Counts by tag:
637
+ # -------------------------
638
+ # ruby 1
639
+ # python 1
640
+ # java 1
641
+ #
642
+ puts "Counts by tag:", "-"*25
643
+ s.results.facets['tags']['terms'].each do |f|
644
+ puts "#{f['term'].ljust(10)} #{f['count']}"
645
+ end
646
+
647
+ #### Sorting
648
+
649
+ # By default, the results are sorted according to their relevancy.
650
+ #
651
+ s = Tire.search('articles') { query { string 'tags:ruby' } }
652
+
653
+ s.results.each do |document|
654
+ puts "* #{ document.title } " +
655
+ "[tags: #{document.tags.join(', ')}; " +
656
+
657
+ # The score is available as the `_score` property.
658
+ #
659
+ "score: #{document._score}]"
660
+ end
661
+
662
+ # The results:
663
+ #
664
+ # * One [tags: ruby; score: 0.30685282]
665
+ # * Four [tags: ruby, php; score: 0.19178301]
666
+ # * Two [tags: ruby, python; score: 0.19178301]
667
+
668
+ # But, what if we want to sort the results based on some other criteria,
669
+ # such as published date or product price? We can do that.
670
+ #
671
+ s = Tire.search 'articles' do
672
+
673
+ # We will search for articles tagged “ruby”, again, ...
674
+ #
675
+ query { string 'tags:ruby' }
676
+
677
+ # ... but will sort them by their `title`, in descending order.
678
+ #
679
+ sort { by :title, 'desc' }
680
+ end
681
+
682
+ # The results:
683
+ #
684
+ # * Two
685
+ # * One
686
+ # * Four
687
+ #
688
+ s.results.each do |document|
689
+ puts "* #{ document.title }"
690
+ end
691
+
692
+ # Of course, it's possible to combine more fields in the sorting definition.
693
+
694
+ s = Tire.search 'articles' do
695
+
696
+ # We will just get all articles in this case.
697
+ #
698
+ query { all }
699
+
700
+ sort do
701
+
702
+ # We will sort the results by their `published_on` property in _ascending_ order (the default),
703
+ #
704
+ by :published_on
705
+
706
+ # and by their `title` property, in _descending_ order.
707
+ #
708
+ by :title, 'desc'
709
+ end
710
+ end
711
+
712
+ # The results:
713
+ # * One (Published on: 2011-01-01)
714
+ # * Two (Published on: 2011-01-02)
715
+ # * Three (Published on: 2011-01-02)
716
+ # * Four (Published on: 2011-01-03)
717
+ #
718
+ s.results.each do |document|
719
+ puts "* #{ document.title.ljust(10) } (Published on: #{ document.published_on })"
720
+ end
721
+
722
+ #### Highlighting
723
+
724
+ # Often, we want to highlight the snippets matching our query in the displayed results.
725
+ # _ElasticSearch_ provides rich
726
+ # [highlighting](http://www.elasticsearch.org/guide/reference/api/search/highlighting.html)
727
+ # features, and _Tire_ makes them trivial to use.
728
+ #
729
+ s = Tire.search 'articles' do
730
+
731
+ # Let's search for documents containing word “Two” in their titles,
732
+ query { string 'title:Two' }
733
+
734
+ # and instruct _ElasticSearch_ to highlight relevant snippets.
735
+ #
736
+ highlight :title
737
+ end
738
+
739
+ # The results:
740
+ # Title: Two; Highlighted: <em>Two</em>
741
+ #
742
+ s.results.each do |document|
743
+ puts "Title: #{ document.title }; Highlighted: #{document.highlight.title}"
744
+ end
745
+
746
+ # We can configure many options for highlighting, such as:
747
+ #
748
+ s = Tire.search 'articles' do
749
+ query { string 'title:Two' }
750
+
751
+ # • specify the fields to highlight
752
+ #
753
+ highlight :title, :body
754
+
755
+ # • specify their individual options
756
+ #
757
+ highlight :title, :body => { :number_of_fragments => 0 }
758
+
759
+ # • or specify global highlighting options, such as the wrapper tag
760
+ #
761
+ highlight :title, :body, :options => { :tag => '<strong class="highlight">' }
762
+ end
763
+
764
+ #### Percolation
765
+
766
+ # _ElasticSearch_ comes with one very interesting, and rather unique feature:
767
+ # [_percolation_](http://www.elasticsearch.org/guide/reference/api/percolate.html).
768
+
769
+ # It works in a „reverse search“ manner to regular search workflow of adding
770
+ # documents to the index and then querying them.
771
+ # Percolation allows us to register a query, and ask if a specific document
772
+ # matches it, either on demand, or immediately as the document is being indexed.
773
+
774
+ # Let's review an example for an index named _weather_.
775
+ # We will register three queries for percolation against this index.
776
+ #
777
+ index = Tire.index('weather') do
778
+ delete
779
+ create
780
+
781
+ # First, a query named _warning_,
782
+ #
783
+ register_percolator_query('warning', :tags => ['warning']) { string 'warning OR severe OR extreme' }
784
+
785
+ # a query named _tsunami_,
786
+ #
787
+ register_percolator_query('tsunami', :tags => ['tsunami']) { string 'tsunami' }
788
+
789
+ # and a query named _floods_.
790
+ #
791
+ register_percolator_query('floods', :tags => ['floods']) { string 'flood*' }
792
+
793
+ end
794
+
795
+ # Notice, that we have added a _tags_ field to the query document, because it behaves
796
+ # just like any other document in _ElasticSearch_.
797
+
798
+ # We will refresh the `_percolator` index for immediate access.
799
+ #
800
+ Tire.index('_percolator').refresh
801
+
802
+ # Now, let's _percolate_ a document containing some trigger words against all registered queries.
803
+ #
804
+ matches = index.percolate(:message => '[Warning] Extreme flooding expected after tsunami wave.')
805
+
806
+ # The result will contain, unsurprisingly, names of all the three registered queries:
807
+ #
808
+ # Matching queries: ["floods", "tsunami", "warning"]
809
+ #
810
+ puts "Matching queries: " + matches.inspect
811
+
812
+ # We can filter the executed queries with a regular _ElasticSearch_ query passed as a block to
813
+ # the `percolate` method.
814
+ #
815
+ matches = index.percolate(:message => '[Warning] Extreme flooding expected after tsunami wave.') do
816
+ # Let's use a _terms_ query against the `tags` field.
817
+ term :tags, 'tsunami'
818
+ end
819
+
820
+ # In this case, the result will contain only the name of the “tsunami” query.
821
+ #
822
+ # Matching queries: ["tsunami"]
823
+ #
824
+ puts "Matching queries: " + matches.inspect
825
+
826
+ # What if we percolate another document, without the “tsunami” trigger word?
827
+ #
828
+ matches = index.percolate(:message => '[Warning] Extreme temperatures expected.') { term :tags, 'tsunami' }
829
+
830
+ # As expected, we will get an empty array:
831
+ #
832
+ # Matching queries: []
833
+ #
834
+ puts "Matching queries: " + matches.inspect
835
+
836
+ # Well, that's of course immensely useful for real-time search systems. But, there's more.
837
+ # We can _percolate_ a document _at the same time_ it is being stored in the index,
838
+ # getting back a list of matching queries.
839
+
840
+ # Let's store a document with some trigger words in the index, and mark it for percolation.
841
+ #
842
+ response = index.store :message => '[Warning] Severe floods expected after tsunami wave.', :percolate => true
843
+
844
+ # We will get the names of all matching queries in response.
845
+ #
846
+ # Matching queries: ["floods", "tsunami", "warning"]
847
+ #
848
+ puts "Matching queries: " + response['matches'].inspect
849
+
850
+ # As with the _percolate_ example, we can filter the executed queries.
851
+ #
852
+ response = index.store :message => '[Warning] Severe floods expected after tsunami wave.',
853
+ # Let's use a simple string query for the “tsunami” tag.
854
+ :percolate => 'tags:tsunami'
855
+
856
+ # Unsurprisingly, the response will contain just the name of the “tsunami” query.
857
+ #
858
+ # Matching queries: ["tsunami"]
859
+ #
860
+ puts "Matching queries: " + response['matches'].inspect
861
+
862
+ ### ActiveModel Integration
863
+
864
+ # As you can see, [_Tire_](https://github.com/karmi/tire) supports the
865
+ # main features of _ElasticSearch_ in Ruby.
866
+ #
867
+ # It allows you to create and delete indices, add documents, search them, retrieve the facets, highlight the results,
868
+ # and comes with a usable logging facility.
869
+ #
870
+ # Of course, the holy grail of any search library is easy, painless integration with your Ruby classes, and,
871
+ # most importantly, with ActiveRecord/ActiveModel classes.
872
+ #
873
+ # Please, check out the [README](https://github.com/karmi/tire/tree/master#readme) file for instructions
874
+ # how to include _Tire_-based search in your models..
875
+ #
876
+ # Send any feedback via Github issues, or ask questions in the [#elasticsearch](irc://irc.freenode.net/#elasticsearch) IRC channel.