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.
- checksums.yaml +5 -5
- data/.gitignore +3 -2
- data/Gemfile +22 -0
- data/LICENSE.txt +199 -10
- data/README.md +96 -43
- data/Rakefile +49 -35
- data/elasticsearch-model.gemspec +53 -41
- data/examples/activerecord_article.rb +18 -1
- data/examples/activerecord_associations.rb +86 -17
- data/examples/activerecord_custom_analyzer.rb +152 -0
- data/examples/activerecord_mapping_completion.rb +33 -16
- data/examples/activerecord_mapping_edge_ngram.rb +118 -0
- data/examples/couchbase_article.rb +17 -0
- data/examples/datamapper_article.rb +28 -1
- data/examples/mongoid_article.rb +17 -0
- data/examples/ohm_article.rb +17 -0
- data/examples/riak_article.rb +17 -0
- data/gemfiles/3.0.gemfile +23 -1
- data/gemfiles/4.0.gemfile +25 -1
- data/gemfiles/5.0.gemfile +35 -0
- data/gemfiles/6.0.gemfile +36 -0
- data/lib/elasticsearch/model/adapter.rb +17 -0
- data/lib/elasticsearch/model/adapters/active_record.rb +31 -27
- data/lib/elasticsearch/model/adapters/default.rb +17 -0
- data/lib/elasticsearch/model/adapters/mongoid.rb +27 -3
- data/lib/elasticsearch/model/adapters/multiple.rb +29 -4
- data/lib/elasticsearch/model/callbacks.rb +17 -0
- data/lib/elasticsearch/model/client.rb +17 -0
- data/lib/elasticsearch/model/ext/active_record.rb +17 -0
- data/lib/elasticsearch/model/hash_wrapper.rb +32 -0
- data/lib/elasticsearch/model/importing.rb +61 -17
- data/lib/elasticsearch/model/indexing.rb +87 -49
- data/lib/elasticsearch/model/multimodel.rb +17 -0
- data/lib/elasticsearch/model/naming.rb +33 -2
- data/lib/elasticsearch/model/proxy.rb +61 -18
- data/lib/elasticsearch/model/response/aggregations.rb +55 -0
- data/lib/elasticsearch/model/response/base.rb +25 -3
- data/lib/elasticsearch/model/response/pagination/kaminari.rb +126 -0
- data/lib/elasticsearch/model/response/pagination/will_paginate.rb +112 -0
- data/lib/elasticsearch/model/response/pagination.rb +19 -192
- data/lib/elasticsearch/model/response/records.rb +17 -1
- data/lib/elasticsearch/model/response/result.rb +19 -2
- data/lib/elasticsearch/model/response/results.rb +17 -0
- data/lib/elasticsearch/model/response/suggestions.rb +20 -1
- data/lib/elasticsearch/model/response.rb +28 -10
- data/lib/elasticsearch/model/searching.rb +17 -0
- data/lib/elasticsearch/model/serializing.rb +17 -0
- data/lib/elasticsearch/model/version.rb +18 -1
- data/lib/elasticsearch/model.rb +36 -39
- data/spec/elasticsearch/model/adapter_spec.rb +136 -0
- data/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +351 -0
- data/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +395 -0
- data/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +35 -0
- data/spec/elasticsearch/model/adapters/active_record/import_spec.rb +193 -0
- data/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +127 -0
- data/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +55 -0
- data/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +332 -0
- data/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +92 -0
- data/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +78 -0
- data/spec/elasticsearch/model/adapters/active_record_spec.rb +224 -0
- data/spec/elasticsearch/model/adapters/default_spec.rb +58 -0
- data/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +284 -0
- data/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +83 -0
- data/spec/elasticsearch/model/adapters/mongoid_spec.rb +252 -0
- data/spec/elasticsearch/model/adapters/multiple_spec.rb +142 -0
- data/spec/elasticsearch/model/callbacks_spec.rb +50 -0
- data/spec/elasticsearch/model/client_spec.rb +83 -0
- data/spec/elasticsearch/model/hash_wrapper_spec.rb +29 -0
- data/spec/elasticsearch/model/importing_spec.rb +243 -0
- data/spec/elasticsearch/model/indexing_spec.rb +1014 -0
- data/spec/elasticsearch/model/module_spec.rb +94 -0
- data/spec/elasticsearch/model/multimodel_spec.rb +72 -0
- data/spec/elasticsearch/model/naming_spec.rb +203 -0
- data/spec/elasticsearch/model/proxy_spec.rb +124 -0
- data/spec/elasticsearch/model/response/aggregations_spec.rb +83 -0
- data/spec/elasticsearch/model/response/base_spec.rb +107 -0
- data/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +472 -0
- data/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +279 -0
- data/spec/elasticsearch/model/response/records_spec.rb +135 -0
- data/spec/elasticsearch/model/response/response_spec.rb +148 -0
- data/spec/elasticsearch/model/response/result_spec.rb +139 -0
- data/spec/elasticsearch/model/response/results_spec.rb +73 -0
- data/spec/elasticsearch/model/searching_search_request_spec.rb +129 -0
- data/spec/elasticsearch/model/searching_spec.rb +66 -0
- data/spec/elasticsearch/model/serializing_spec.rb +39 -0
- data/spec/spec_helper.rb +193 -0
- data/spec/support/app/answer.rb +50 -0
- data/spec/support/app/article.rb +39 -0
- data/spec/support/app/article_for_pagination.rb +29 -0
- data/spec/support/app/article_no_type.rb +37 -0
- data/spec/support/app/article_with_custom_serialization.rb +30 -0
- data/spec/support/app/article_with_dynamic_index_name.rb +32 -0
- data/spec/support/app/author.rb +26 -0
- data/spec/support/app/authorship.rb +21 -0
- data/spec/support/app/category.rb +20 -0
- data/spec/support/app/comment.rb +20 -0
- data/spec/support/app/episode.rb +28 -0
- data/spec/support/app/image.rb +36 -0
- data/spec/support/app/import_article.rb +29 -0
- data/spec/support/app/mongoid_article.rb +38 -0
- data/spec/support/app/namespaced_book.rb +27 -0
- data/spec/support/app/parent_and_child_searchable.rb +41 -0
- data/spec/support/app/post.rb +31 -0
- data/spec/support/app/question.rb +44 -0
- data/spec/support/app/searchable.rb +65 -0
- data/spec/support/app/series.rb +28 -0
- data/spec/support/app.rb +46 -0
- data/spec/support/model.json +1 -0
- data/{test → spec}/support/model.yml +0 -0
- metadata +175 -121
- data/test/integration/active_record_associations_parent_child.rb +0 -139
- data/test/integration/active_record_associations_test.rb +0 -326
- data/test/integration/active_record_basic_test.rb +0 -234
- data/test/integration/active_record_custom_serialization_test.rb +0 -62
- data/test/integration/active_record_import_test.rb +0 -109
- data/test/integration/active_record_namespaced_model_test.rb +0 -49
- data/test/integration/active_record_pagination_test.rb +0 -145
- data/test/integration/dynamic_index_name_test.rb +0 -47
- data/test/integration/mongoid_basic_test.rb +0 -177
- data/test/integration/multiple_models_test.rb +0 -172
- data/test/support/model.json +0 -1
- data/test/test_helper.rb +0 -93
- data/test/unit/adapter_active_record_test.rb +0 -157
- data/test/unit/adapter_default_test.rb +0 -41
- data/test/unit/adapter_mongoid_test.rb +0 -104
- data/test/unit/adapter_multiple_test.rb +0 -106
- data/test/unit/adapter_test.rb +0 -69
- data/test/unit/callbacks_test.rb +0 -31
- data/test/unit/client_test.rb +0 -27
- data/test/unit/importing_test.rb +0 -203
- data/test/unit/indexing_test.rb +0 -650
- data/test/unit/module_test.rb +0 -57
- data/test/unit/multimodel_test.rb +0 -38
- data/test/unit/naming_test.rb +0 -103
- data/test/unit/proxy_test.rb +0 -100
- data/test/unit/response_base_test.rb +0 -40
- data/test/unit/response_pagination_kaminari_test.rb +0 -433
- data/test/unit/response_pagination_will_paginate_test.rb +0 -398
- data/test/unit/response_records_test.rb +0 -91
- data/test/unit/response_result_test.rb +0 -90
- data/test/unit/response_results_test.rb +0 -31
- data/test/unit/response_test.rb +0 -104
- data/test/unit/searching_search_request_test.rb +0 -78
- data/test/unit/searching_test.rb +0 -41
- data/test/unit/serializing_test.rb +0 -17
data/elasticsearch-model.gemspec
CHANGED
@@ -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
|
-
|
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 =
|
25
|
+
s.name = 'elasticsearch-model'
|
8
26
|
s.version = Elasticsearch::Model::VERSION
|
9
|
-
s.authors = [
|
10
|
-
s.email = [
|
11
|
-
s.description =
|
12
|
-
s.summary =
|
13
|
-
s.homepage =
|
14
|
-
s.license =
|
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 = [
|
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.
|
36
|
-
s.
|
39
|
+
s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
|
40
|
+
s.rdoc_options = ['--charset=UTF-8']
|
37
41
|
|
38
|
-
s.
|
39
|
-
s.add_development_dependency "kaminari"
|
40
|
-
s.add_development_dependency "will_paginate"
|
42
|
+
s.required_ruby_version = '>= 2.4'
|
41
43
|
|
42
|
-
s.
|
43
|
-
s.
|
44
|
-
s.
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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.
|
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
|
-
|
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[
|
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
|
-
|
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 "
|
205
|
+
puts "",
|
206
|
+
"All Models containing 'one':".ansi(:bold),
|
207
|
+
Elasticsearch::Model.search('one').records.to_a.map(&:inspect),
|
208
|
+
""
|
166
209
|
|
167
|
-
#
|
210
|
+
# Difference between `records` and `results`
|
168
211
|
#
|
169
|
-
|
212
|
+
response = Article.search query: { match: { title: 'first' } }
|
170
213
|
|
171
|
-
|
214
|
+
puts "",
|
215
|
+
"Search results are wrapped in the <#{response.class}> class",
|
216
|
+
""
|
172
217
|
|
173
|
-
puts
|
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(
|
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
|
-
|
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.
|
57
|
-
|
58
|
-
|
70
|
+
response_2 = Article.search \
|
71
|
+
query: {
|
72
|
+
match: { title: 'foo' }
|
73
|
+
},
|
74
|
+
suggest: {
|
59
75
|
articles: {
|
60
76
|
text: 'foo',
|
61
|
-
completion: { field: '
|
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['
|
67
|
-
inspect.ansi(:bold, :
|
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
|
#
|