elasticsearch-model 0.1.9 → 7.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -2
  3. data/Gemfile +22 -0
  4. data/LICENSE.txt +199 -10
  5. data/README.md +96 -43
  6. data/Rakefile +49 -35
  7. data/elasticsearch-model.gemspec +53 -41
  8. data/examples/activerecord_article.rb +18 -1
  9. data/examples/activerecord_associations.rb +86 -17
  10. data/examples/activerecord_custom_analyzer.rb +152 -0
  11. data/examples/activerecord_mapping_completion.rb +33 -16
  12. data/examples/activerecord_mapping_edge_ngram.rb +118 -0
  13. data/examples/couchbase_article.rb +17 -0
  14. data/examples/datamapper_article.rb +28 -1
  15. data/examples/mongoid_article.rb +17 -0
  16. data/examples/ohm_article.rb +17 -0
  17. data/examples/riak_article.rb +17 -0
  18. data/gemfiles/3.0.gemfile +23 -1
  19. data/gemfiles/4.0.gemfile +25 -1
  20. data/gemfiles/5.0.gemfile +35 -0
  21. data/gemfiles/6.0.gemfile +36 -0
  22. data/lib/elasticsearch/model/adapter.rb +17 -0
  23. data/lib/elasticsearch/model/adapters/active_record.rb +31 -27
  24. data/lib/elasticsearch/model/adapters/default.rb +17 -0
  25. data/lib/elasticsearch/model/adapters/mongoid.rb +27 -3
  26. data/lib/elasticsearch/model/adapters/multiple.rb +29 -4
  27. data/lib/elasticsearch/model/callbacks.rb +17 -0
  28. data/lib/elasticsearch/model/client.rb +17 -0
  29. data/lib/elasticsearch/model/ext/active_record.rb +17 -0
  30. data/lib/elasticsearch/model/hash_wrapper.rb +32 -0
  31. data/lib/elasticsearch/model/importing.rb +61 -17
  32. data/lib/elasticsearch/model/indexing.rb +87 -49
  33. data/lib/elasticsearch/model/multimodel.rb +17 -0
  34. data/lib/elasticsearch/model/naming.rb +33 -2
  35. data/lib/elasticsearch/model/proxy.rb +61 -18
  36. data/lib/elasticsearch/model/response/aggregations.rb +55 -0
  37. data/lib/elasticsearch/model/response/base.rb +25 -3
  38. data/lib/elasticsearch/model/response/pagination/kaminari.rb +126 -0
  39. data/lib/elasticsearch/model/response/pagination/will_paginate.rb +112 -0
  40. data/lib/elasticsearch/model/response/pagination.rb +19 -192
  41. data/lib/elasticsearch/model/response/records.rb +17 -1
  42. data/lib/elasticsearch/model/response/result.rb +19 -2
  43. data/lib/elasticsearch/model/response/results.rb +17 -0
  44. data/lib/elasticsearch/model/response/suggestions.rb +20 -1
  45. data/lib/elasticsearch/model/response.rb +28 -10
  46. data/lib/elasticsearch/model/searching.rb +17 -0
  47. data/lib/elasticsearch/model/serializing.rb +17 -0
  48. data/lib/elasticsearch/model/version.rb +18 -1
  49. data/lib/elasticsearch/model.rb +36 -39
  50. data/spec/elasticsearch/model/adapter_spec.rb +136 -0
  51. data/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +351 -0
  52. data/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +395 -0
  53. data/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +35 -0
  54. data/spec/elasticsearch/model/adapters/active_record/import_spec.rb +193 -0
  55. data/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +127 -0
  56. data/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +55 -0
  57. data/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +332 -0
  58. data/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +92 -0
  59. data/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +78 -0
  60. data/spec/elasticsearch/model/adapters/active_record_spec.rb +224 -0
  61. data/spec/elasticsearch/model/adapters/default_spec.rb +58 -0
  62. data/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +284 -0
  63. data/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +83 -0
  64. data/spec/elasticsearch/model/adapters/mongoid_spec.rb +252 -0
  65. data/spec/elasticsearch/model/adapters/multiple_spec.rb +142 -0
  66. data/spec/elasticsearch/model/callbacks_spec.rb +50 -0
  67. data/spec/elasticsearch/model/client_spec.rb +83 -0
  68. data/spec/elasticsearch/model/hash_wrapper_spec.rb +29 -0
  69. data/spec/elasticsearch/model/importing_spec.rb +243 -0
  70. data/spec/elasticsearch/model/indexing_spec.rb +1014 -0
  71. data/spec/elasticsearch/model/module_spec.rb +94 -0
  72. data/spec/elasticsearch/model/multimodel_spec.rb +72 -0
  73. data/spec/elasticsearch/model/naming_spec.rb +203 -0
  74. data/spec/elasticsearch/model/proxy_spec.rb +124 -0
  75. data/spec/elasticsearch/model/response/aggregations_spec.rb +83 -0
  76. data/spec/elasticsearch/model/response/base_spec.rb +107 -0
  77. data/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +472 -0
  78. data/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +279 -0
  79. data/spec/elasticsearch/model/response/records_spec.rb +135 -0
  80. data/spec/elasticsearch/model/response/response_spec.rb +148 -0
  81. data/spec/elasticsearch/model/response/result_spec.rb +139 -0
  82. data/spec/elasticsearch/model/response/results_spec.rb +73 -0
  83. data/spec/elasticsearch/model/searching_search_request_spec.rb +129 -0
  84. data/spec/elasticsearch/model/searching_spec.rb +66 -0
  85. data/spec/elasticsearch/model/serializing_spec.rb +39 -0
  86. data/spec/spec_helper.rb +193 -0
  87. data/spec/support/app/answer.rb +50 -0
  88. data/spec/support/app/article.rb +39 -0
  89. data/spec/support/app/article_for_pagination.rb +29 -0
  90. data/spec/support/app/article_no_type.rb +37 -0
  91. data/spec/support/app/article_with_custom_serialization.rb +30 -0
  92. data/spec/support/app/article_with_dynamic_index_name.rb +32 -0
  93. data/spec/support/app/author.rb +26 -0
  94. data/spec/support/app/authorship.rb +21 -0
  95. data/spec/support/app/category.rb +20 -0
  96. data/spec/support/app/comment.rb +20 -0
  97. data/spec/support/app/episode.rb +28 -0
  98. data/spec/support/app/image.rb +36 -0
  99. data/spec/support/app/import_article.rb +29 -0
  100. data/spec/support/app/mongoid_article.rb +38 -0
  101. data/spec/support/app/namespaced_book.rb +27 -0
  102. data/spec/support/app/parent_and_child_searchable.rb +41 -0
  103. data/spec/support/app/post.rb +31 -0
  104. data/spec/support/app/question.rb +44 -0
  105. data/spec/support/app/searchable.rb +65 -0
  106. data/spec/support/app/series.rb +28 -0
  107. data/spec/support/app.rb +46 -0
  108. data/spec/support/model.json +1 -0
  109. data/{test → spec}/support/model.yml +0 -0
  110. metadata +175 -121
  111. data/test/integration/active_record_associations_parent_child.rb +0 -139
  112. data/test/integration/active_record_associations_test.rb +0 -326
  113. data/test/integration/active_record_basic_test.rb +0 -234
  114. data/test/integration/active_record_custom_serialization_test.rb +0 -62
  115. data/test/integration/active_record_import_test.rb +0 -109
  116. data/test/integration/active_record_namespaced_model_test.rb +0 -49
  117. data/test/integration/active_record_pagination_test.rb +0 -145
  118. data/test/integration/dynamic_index_name_test.rb +0 -47
  119. data/test/integration/mongoid_basic_test.rb +0 -177
  120. data/test/integration/multiple_models_test.rb +0 -172
  121. data/test/support/model.json +0 -1
  122. data/test/test_helper.rb +0 -93
  123. data/test/unit/adapter_active_record_test.rb +0 -157
  124. data/test/unit/adapter_default_test.rb +0 -41
  125. data/test/unit/adapter_mongoid_test.rb +0 -104
  126. data/test/unit/adapter_multiple_test.rb +0 -106
  127. data/test/unit/adapter_test.rb +0 -69
  128. data/test/unit/callbacks_test.rb +0 -31
  129. data/test/unit/client_test.rb +0 -27
  130. data/test/unit/importing_test.rb +0 -203
  131. data/test/unit/indexing_test.rb +0 -650
  132. data/test/unit/module_test.rb +0 -57
  133. data/test/unit/multimodel_test.rb +0 -38
  134. data/test/unit/naming_test.rb +0 -103
  135. data/test/unit/proxy_test.rb +0 -100
  136. data/test/unit/response_base_test.rb +0 -40
  137. data/test/unit/response_pagination_kaminari_test.rb +0 -433
  138. data/test/unit/response_pagination_will_paginate_test.rb +0 -398
  139. data/test/unit/response_records_test.rb +0 -91
  140. data/test/unit/response_result_test.rb +0 -90
  141. data/test/unit/response_results_test.rb +0 -31
  142. data/test/unit/response_test.rb +0 -104
  143. data/test/unit/searching_search_request_test.rb +0 -78
  144. data/test/unit/searching_test.rb +0 -41
  145. data/test/unit/serializing_test.rb +0 -17
@@ -1,57 +1,69 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
1
18
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
19
+
20
+ lib = File.expand_path('lib', __dir__)
3
21
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
22
  require 'elasticsearch/model/version'
5
23
 
6
24
  Gem::Specification.new do |s|
7
- s.name = "elasticsearch-model"
25
+ s.name = 'elasticsearch-model'
8
26
  s.version = Elasticsearch::Model::VERSION
9
- s.authors = ["Karel Minarik"]
10
- s.email = ["karel.minarik@elasticsearch.org"]
11
- s.description = "ActiveModel/Record integrations for Elasticsearch."
12
- s.summary = "ActiveModel/Record integrations for Elasticsearch."
13
- s.homepage = "https://github.com/elasticsearch/elasticsearch-rails/"
14
- s.license = "Apache 2"
27
+ s.authors = ['Karel Minarik']
28
+ s.email = ['karel.minarik@elasticsearch.org']
29
+ s.description = 'ActiveModel/Record integrations for Elasticsearch.'
30
+ s.summary = 'ActiveModel/Record integrations for Elasticsearch.'
31
+ s.homepage = 'https://github.com/elasticsearch/elasticsearch-rails/'
32
+ s.license = 'Apache 2'
15
33
 
16
34
  s.files = `git ls-files`.split($/)
17
35
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
36
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
19
- s.require_paths = ["lib"]
20
-
21
- s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ]
22
- s.rdoc_options = [ "--charset=UTF-8" ]
23
-
24
- s.required_ruby_version = ">= 1.9.3"
25
-
26
- s.add_dependency "elasticsearch", '> 0.4'
27
- s.add_dependency "activesupport", '> 3'
28
- s.add_dependency "hashie"
29
-
30
- s.add_development_dependency "bundler", "~> 1.3"
31
- s.add_development_dependency "rake", "< 11.0"
32
-
33
- s.add_development_dependency "elasticsearch-extensions"
37
+ s.require_paths = ['lib']
34
38
 
35
- s.add_development_dependency "sqlite3"
36
- s.add_development_dependency "activemodel", "> 3.0"
39
+ s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
40
+ s.rdoc_options = ['--charset=UTF-8']
37
41
 
38
- s.add_development_dependency "oj"
39
- s.add_development_dependency "kaminari"
40
- s.add_development_dependency "will_paginate"
42
+ s.required_ruby_version = '>= 2.4'
41
43
 
42
- s.add_development_dependency "minitest", "~> 4.2"
43
- s.add_development_dependency "test-unit" if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2'
44
- s.add_development_dependency "shoulda-context"
45
- s.add_development_dependency "mocha"
46
- s.add_development_dependency "turn"
47
- s.add_development_dependency "yard"
48
- s.add_development_dependency "ruby-prof"
49
- s.add_development_dependency "pry"
50
- s.add_development_dependency "ci_reporter", "~> 1.9"
44
+ s.add_dependency 'activesupport', '> 3'
45
+ s.add_dependency 'elasticsearch', '> 1'
46
+ s.add_dependency 'hashie'
51
47
 
52
- if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9'
53
- s.add_development_dependency "simplecov"
54
- s.add_development_dependency "cane"
55
- s.add_development_dependency "require-prof"
48
+ s.add_development_dependency 'activemodel', '> 3'
49
+ s.add_development_dependency 'bundler'
50
+ s.add_development_dependency 'cane'
51
+ s.add_development_dependency 'elasticsearch-extensions'
52
+ s.add_development_dependency 'kaminari'
53
+ s.add_development_dependency 'minitest'
54
+ s.add_development_dependency 'mocha'
55
+ s.add_development_dependency 'pry'
56
+ s.add_development_dependency 'rake', '~> 12'
57
+ s.add_development_dependency 'require-prof'
58
+ s.add_development_dependency 'shoulda-context'
59
+ s.add_development_dependency 'simplecov'
60
+ s.add_development_dependency 'test-unit'
61
+ s.add_development_dependency 'turn'
62
+ s.add_development_dependency 'will_paginate'
63
+ s.add_development_dependency 'yard'
64
+ unless defined?(JRUBY_VERSION)
65
+ s.add_development_dependency 'oj'
66
+ s.add_development_dependency 'ruby-prof'
67
+ s.add_development_dependency 'sqlite3'
56
68
  end
57
69
  end
@@ -1,3 +1,20 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
1
18
  # ActiveRecord and Elasticsearch
2
19
  # ==============================
3
20
  #
@@ -26,7 +43,7 @@ ActiveRecord::Schema.define(version: 1) do
26
43
  end
27
44
  end
28
45
 
29
- Kaminari::Hooks.init
46
+ Kaminari::Hooks.init if defined?(Kaminari::Hooks) if defined?(Kaminari::Hooks)
30
47
 
31
48
  class Article < ActiveRecord::Base
32
49
  end
@@ -1,3 +1,20 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
1
18
  # ActiveRecord associations and Elasticsearch
2
19
  # ===========================================
3
20
  #
@@ -12,12 +29,12 @@
12
29
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
13
30
 
14
31
  require 'pry'
15
- Pry.config.history.file = File.expand_path('../../tmp/elasticsearch_development.pry', __FILE__)
16
32
 
17
33
  require 'logger'
18
34
  require 'ansi/core'
19
35
  require 'active_record'
20
36
 
37
+ require 'json'
21
38
  require 'elasticsearch/model'
22
39
 
23
40
  ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
@@ -28,23 +45,24 @@ ActiveRecord::Base.establish_connection( adapter: 'sqlite3', database: ":memory:
28
45
  ActiveRecord::Schema.define(version: 1) do
29
46
  create_table :categories do |t|
30
47
  t.string :title
31
- t.timestamps
48
+ t.timestamps null: false
32
49
  end
33
50
 
34
51
  create_table :authors do |t|
35
52
  t.string :first_name, :last_name
36
- t.timestamps
53
+ t.string :department
54
+ t.timestamps null: false
37
55
  end
38
56
 
39
57
  create_table :authorships do |t|
40
58
  t.references :article
41
59
  t.references :author
42
- t.timestamps
60
+ t.timestamps null: false
43
61
  end
44
62
 
45
63
  create_table :articles do |t|
46
64
  t.string :title
47
- t.timestamps
65
+ t.timestamps null: false
48
66
  end
49
67
 
50
68
  create_table :articles_categories, id: false do |t|
@@ -54,15 +72,16 @@ ActiveRecord::Schema.define(version: 1) do
54
72
  create_table :comments do |t|
55
73
  t.string :text
56
74
  t.references :article
57
- t.timestamps
75
+ t.timestamps null: false
58
76
  end
59
- add_index(:comments, :article_id)
77
+
78
+ add_index(:comments, :article_id) unless index_exists?(:comments, :article_id)
60
79
  end
61
80
 
62
81
  # ----- Elasticsearch client setup ----------------------------------------------------------------
63
82
 
64
83
  Elasticsearch::Model.client = Elasticsearch::Client.new log: true
65
- Elasticsearch::Model.client.transport.logger.formatter = proc { |s, d, p, m| "\e[32m#{m}\n\e[0m" }
84
+ Elasticsearch::Model.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" }
66
85
 
67
86
  # ----- Search integration ------------------------------------------------------------------------
68
87
 
@@ -79,11 +98,27 @@ module Searchable
79
98
 
80
99
  module Indexing
81
100
 
101
+ #Index only the specified fields
102
+ settings do
103
+ mappings dynamic: false do
104
+ indexes :categories, type: :object do
105
+ indexes :title
106
+ end
107
+ indexes :authors, type: :object do
108
+ indexes :full_name
109
+ indexes :department
110
+ end
111
+ indexes :comments, type: :object do
112
+ indexes :text
113
+ end
114
+ end
115
+ end
116
+
82
117
  # Customize the JSON serialization for Elasticsearch
83
118
  def as_indexed_json(options={})
84
119
  self.as_json(
85
120
  include: { categories: { only: :title},
86
- authors: { methods: [:full_name], only: [:full_name] },
121
+ authors: { methods: [:full_name, :department], only: [:full_name, :department] },
87
122
  comments: { only: :text }
88
123
  })
89
124
  end
@@ -139,7 +174,7 @@ category = Category.create title: 'One'
139
174
 
140
175
  # Create author
141
176
  #
142
- author = Author.create first_name: 'John', last_name: 'Smith'
177
+ author = Author.create first_name: 'John', last_name: 'Smith', department: 'Business'
143
178
 
144
179
  # Create article
145
180
 
@@ -160,18 +195,52 @@ article.comments.create text: 'Second comment for article One'
160
195
 
161
196
  Elasticsearch::Model.client.indices.refresh index: Elasticsearch::Model::Registry.all.map(&:index_name)
162
197
 
163
- puts "\n\e[1mArticles containing 'one':\e[0m", Article.search('one').records.to_a.map(&:inspect), ""
198
+ # Search for a term and return records
199
+ #
200
+ puts "",
201
+ "Articles containing 'one':".ansi(:bold),
202
+ Article.search('one').records.to_a.map(&:inspect),
203
+ ""
164
204
 
165
- puts "\n\e[1mModels containing 'one':\e[0m", Elasticsearch::Model.search('one').records.to_a.map(&:inspect), ""
205
+ puts "",
206
+ "All Models containing 'one':".ansi(:bold),
207
+ Elasticsearch::Model.search('one').records.to_a.map(&:inspect),
208
+ ""
166
209
 
167
- # Load model
210
+ # Difference between `records` and `results`
168
211
  #
169
- article = Article.all.includes(:categories, :authors, :comments).first
212
+ response = Article.search query: { match: { title: 'first' } }
170
213
 
171
- # ----- Pry ---------------------------------------------------------------------------------------
214
+ puts "",
215
+ "Search results are wrapped in the <#{response.class}> class",
216
+ ""
172
217
 
173
- puts '', '-'*Pry::Terminal.width!
218
+ puts "",
219
+ "Access the <ActiveRecord> instances with the `#records` method:".ansi(:bold),
220
+ response.records.map { |r| "* #{r.title} | Authors: #{r.authors.map(&:full_name) } | Comment count: #{r.comments.size}" }.join("\n"),
221
+ ""
222
+
223
+ puts "",
224
+ "Access the Elasticsearch documents with the `#results` method (without touching the database):".ansi(:bold),
225
+ response.results.map { |r| "* #{r.title} | Authors: #{r.authors.map(&:full_name) } | Comment count: #{r.comments.size}" }.join("\n"),
226
+ ""
227
+
228
+ puts "",
229
+ "The whole indexed document (according to `Article#as_indexed_json`):".ansi(:bold),
230
+ JSON.pretty_generate(response.results.first._source.to_hash),
231
+ ""
232
+
233
+ # Retrieve only selected fields from Elasticsearch
234
+ #
235
+ response = Article.search query: { match: { title: 'first' } }, _source: ['title', 'authors.full_name']
236
+
237
+ puts "",
238
+ "Retrieve only selected fields from Elasticsearch:".ansi(:bold),
239
+ JSON.pretty_generate(response.results.first._source.to_hash),
240
+ ""
241
+
242
+ # ----- Pry ---------------------------------------------------------------------------------------
174
243
 
175
244
  Pry.start(binding, prompt: lambda { |obj, nest_level, _| '> ' },
176
- input: StringIO.new("article.as_indexed_json\n"),
245
+ input: StringIO.new('response.records.first'),
177
246
  quiet: true)
@@ -0,0 +1,152 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # Custom Analyzer for ActiveRecord integration with Elasticsearch
19
+ # ===============================================================
20
+
21
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
22
+
23
+ require 'ansi'
24
+ require 'logger'
25
+
26
+ require 'active_record'
27
+ require 'elasticsearch/model'
28
+
29
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
30
+ ActiveRecord::Base.establish_connection( adapter: 'sqlite3', database: ":memory:" )
31
+
32
+ ActiveRecord::Schema.define(version: 1) do
33
+ create_table :articles do |t|
34
+ t.string :title
35
+ t.date :published_at
36
+ t.timestamps
37
+ end
38
+ end
39
+
40
+ Elasticsearch::Model.client.transport.logger = ActiveSupport::Logger.new(STDOUT)
41
+ Elasticsearch::Model.client.transport.logger.formatter = lambda { |s, d, p, m| "#{m.ansi(:faint)}\n" }
42
+
43
+ class Article < ActiveRecord::Base
44
+ include Elasticsearch::Model
45
+
46
+ settings index: {
47
+ number_of_shards: 1,
48
+ number_of_replicas: 0,
49
+ analysis: {
50
+ analyzer: {
51
+ pattern: {
52
+ type: 'pattern',
53
+ pattern: "\\s|_|-|\\.",
54
+ lowercase: true
55
+ },
56
+ trigram: {
57
+ tokenizer: 'trigram'
58
+ }
59
+ },
60
+ tokenizer: {
61
+ trigram: {
62
+ type: 'ngram',
63
+ min_gram: 3,
64
+ max_gram: 3,
65
+ token_chars: ['letter', 'digit']
66
+ }
67
+ }
68
+ } } do
69
+ mapping do
70
+ indexes :title, type: 'text', analyzer: 'english' do
71
+ indexes :keyword, analyzer: 'keyword'
72
+ indexes :pattern, analyzer: 'pattern'
73
+ indexes :trigram, analyzer: 'trigram'
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ # Create example records
80
+ #
81
+ Article.delete_all
82
+ Article.create title: 'Foo'
83
+ Article.create title: 'Foo-Bar'
84
+ Article.create title: 'Foo_Bar_Bazooka'
85
+ Article.create title: 'Foo.Bar'
86
+
87
+ # Index records
88
+ #
89
+ errors = Article.import force: true, refresh: true, return: 'errors'
90
+ puts "[!] Errors importing records: #{errors.map { |d| d['index']['error'] }.join(', ')}".ansi(:red) && exit(1) unless errors.empty?
91
+
92
+ puts '', '-'*80
93
+
94
+ puts "English analyzer [Foo_Bar_1_Bazooka]".ansi(:bold),
95
+ "Tokens: " +
96
+ Article.__elasticsearch__.client.indices
97
+ .analyze(index: Article.index_name, body: { field: 'title', text: 'Foo_Bar_1_Bazooka' })['tokens']
98
+ .map { |d| "[#{d['token']}]" }.join(' '),
99
+ "\n"
100
+
101
+ puts "Keyword analyzer [Foo_Bar_1_Bazooka]".ansi(:bold),
102
+ "Tokens: " +
103
+ Article.__elasticsearch__.client.indices
104
+ .analyze(index: Article.index_name, body: { field: 'title.keyword', text: 'Foo_Bar_1_Bazooka' })['tokens']
105
+ .map { |d| "[#{d['token']}]" }.join(' '),
106
+ "\n"
107
+
108
+ puts "Pattern analyzer [Foo_Bar_1_Bazooka]".ansi(:bold),
109
+ "Tokens: " +
110
+ Article.__elasticsearch__.client.indices
111
+ .analyze(index: Article.index_name, body: { field: 'title.pattern', text: 'Foo_Bar_1_Bazooka' })['tokens']
112
+ .map { |d| "[#{d['token']}]" }.join(' '),
113
+ "\n"
114
+
115
+ puts "Trigram analyzer [Foo_Bar_1_Bazooka]".ansi(:bold),
116
+ "Tokens: " +
117
+ Article.__elasticsearch__.client.indices
118
+ .analyze(index: Article.index_name, body: { field: 'title.trigram', text: 'Foo_Bar_1_Bazooka' })['tokens']
119
+ .map { |d| "[#{d['token']}]" }.join(' '),
120
+ "\n"
121
+
122
+ puts '', '-'*80
123
+
124
+ response = Article.search query: { match: { 'title' => 'foo' } } ;
125
+
126
+ puts "English search for 'foo'".ansi(:bold),
127
+ "#{response.response.hits.total} matches: " +
128
+ response.records.map { |d| d.title }.join(', '),
129
+ "\n"
130
+
131
+ puts '', '-'*80
132
+
133
+ response = Article.search query: { match: { 'title.pattern' => 'foo' } } ;
134
+
135
+ puts "Pattern search for 'foo'".ansi(:bold),
136
+ "#{response.response.hits.total} matches: " +
137
+ response.records.map { |d| d.title }.join(', '),
138
+ "\n"
139
+
140
+ puts '', '-'*80
141
+
142
+ response = Article.search query: { match: { 'title.trigram' => 'zoo' } } ;
143
+
144
+ puts "Trigram search for 'zoo'".ansi(:bold),
145
+ "#{response.response.hits.total} matches: " +
146
+ response.records.map { |d| d.title }.join(', '),
147
+ "\n"
148
+
149
+ puts '', '-'*80
150
+
151
+
152
+ require 'pry'; binding.pry;
@@ -1,3 +1,20 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
1
18
  require 'ansi'
2
19
  require 'active_record'
3
20
  require 'elasticsearch/model'
@@ -18,17 +35,14 @@ class Article < ActiveRecord::Base
18
35
  include Elasticsearch::Model::Callbacks
19
36
 
20
37
  mapping do
21
- indexes :title
22
- indexes :title_suggest, type: 'completion', payloads: true
38
+ indexes :title, type: 'text' do
39
+ indexes :suggest, type: 'completion'
40
+ end
41
+ indexes :url, type: 'keyword'
23
42
  end
24
43
 
25
44
  def as_indexed_json(options={})
26
- as_json.merge \
27
- title_suggest: {
28
- input: title,
29
- output: title,
30
- payload: { url: "/articles/#{id}" }
31
- }
45
+ as_json.merge 'url' => "/articles/#{id}"
32
46
  end
33
47
  end
34
48
 
@@ -53,17 +67,20 @@ response_1 = Article.search 'foo';
53
67
  puts "Article search:".ansi(:bold),
54
68
  response_1.to_a.map { |d| "Title: #{d.title}" }.inspect.ansi(:bold, :yellow)
55
69
 
56
- response_2 = Article.__elasticsearch__.client.suggest \
57
- index: Article.index_name,
58
- body: {
70
+ response_2 = Article.search \
71
+ query: {
72
+ match: { title: 'foo' }
73
+ },
74
+ suggest: {
59
75
  articles: {
60
76
  text: 'foo',
61
- completion: { field: 'title_suggest', size: 25 }
77
+ completion: { field: 'title.suggest' }
62
78
  }
63
- };
79
+ },
80
+ _source: ['title', 'url']
64
81
 
65
- puts "Article suggest:".ansi(:bold),
66
- response_2['articles'].first['options'].map { |d| "#{d['text']} -> #{d['payload']['url']}" }.
67
- inspect.ansi(:bold, :green)
82
+ puts "Article search with suggest:".ansi(:bold),
83
+ response_2.response['suggest']['articles'].first['options'].map { |d| "#{d['text']} -> #{d['_source']['url']}" }.
84
+ inspect.ansi(:bold, :blue)
68
85
 
69
86
  require 'pry'; binding.pry;
@@ -0,0 +1,118 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ require 'ansi'
19
+ require 'sqlite3'
20
+ require 'active_record'
21
+ require 'elasticsearch/model'
22
+
23
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
24
+ ActiveRecord::Base.establish_connection( adapter: 'sqlite3', database: ":memory:" )
25
+
26
+ ActiveRecord::Schema.define(version: 1) do
27
+ create_table :articles do |t|
28
+ t.string :title
29
+ t.date :published_at
30
+ t.timestamps
31
+ end
32
+ end
33
+
34
+ class Article < ActiveRecord::Base
35
+ include Elasticsearch::Model
36
+ include Elasticsearch::Model::Callbacks
37
+
38
+ article_es_settings = {
39
+ index: {
40
+ analysis: {
41
+ filter: {
42
+ autocomplete_filter: {
43
+ type: "edge_ngram",
44
+ min_gram: 1,
45
+ max_gram: 20
46
+ }
47
+ },
48
+ analyzer:{
49
+ autocomplete: {
50
+ type: "custom",
51
+ tokenizer: "standard",
52
+ filter: ["lowercase", "autocomplete_filter"]
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ settings article_es_settings do
60
+ mapping do
61
+ indexes :title
62
+ indexes :suggestable_title, type: 'string', analyzer: 'autocomplete'
63
+ end
64
+ end
65
+
66
+ def as_indexed_json(options={})
67
+ as_json.merge(suggestable_title: title)
68
+ end
69
+ end
70
+
71
+ Article.__elasticsearch__.client = Elasticsearch::Client.new log: true
72
+
73
+ # Create index
74
+
75
+ Article.__elasticsearch__.create_index! force: true
76
+
77
+ # Store data
78
+
79
+ Article.delete_all
80
+ Article.create title: 'Foo'
81
+ Article.create title: 'Bar'
82
+ Article.create title: 'Foo Foo'
83
+ Article.__elasticsearch__.refresh_index!
84
+
85
+ # Search and suggest
86
+ fulltext_search_response = Article.search(query: { match: { title: 'foo'} } )
87
+
88
+ puts "", "Article search for 'foo':".ansi(:bold),
89
+ fulltext_search_response.to_a.map { |d| "Title: #{d.title}" }.inspect.ansi(:bold, :yellow),
90
+ ""
91
+
92
+ fulltext_search_response_2 = Article.search(query: { match: { title: 'fo'} } )
93
+
94
+ puts "", "Article search for 'fo':".ansi(:bold),
95
+ fulltext_search_response_2.to_a.map { |d| "Title: #{d.title}" }.inspect.ansi(:bold, :red),
96
+ ""
97
+
98
+ autocomplete_search_response = Article.search(query: { match: { suggestable_title: { query: 'fo', analyzer: 'standard'} } } )
99
+
100
+ puts "", "Article autocomplete for 'fo':".ansi(:bold),
101
+ autocomplete_search_response.to_a.map { |d| "Title: #{d.suggestable_title}" }.inspect.ansi(:bold, :green),
102
+ ""
103
+
104
+ puts "", "Text 'Foo Bar' analyzed with the default analyzer:".ansi(:bold),
105
+ Article.__elasticsearch__.client.indices.analyze(
106
+ index: Article.__elasticsearch__.index_name,
107
+ field: 'title',
108
+ text: 'Foo Bar')['tokens'].map { |t| t['token'] }.inspect.ansi(:bold, :yellow),
109
+ ""
110
+
111
+ puts "", "Text 'Foo Bar' analyzed with the autocomplete filter:".ansi(:bold),
112
+ Article.__elasticsearch__.client.indices.analyze(
113
+ index: Article.__elasticsearch__.index_name,
114
+ field: 'suggestable_title',
115
+ text: 'Foo Bar')['tokens'].map { |t| t['token'] }.inspect.ansi(:bold, :yellow),
116
+ ""
117
+
118
+ require 'pry'; binding.pry;
@@ -1,3 +1,20 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
1
18
  # Couchbase and Elasticsearch
2
19
  # ===========================
3
20
  #