elasticsearch-model 5.1.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/README.md +18 -11
- data/Rakefile +27 -36
- data/elasticsearch-model.gemspec +5 -5
- data/examples/activerecord_custom_analyzer.rb +135 -0
- data/examples/activerecord_mapping_completion.rb +3 -18
- data/examples/datamapper_article.rb +11 -1
- data/gemfiles/3.0.gemfile +6 -1
- data/gemfiles/4.0.gemfile +7 -1
- data/gemfiles/5.0.gemfile +7 -1
- data/lib/elasticsearch/model/adapters/active_record.rb +7 -23
- data/lib/elasticsearch/model/adapters/mongoid.rb +10 -3
- data/lib/elasticsearch/model/importing.rb +1 -1
- data/lib/elasticsearch/model/indexing.rb +6 -4
- data/lib/elasticsearch/model/naming.rb +9 -2
- data/lib/elasticsearch/model/response/aggregations.rb +1 -1
- data/lib/elasticsearch/model/response/base.rb +3 -2
- data/lib/elasticsearch/model/response/pagination/kaminari.rb +109 -0
- data/lib/elasticsearch/model/response/pagination/will_paginate.rb +95 -0
- data/lib/elasticsearch/model/response/pagination.rb +2 -192
- data/lib/elasticsearch/model/response/result.rb +1 -1
- data/lib/elasticsearch/model/response/suggestions.rb +1 -1
- data/lib/elasticsearch/model/response.rb +11 -10
- data/lib/elasticsearch/model/version.rb +1 -1
- data/lib/elasticsearch/model.rb +15 -8
- data/spec/elasticsearch/model/adapter_spec.rb +119 -0
- data/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +334 -0
- data/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +340 -0
- data/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +18 -0
- data/spec/elasticsearch/model/adapters/active_record/import_spec.rb +187 -0
- data/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +110 -0
- data/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +38 -0
- data/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +315 -0
- data/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +75 -0
- data/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +61 -0
- data/spec/elasticsearch/model/adapters/active_record_spec.rb +207 -0
- data/spec/elasticsearch/model/adapters/default_spec.rb +41 -0
- data/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +267 -0
- data/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +66 -0
- data/spec/elasticsearch/model/adapters/mongoid_spec.rb +235 -0
- data/spec/elasticsearch/model/adapters/multiple_spec.rb +125 -0
- data/spec/elasticsearch/model/callbacks_spec.rb +33 -0
- data/spec/elasticsearch/model/client_spec.rb +66 -0
- data/spec/elasticsearch/model/hash_wrapper_spec.rb +12 -0
- data/spec/elasticsearch/model/importing_spec.rb +214 -0
- data/spec/elasticsearch/model/indexing_spec.rb +918 -0
- data/spec/elasticsearch/model/module_spec.rb +101 -0
- data/spec/elasticsearch/model/multimodel_spec.rb +55 -0
- data/spec/elasticsearch/model/naming_inheritance_spec.rb +184 -0
- data/spec/elasticsearch/model/naming_spec.rb +186 -0
- data/spec/elasticsearch/model/proxy_spec.rb +107 -0
- data/spec/elasticsearch/model/response/aggregations_spec.rb +66 -0
- data/spec/elasticsearch/model/response/base_spec.rb +90 -0
- data/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +410 -0
- data/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +262 -0
- data/spec/elasticsearch/model/response/records_spec.rb +118 -0
- data/spec/elasticsearch/model/response/response_spec.rb +131 -0
- data/spec/elasticsearch/model/response/result_spec.rb +122 -0
- data/spec/elasticsearch/model/response/results_spec.rb +56 -0
- data/spec/elasticsearch/model/searching_search_request_spec.rb +112 -0
- data/spec/elasticsearch/model/searching_spec.rb +49 -0
- data/spec/elasticsearch/model/serializing_spec.rb +22 -0
- data/spec/spec_helper.rb +161 -0
- data/spec/support/app/answer.rb +33 -0
- data/spec/support/app/article.rb +22 -0
- data/spec/support/app/article_for_pagination.rb +12 -0
- data/spec/support/app/article_with_custom_serialization.rb +13 -0
- data/spec/support/app/article_with_dynamic_index_name.rb +15 -0
- data/spec/support/app/author.rb +9 -0
- data/spec/support/app/authorship.rb +4 -0
- data/spec/support/app/category.rb +3 -0
- data/spec/support/app/comment.rb +3 -0
- data/spec/support/app/episode.rb +11 -0
- data/spec/support/app/image.rb +19 -0
- data/spec/support/app/import_article.rb +12 -0
- data/spec/support/app/mongoid_article.rb +21 -0
- data/spec/support/app/namespaced_book.rb +10 -0
- data/spec/support/app/parent_and_child_searchable.rb +24 -0
- data/spec/support/app/post.rb +14 -0
- data/spec/support/app/question.rb +27 -0
- data/spec/support/app/searchable.rb +48 -0
- data/spec/support/app/series.rb +11 -0
- data/spec/support/app.rb +21 -0
- data/spec/support/model.json +1 -0
- metadata +134 -90
- data/test/integration/active_record_associations_parent_child_test.rb +0 -147
- data/test/integration/active_record_associations_test.rb +0 -339
- data/test/integration/active_record_basic_test.rb +0 -251
- data/test/integration/active_record_custom_serialization_test.rb +0 -67
- data/test/integration/active_record_import_test.rb +0 -115
- data/test/integration/active_record_namespaced_model_test.rb +0 -54
- data/test/integration/active_record_pagination_test.rb +0 -149
- data/test/integration/dynamic_index_name_test.rb +0 -52
- data/test/integration/mongoid_basic_test.rb +0 -176
- data/test/integration/multiple_models_test.rb +0 -176
- data/test/support/model.json +0 -1
- data/test/test_helper.rb +0 -92
- 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/hash_wrapper_test.rb +0 -13
- data/test/unit/importing_test.rb +0 -203
- data/test/unit/indexing_test.rb +0 -687
- data/test/unit/module_test.rb +0 -68
- data/test/unit/multimodel_test.rb +0 -38
- data/test/unit/naming_inheritance_test.rb +0 -94
- data/test/unit/naming_test.rb +0 -103
- data/test/unit/proxy_test.rb +0 -98
- data/test/unit/response_aggregations_test.rb +0 -46
- 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/{test → spec}/support/model.yml +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Elasticsearch::Model::Serializing do
|
|
4
|
+
|
|
5
|
+
before(:all) do
|
|
6
|
+
class ::DummySearchingModel
|
|
7
|
+
extend Elasticsearch::Model::Searching::ClassMethods
|
|
8
|
+
def self.index_name; 'foo'; end
|
|
9
|
+
def self.document_type; 'bar'; end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after(:all) do
|
|
14
|
+
remove_classes(DummySearchingModel)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
before do
|
|
18
|
+
allow(DummySearchingModel).to receive(:client).and_return(client)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
let(:client) do
|
|
22
|
+
double('client')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '#initialize' do
|
|
26
|
+
|
|
27
|
+
context 'when the search definition is a simple query' do
|
|
28
|
+
|
|
29
|
+
before do
|
|
30
|
+
expect(client).to receive(:search).with(index: 'foo', type: 'bar', q: 'foo').and_return({})
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
let(:search) do
|
|
34
|
+
Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, 'foo')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'passes the query to the client' do
|
|
38
|
+
expect(search.execute!).to eq({})
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context 'when the search definition is a hash' do
|
|
43
|
+
|
|
44
|
+
before do
|
|
45
|
+
expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: { foo: 'bar' }).and_return({})
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
let(:search) do
|
|
49
|
+
Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, foo: 'bar')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'passes the hash to the client' do
|
|
53
|
+
expect(search.execute!).to eq({})
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context 'when the search definition is a json string' do
|
|
58
|
+
|
|
59
|
+
before do
|
|
60
|
+
expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: '{"foo":"bar"}').and_return({})
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
let(:search) do
|
|
64
|
+
Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, '{"foo":"bar"}')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'passes the json string to the client' do
|
|
68
|
+
expect(search.execute!).to eq({})
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context 'when the search definition is a custom object' do
|
|
73
|
+
|
|
74
|
+
before(:all) do
|
|
75
|
+
class MySpecialQueryBuilder
|
|
76
|
+
def to_hash; {foo: 'bar'}; end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
after(:all) do
|
|
81
|
+
Object.send(:remove_const, :MySpecialQueryBuilder) if defined?(MySpecialQueryBuilder)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
before do
|
|
85
|
+
expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: {foo: 'bar'}).and_return({})
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
let(:search) do
|
|
89
|
+
Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, MySpecialQueryBuilder.new)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'passes the query builder to the client and calls #to_hash on it' do
|
|
93
|
+
expect(search.execute!).to eq({})
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context 'when extra options are specified' do
|
|
98
|
+
|
|
99
|
+
before do
|
|
100
|
+
expect(client).to receive(:search).with(index: 'foo', type: 'bar', q: 'foo', size: 15).and_return({})
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
let(:search) do
|
|
104
|
+
Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, 'foo', size: 15)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'passes the extra options to the client as part of the request' do
|
|
108
|
+
expect(search.execute!).to eq({})
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Elasticsearch::Model::Searching::ClassMethods do
|
|
4
|
+
|
|
5
|
+
before(:all) do
|
|
6
|
+
class ::DummySearchingModel
|
|
7
|
+
extend Elasticsearch::Model::Searching::ClassMethods
|
|
8
|
+
|
|
9
|
+
def self.index_name; 'foo'; end
|
|
10
|
+
def self.document_type; 'bar'; end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
after(:all) do
|
|
15
|
+
remove_classes(DummySearchingModel)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'has the search method' do
|
|
19
|
+
expect(DummySearchingModel).to respond_to(:search)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe '#search' do
|
|
23
|
+
|
|
24
|
+
let(:response) do
|
|
25
|
+
double('search', execute!: { 'hits' => {'hits' => [ {'_id' => 2 }, {'_id' => 1 } ]} })
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
before do
|
|
29
|
+
expect(Elasticsearch::Model::Searching::SearchRequest).to receive(:new).with(DummySearchingModel, 'foo', { default_operator: 'AND' }).and_return(response)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'creates a search object' do
|
|
33
|
+
expect(DummySearchingModel.search('foo', default_operator: 'AND')).to be_a(Elasticsearch::Model::Response::Response)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe 'lazy execution' do
|
|
38
|
+
|
|
39
|
+
let(:response) do
|
|
40
|
+
double('search').tap do |r|
|
|
41
|
+
expect(r).to receive(:execute!).never
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'does not execute the search until the results are accessed' do
|
|
46
|
+
DummySearchingModel.search('foo')
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Elasticsearch::Model::Serializing do
|
|
4
|
+
|
|
5
|
+
before(:all) do
|
|
6
|
+
class DummyClass
|
|
7
|
+
include Elasticsearch::Model::Serializing::InstanceMethods
|
|
8
|
+
|
|
9
|
+
def as_json(options={})
|
|
10
|
+
'HASH'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
after(:all) do
|
|
16
|
+
remove_classes(DummyClass)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'delegates to #as_json by default' do
|
|
20
|
+
expect(DummyClass.new.as_indexed_json).to eq('HASH')
|
|
21
|
+
end
|
|
22
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
require 'pry-nav'
|
|
2
|
+
require 'kaminari'
|
|
3
|
+
require 'kaminari/version'
|
|
4
|
+
require 'will_paginate'
|
|
5
|
+
require 'will_paginate/collection'
|
|
6
|
+
require 'elasticsearch/model'
|
|
7
|
+
require 'hashie/version'
|
|
8
|
+
require 'active_model'
|
|
9
|
+
require 'mongoid'
|
|
10
|
+
require 'yaml'
|
|
11
|
+
require 'active_record'
|
|
12
|
+
|
|
13
|
+
unless defined?(ELASTICSEARCH_URL)
|
|
14
|
+
ELASTICSEARCH_URL = ENV['ELASTICSEARCH_URL'] || "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9200)}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
RSpec.configure do |config|
|
|
18
|
+
config.formatter = 'documentation'
|
|
19
|
+
config.color = true
|
|
20
|
+
|
|
21
|
+
config.before(:suite) do
|
|
22
|
+
require 'ansi'
|
|
23
|
+
tracer = ::Logger.new(STDERR)
|
|
24
|
+
tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" }
|
|
25
|
+
Elasticsearch::Model.client = Elasticsearch::Client.new host: ELASTICSEARCH_URL,
|
|
26
|
+
tracer: (ENV['QUIET'] ? nil : tracer)
|
|
27
|
+
|
|
28
|
+
unless ActiveRecord::Base.connected?
|
|
29
|
+
ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" )
|
|
30
|
+
end
|
|
31
|
+
require 'support/app'
|
|
32
|
+
|
|
33
|
+
if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5'
|
|
34
|
+
::ActiveRecord::Base.raise_in_transactional_callbacks = true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
config.after(:all) do
|
|
39
|
+
drop_all_tables!
|
|
40
|
+
delete_all_indices!
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Is the ActiveRecord version at least 4.0?
|
|
45
|
+
#
|
|
46
|
+
# @return [ true, false ] Whether the ActiveRecord version is at least 4.0.
|
|
47
|
+
#
|
|
48
|
+
# @since 6.0.1
|
|
49
|
+
def active_record_at_least_4?
|
|
50
|
+
defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Delete all documents from the indices of the provided list of models.
|
|
54
|
+
#
|
|
55
|
+
# @param [ Array<ActiveRecord::Base> ] models The list of models.
|
|
56
|
+
#
|
|
57
|
+
# @return [ true ]
|
|
58
|
+
#
|
|
59
|
+
# @since 6.0.1
|
|
60
|
+
def clear_indices(*models)
|
|
61
|
+
models.each do |model|
|
|
62
|
+
begin; Elasticsearch::Model.client.delete_by_query(index: model.index_name, q: '*'); rescue; end
|
|
63
|
+
end and true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Delete all documents from the tables of the provided list of models.
|
|
67
|
+
#
|
|
68
|
+
# @param [ Array<ActiveRecord::Base> ] models The list of models.
|
|
69
|
+
#
|
|
70
|
+
# @return [ true ]
|
|
71
|
+
#
|
|
72
|
+
# @since 6.0.1
|
|
73
|
+
def clear_tables(*models)
|
|
74
|
+
begin; models.map(&:delete_all); rescue; end and true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Drop all tables of models registered as subclasses of ActiveRecord::Base.
|
|
78
|
+
#
|
|
79
|
+
# @return [ true ]
|
|
80
|
+
#
|
|
81
|
+
# @since 6.0.1
|
|
82
|
+
def drop_all_tables!
|
|
83
|
+
ActiveRecord::Base.descendants.each do |model|
|
|
84
|
+
begin
|
|
85
|
+
ActiveRecord::Schema.define do
|
|
86
|
+
drop_table model
|
|
87
|
+
end if model.table_exists?
|
|
88
|
+
rescue
|
|
89
|
+
end
|
|
90
|
+
end and true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Drop all indices of models registered as subclasses of ActiveRecord::Base.
|
|
94
|
+
#
|
|
95
|
+
# @return [ true ]
|
|
96
|
+
#
|
|
97
|
+
# @since 6.0.1
|
|
98
|
+
def delete_all_indices!
|
|
99
|
+
client = Elasticsearch::Model.client
|
|
100
|
+
ActiveRecord::Base.descendants.each do |model|
|
|
101
|
+
begin
|
|
102
|
+
client.indices.delete(index: model.index_name) if model.__elasticsearch__.index_exists?
|
|
103
|
+
rescue
|
|
104
|
+
end
|
|
105
|
+
end and true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Remove all classes.
|
|
109
|
+
#
|
|
110
|
+
# @param [ Array<Class> ] classes The list of classes to remove.
|
|
111
|
+
#
|
|
112
|
+
# @return [ true ]
|
|
113
|
+
#
|
|
114
|
+
# @since 6.0.1
|
|
115
|
+
def remove_classes(*classes)
|
|
116
|
+
classes.each do |_class|
|
|
117
|
+
Object.send(:remove_const, _class.name.to_sym) if defined?(_class)
|
|
118
|
+
end and true
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Determine whether the tests with Mongoid should be run.
|
|
122
|
+
# Depends on whether MongoDB is running on the default host and port, `localhost:27017`.
|
|
123
|
+
#
|
|
124
|
+
# @return [ true, false ]
|
|
125
|
+
#
|
|
126
|
+
# @since 6.0.1
|
|
127
|
+
def test_mongoid?
|
|
128
|
+
$mongoid_available ||= begin
|
|
129
|
+
require 'mongoid'
|
|
130
|
+
if defined?(Mongo) # older versions of Mongoid use the driver, Moped
|
|
131
|
+
client = Mongo::Client.new(['localhost:27017'])
|
|
132
|
+
Timeout.timeout(1) do
|
|
133
|
+
client.database.command(ping: 1) && true
|
|
134
|
+
end
|
|
135
|
+
end and true
|
|
136
|
+
rescue Timeout::Error, LoadError, Mongo::Error => e
|
|
137
|
+
client.close
|
|
138
|
+
$stderr.puts("MongoDB not installed or running: #{e}")
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Connect Mongoid and set up its Logger if Mongoid tests should be run.
|
|
143
|
+
#
|
|
144
|
+
# @since 6.0.1
|
|
145
|
+
def connect_mongoid(source)
|
|
146
|
+
if test_mongoid?
|
|
147
|
+
$stderr.puts "Mongoid #{Mongoid::VERSION}", '-'*80
|
|
148
|
+
|
|
149
|
+
if !ENV['QUIET'] == 'true'
|
|
150
|
+
logger = ::Logger.new($stderr)
|
|
151
|
+
logger.formatter = lambda { |s, d, p, m| " #{m.ansi(:faint, :cyan)}\n" }
|
|
152
|
+
logger.level = ::Logger::DEBUG
|
|
153
|
+
Mongoid.logger = logger
|
|
154
|
+
Mongo::Logger.logger = logger
|
|
155
|
+
else
|
|
156
|
+
Mongo::Logger.logger.level = ::Logger::WARN
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
Mongoid.connect_to(source)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class Answer < ActiveRecord::Base
|
|
2
|
+
include Elasticsearch::Model
|
|
3
|
+
|
|
4
|
+
belongs_to :question
|
|
5
|
+
|
|
6
|
+
JOIN_TYPE = 'answer'.freeze
|
|
7
|
+
|
|
8
|
+
index_name 'questions_and_answers'.freeze
|
|
9
|
+
document_type 'doc'.freeze
|
|
10
|
+
|
|
11
|
+
before_create :randomize_id
|
|
12
|
+
|
|
13
|
+
def randomize_id
|
|
14
|
+
begin
|
|
15
|
+
self.id = SecureRandom.random_number(1_000_000)
|
|
16
|
+
end while Answer.where(id: self.id).exists?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
mapping do
|
|
20
|
+
indexes :text
|
|
21
|
+
indexes :author
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def as_indexed_json(options={})
|
|
25
|
+
# This line is necessary for differences between ActiveModel::Serializers::JSON#as_json versions
|
|
26
|
+
json = as_json(options)[JOIN_TYPE] || as_json(options)
|
|
27
|
+
json.merge(join_field: { name: JOIN_TYPE, parent: question_id })
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
after_commit lambda { __elasticsearch__.index_document(routing: (question_id || 1)) }, on: :create
|
|
31
|
+
after_commit lambda { __elasticsearch__.update_document(routing: (question_id || 1)) }, on: :update
|
|
32
|
+
after_commit lambda {__elasticsearch__.delete_document(routing: (question_id || 1)) }, on: :destroy
|
|
33
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class ::Article < ActiveRecord::Base
|
|
2
|
+
include Elasticsearch::Model
|
|
3
|
+
include Elasticsearch::Model::Callbacks
|
|
4
|
+
|
|
5
|
+
document_type 'article'
|
|
6
|
+
|
|
7
|
+
settings index: {number_of_shards: 1, number_of_replicas: 0} do
|
|
8
|
+
mapping do
|
|
9
|
+
indexes :title, type: 'text', analyzer: 'snowball'
|
|
10
|
+
indexes :body, type: 'text'
|
|
11
|
+
indexes :clicks, type: 'integer'
|
|
12
|
+
indexes :created_at, type: 'date'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def as_indexed_json(options = {})
|
|
17
|
+
attributes
|
|
18
|
+
.symbolize_keys
|
|
19
|
+
.slice(:title, :body, :clicks, :created_at)
|
|
20
|
+
.merge(suggest_title: title)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class ::ArticleForPagination < ActiveRecord::Base
|
|
2
|
+
include Elasticsearch::Model
|
|
3
|
+
|
|
4
|
+
scope :published, -> { where(published: true) }
|
|
5
|
+
|
|
6
|
+
settings index: { number_of_shards: 1, number_of_replicas: 0 } do
|
|
7
|
+
mapping do
|
|
8
|
+
indexes :title, type: 'text', analyzer: 'snowball'
|
|
9
|
+
indexes :created_at, type: 'date'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class ::ArticleWithCustomSerialization < ActiveRecord::Base
|
|
2
|
+
include Elasticsearch::Model
|
|
3
|
+
include Elasticsearch::Model::Callbacks
|
|
4
|
+
|
|
5
|
+
mapping do
|
|
6
|
+
indexes :title
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def as_indexed_json(options={})
|
|
10
|
+
# as_json(options.merge root: false).slice('title')
|
|
11
|
+
{ title: self.title }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class ::ArticleWithDynamicIndexName < ActiveRecord::Base
|
|
2
|
+
include Elasticsearch::Model
|
|
3
|
+
include Elasticsearch::Model::Callbacks
|
|
4
|
+
|
|
5
|
+
def self.counter=(value)
|
|
6
|
+
@counter = 0
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.counter
|
|
10
|
+
(@counter ||= 0) && @counter += 1
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
mapping { indexes :title }
|
|
14
|
+
index_name { "articles-#{counter}" }
|
|
15
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class Episode < ActiveRecord::Base
|
|
2
|
+
include Elasticsearch::Model
|
|
3
|
+
include Elasticsearch::Model::Callbacks
|
|
4
|
+
|
|
5
|
+
settings index: {number_of_shards: 1, number_of_replicas: 0} do
|
|
6
|
+
mapping do
|
|
7
|
+
indexes :name, type: 'text', analyzer: 'snowball'
|
|
8
|
+
indexes :created_at, type: 'date'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class Image
|
|
2
|
+
include Mongoid::Document
|
|
3
|
+
include Elasticsearch::Model
|
|
4
|
+
include Elasticsearch::Model::Callbacks
|
|
5
|
+
|
|
6
|
+
field :name, type: String
|
|
7
|
+
attr_accessible :name if respond_to? :attr_accessible
|
|
8
|
+
|
|
9
|
+
settings index: {number_of_shards: 1, number_of_replicas: 0} do
|
|
10
|
+
mapping do
|
|
11
|
+
indexes :name, type: 'text', analyzer: 'snowball'
|
|
12
|
+
indexes :created_at, type: 'date'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def as_indexed_json(options={})
|
|
17
|
+
as_json(except: [:_id])
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class ImportArticle < ActiveRecord::Base
|
|
2
|
+
include Elasticsearch::Model
|
|
3
|
+
|
|
4
|
+
scope :popular, -> { where('views >= 5') }
|
|
5
|
+
|
|
6
|
+
mapping do
|
|
7
|
+
indexes :title, type: 'text'
|
|
8
|
+
indexes :views, type: 'integer'
|
|
9
|
+
indexes :numeric, type: 'integer'
|
|
10
|
+
indexes :created_at, type: 'date'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class ::MongoidArticle
|
|
2
|
+
include Mongoid::Document
|
|
3
|
+
include Elasticsearch::Model
|
|
4
|
+
include Elasticsearch::Model::Callbacks
|
|
5
|
+
|
|
6
|
+
field :id, type: String
|
|
7
|
+
field :title, type: String
|
|
8
|
+
field :views, type: Integer
|
|
9
|
+
attr_accessible :title if respond_to? :attr_accessible
|
|
10
|
+
|
|
11
|
+
settings index: { number_of_shards: 1, number_of_replicas: 0 } do
|
|
12
|
+
mapping do
|
|
13
|
+
indexes :title, type: 'text', analyzer: 'snowball'
|
|
14
|
+
indexes :created_at, type: 'date'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def as_indexed_json(options={})
|
|
19
|
+
as_json(except: [:id, :_id])
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ParentChildSearchable
|
|
2
|
+
INDEX_NAME = 'questions_and_answers'.freeze
|
|
3
|
+
JOIN = 'join'.freeze
|
|
4
|
+
|
|
5
|
+
def create_index!(options={})
|
|
6
|
+
client = Question.__elasticsearch__.client
|
|
7
|
+
client.indices.delete index: INDEX_NAME rescue nil if options[:force]
|
|
8
|
+
|
|
9
|
+
settings = Question.settings.to_hash.merge Answer.settings.to_hash
|
|
10
|
+
mapping_properties = { join_field: { type: JOIN,
|
|
11
|
+
relations: { Question::JOIN_TYPE => Answer::JOIN_TYPE } } }
|
|
12
|
+
|
|
13
|
+
merged_properties = mapping_properties.merge(Question.mappings.to_hash[:doc][:properties]).merge(
|
|
14
|
+
Answer.mappings.to_hash[:doc][:properties])
|
|
15
|
+
mappings = { doc: { properties: merged_properties }}
|
|
16
|
+
|
|
17
|
+
client.indices.create index: INDEX_NAME,
|
|
18
|
+
body: {
|
|
19
|
+
settings: settings.to_hash,
|
|
20
|
+
mappings: mappings }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
extend self
|
|
24
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class Post < ActiveRecord::Base
|
|
2
|
+
include Searchable
|
|
3
|
+
|
|
4
|
+
has_and_belongs_to_many :categories, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ],
|
|
5
|
+
after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ]
|
|
6
|
+
has_many :authorships
|
|
7
|
+
has_many :authors, through: :authorships,
|
|
8
|
+
after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ],
|
|
9
|
+
after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ]
|
|
10
|
+
has_many :comments, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ],
|
|
11
|
+
after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ]
|
|
12
|
+
|
|
13
|
+
after_touch() { __elasticsearch__.index_document }
|
|
14
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class Question < ActiveRecord::Base
|
|
2
|
+
include Elasticsearch::Model
|
|
3
|
+
|
|
4
|
+
has_many :answers, dependent: :destroy
|
|
5
|
+
|
|
6
|
+
JOIN_TYPE = 'question'.freeze
|
|
7
|
+
JOIN_METADATA = { join_field: JOIN_TYPE}.freeze
|
|
8
|
+
|
|
9
|
+
index_name 'questions_and_answers'.freeze
|
|
10
|
+
document_type 'doc'.freeze
|
|
11
|
+
|
|
12
|
+
mapping do
|
|
13
|
+
indexes :title
|
|
14
|
+
indexes :text
|
|
15
|
+
indexes :author
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def as_indexed_json(options={})
|
|
19
|
+
# This line is necessary for differences between ActiveModel::Serializers::JSON#as_json versions
|
|
20
|
+
json = as_json(options)[JOIN_TYPE] || as_json(options)
|
|
21
|
+
json.merge(JOIN_METADATA)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
after_commit lambda { __elasticsearch__.index_document }, on: :create
|
|
25
|
+
after_commit lambda { __elasticsearch__.update_document }, on: :update
|
|
26
|
+
after_commit lambda { __elasticsearch__.delete_document }, on: :destroy
|
|
27
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Searchable
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
include Elasticsearch::Model
|
|
6
|
+
include Elasticsearch::Model::Callbacks
|
|
7
|
+
|
|
8
|
+
# Set up the mapping
|
|
9
|
+
#
|
|
10
|
+
settings index: { number_of_shards: 1, number_of_replicas: 0 } do
|
|
11
|
+
mapping do
|
|
12
|
+
indexes :title, analyzer: 'snowball'
|
|
13
|
+
indexes :created_at, type: 'date'
|
|
14
|
+
|
|
15
|
+
indexes :authors do
|
|
16
|
+
indexes :first_name
|
|
17
|
+
indexes :last_name
|
|
18
|
+
indexes :full_name, type: 'text' do
|
|
19
|
+
indexes :raw, type: 'keyword'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
indexes :categories, type: 'keyword'
|
|
24
|
+
|
|
25
|
+
indexes :comments, type: 'nested' do
|
|
26
|
+
indexes :text
|
|
27
|
+
indexes :author
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Customize the JSON serialization for Elasticsearch
|
|
33
|
+
#
|
|
34
|
+
def as_indexed_json(options={})
|
|
35
|
+
{
|
|
36
|
+
title: title,
|
|
37
|
+
text: text,
|
|
38
|
+
categories: categories.map(&:title),
|
|
39
|
+
authors: authors.as_json(methods: [:full_name], only: [:full_name, :first_name, :last_name]),
|
|
40
|
+
comments: comments.as_json(only: [:text, :author])
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Update document in the index after touch
|
|
45
|
+
#
|
|
46
|
+
after_touch() { __elasticsearch__.index_document }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class Series < ActiveRecord::Base
|
|
2
|
+
include Elasticsearch::Model
|
|
3
|
+
include Elasticsearch::Model::Callbacks
|
|
4
|
+
|
|
5
|
+
settings index: {number_of_shards: 1, number_of_replicas: 0} do
|
|
6
|
+
mapping do
|
|
7
|
+
indexes :name, type: 'text', analyzer: 'snowball'
|
|
8
|
+
indexes :created_at, type: 'date'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|