elasticsearch-model-queryable 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/CHANGELOG.md +26 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +695 -0
- data/Rakefile +59 -0
- data/elasticsearch-model.gemspec +57 -0
- data/examples/activerecord_article.rb +77 -0
- data/examples/activerecord_associations.rb +162 -0
- data/examples/couchbase_article.rb +66 -0
- data/examples/datamapper_article.rb +71 -0
- data/examples/mongoid_article.rb +68 -0
- data/examples/ohm_article.rb +70 -0
- data/examples/riak_article.rb +52 -0
- data/gemfiles/3.0.gemfile +12 -0
- data/gemfiles/4.0.gemfile +11 -0
- data/lib/elasticsearch/model/adapter.rb +145 -0
- data/lib/elasticsearch/model/adapters/active_record.rb +104 -0
- data/lib/elasticsearch/model/adapters/default.rb +50 -0
- data/lib/elasticsearch/model/adapters/mongoid.rb +92 -0
- data/lib/elasticsearch/model/callbacks.rb +35 -0
- data/lib/elasticsearch/model/client.rb +61 -0
- data/lib/elasticsearch/model/ext/active_record.rb +14 -0
- data/lib/elasticsearch/model/hash_wrapper.rb +15 -0
- data/lib/elasticsearch/model/importing.rb +144 -0
- data/lib/elasticsearch/model/indexing.rb +472 -0
- data/lib/elasticsearch/model/naming.rb +101 -0
- data/lib/elasticsearch/model/proxy.rb +127 -0
- data/lib/elasticsearch/model/response/base.rb +44 -0
- data/lib/elasticsearch/model/response/pagination.rb +173 -0
- data/lib/elasticsearch/model/response/records.rb +69 -0
- data/lib/elasticsearch/model/response/result.rb +63 -0
- data/lib/elasticsearch/model/response/results.rb +31 -0
- data/lib/elasticsearch/model/response.rb +71 -0
- data/lib/elasticsearch/model/searching.rb +107 -0
- data/lib/elasticsearch/model/serializing.rb +35 -0
- data/lib/elasticsearch/model/version.rb +5 -0
- data/lib/elasticsearch/model.rb +157 -0
- data/test/integration/active_record_associations_parent_child.rb +139 -0
- data/test/integration/active_record_associations_test.rb +307 -0
- data/test/integration/active_record_basic_test.rb +179 -0
- data/test/integration/active_record_custom_serialization_test.rb +62 -0
- data/test/integration/active_record_import_test.rb +100 -0
- data/test/integration/active_record_namespaced_model_test.rb +49 -0
- data/test/integration/active_record_pagination_test.rb +132 -0
- data/test/integration/mongoid_basic_test.rb +193 -0
- data/test/test_helper.rb +63 -0
- data/test/unit/adapter_active_record_test.rb +140 -0
- data/test/unit/adapter_default_test.rb +41 -0
- data/test/unit/adapter_mongoid_test.rb +102 -0
- data/test/unit/adapter_test.rb +69 -0
- data/test/unit/callbacks_test.rb +31 -0
- data/test/unit/client_test.rb +27 -0
- data/test/unit/importing_test.rb +176 -0
- data/test/unit/indexing_test.rb +478 -0
- data/test/unit/module_test.rb +57 -0
- data/test/unit/naming_test.rb +76 -0
- data/test/unit/proxy_test.rb +89 -0
- data/test/unit/response_base_test.rb +40 -0
- data/test/unit/response_pagination_kaminari_test.rb +189 -0
- data/test/unit/response_pagination_will_paginate_test.rb +208 -0
- data/test/unit/response_records_test.rb +91 -0
- data/test/unit/response_result_test.rb +90 -0
- data/test/unit/response_results_test.rb +31 -0
- data/test/unit/response_test.rb +67 -0
- data/test/unit/searching_search_request_test.rb +78 -0
- data/test/unit/searching_test.rb +41 -0
- data/test/unit/serializing_test.rb +17 -0
- metadata +466 -0
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'elasticsearch'
|
2
|
+
|
3
|
+
require 'hashie'
|
4
|
+
|
5
|
+
require 'active_support/core_ext/module/delegation'
|
6
|
+
|
7
|
+
require 'elasticsearch/model/version'
|
8
|
+
|
9
|
+
require 'elasticsearch/model/client'
|
10
|
+
|
11
|
+
require 'elasticsearch/model/adapter'
|
12
|
+
require 'elasticsearch/model/adapters/default'
|
13
|
+
require 'elasticsearch/model/adapters/active_record'
|
14
|
+
require 'elasticsearch/model/adapters/mongoid'
|
15
|
+
|
16
|
+
require 'elasticsearch/model/importing'
|
17
|
+
require 'elasticsearch/model/indexing'
|
18
|
+
require 'elasticsearch/model/naming'
|
19
|
+
require 'elasticsearch/model/serializing'
|
20
|
+
require 'elasticsearch/model/searching'
|
21
|
+
require 'elasticsearch/model/callbacks'
|
22
|
+
|
23
|
+
require 'elasticsearch/model/proxy'
|
24
|
+
|
25
|
+
require 'elasticsearch/model/response'
|
26
|
+
require 'elasticsearch/model/response/base'
|
27
|
+
require 'elasticsearch/model/response/result'
|
28
|
+
require 'elasticsearch/model/response/results'
|
29
|
+
require 'elasticsearch/model/response/records'
|
30
|
+
require 'elasticsearch/model/response/pagination'
|
31
|
+
|
32
|
+
require 'elasticsearch/model/ext/active_record'
|
33
|
+
|
34
|
+
case
|
35
|
+
when defined?(::Kaminari)
|
36
|
+
Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model::Response::Pagination::Kaminari
|
37
|
+
when defined?(::WillPaginate)
|
38
|
+
Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model::Response::Pagination::WillPaginate
|
39
|
+
end
|
40
|
+
|
41
|
+
module Elasticsearch
|
42
|
+
|
43
|
+
# Elasticsearch integration for Ruby models
|
44
|
+
# =========================================
|
45
|
+
#
|
46
|
+
# `Elasticsearch::Model` contains modules for integrating the Elasticsearch search and analytical engine
|
47
|
+
# with ActiveModel-based classes, or models, for the Ruby programming language.
|
48
|
+
#
|
49
|
+
# It facilitates importing your data into an index, automatically updating it when a record changes,
|
50
|
+
# searching the specific index, setting up the index mapping or the model JSON serialization.
|
51
|
+
#
|
52
|
+
# When the `Elasticsearch::Model` module is included in your class, it automatically extends it
|
53
|
+
# with the functionality; see {Elasticsearch::Model.included}. Most methods are available via
|
54
|
+
# the `__elasticsearch__` class and instance method proxies.
|
55
|
+
#
|
56
|
+
# It is possible to include/extend the model with the corresponding
|
57
|
+
# modules directly, if that is desired:
|
58
|
+
#
|
59
|
+
# MyModel.__send__ :extend, Elasticsearch::Model::Client::ClassMethods
|
60
|
+
# MyModel.__send__ :include, Elasticsearch::Model::Client::InstanceMethods
|
61
|
+
# MyModel.__send__ :extend, Elasticsearch::Model::Searching::ClassMethods
|
62
|
+
# # ...
|
63
|
+
#
|
64
|
+
module Model
|
65
|
+
METHODS = [:search, :mapping, :mappings, :settings, :index_name, :document_type, :import]
|
66
|
+
|
67
|
+
# Adds the `Elasticsearch::Model` functionality to the including class.
|
68
|
+
#
|
69
|
+
# * Creates the `__elasticsearch__` class and instance methods, pointing to the proxy object
|
70
|
+
# * Includes the necessary modules in the proxy classes
|
71
|
+
# * Sets up delegation for crucial methods such as `search`, etc.
|
72
|
+
#
|
73
|
+
# @example Include the module in the `Article` model definition
|
74
|
+
#
|
75
|
+
# class Article < ActiveRecord::Base
|
76
|
+
# include Elasticsearch::Model
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# @example Inject the module into the `Article` model during run time
|
80
|
+
#
|
81
|
+
# Article.__send__ :include, Elasticsearch::Model
|
82
|
+
#
|
83
|
+
#
|
84
|
+
def self.included(base)
|
85
|
+
base.class_eval do
|
86
|
+
include Elasticsearch::Model::Proxy
|
87
|
+
|
88
|
+
Elasticsearch::Model::Proxy::ClassMethodsProxy.class_eval do
|
89
|
+
include Elasticsearch::Model::Client::ClassMethods
|
90
|
+
include Elasticsearch::Model::Naming::ClassMethods
|
91
|
+
include Elasticsearch::Model::Indexing::ClassMethods
|
92
|
+
include Elasticsearch::Model::Searching::ClassMethods
|
93
|
+
end
|
94
|
+
|
95
|
+
Elasticsearch::Model::Proxy::InstanceMethodsProxy.class_eval do
|
96
|
+
include Elasticsearch::Model::Client::InstanceMethods
|
97
|
+
include Elasticsearch::Model::Naming::InstanceMethods
|
98
|
+
include Elasticsearch::Model::Indexing::InstanceMethods
|
99
|
+
include Elasticsearch::Model::Serializing::InstanceMethods
|
100
|
+
end
|
101
|
+
|
102
|
+
Elasticsearch::Model::Proxy::InstanceMethodsProxy.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
103
|
+
def as_indexed_json(options={})
|
104
|
+
target.respond_to?(:as_indexed_json) ? target.__send__(:as_indexed_json, options) : super
|
105
|
+
end
|
106
|
+
CODE
|
107
|
+
|
108
|
+
# Delegate important methods to the `__elasticsearch__` proxy, unless they are defined already
|
109
|
+
#
|
110
|
+
class << self
|
111
|
+
METHODS.each do |method|
|
112
|
+
delegate method, to: :__elasticsearch__ unless self.public_instance_methods.include?(method)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Mix the importing module into the proxy
|
117
|
+
#
|
118
|
+
self.__elasticsearch__.class_eval do
|
119
|
+
include Elasticsearch::Model::Importing::ClassMethods
|
120
|
+
include Adapter.from_class(base).importing_mixin
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
module ClassMethods
|
126
|
+
|
127
|
+
# Get the client common for all models
|
128
|
+
#
|
129
|
+
# @example Get the client
|
130
|
+
#
|
131
|
+
# Elasticsearch::Model.client
|
132
|
+
# => #<Elasticsearch::Transport::Client:0x007f96a7d0d000 @transport=... >
|
133
|
+
#
|
134
|
+
def client
|
135
|
+
@client ||= Elasticsearch::Client.new
|
136
|
+
end
|
137
|
+
|
138
|
+
# Set the client for all models
|
139
|
+
#
|
140
|
+
# @example Configure (set) the client for all models
|
141
|
+
#
|
142
|
+
# Elasticsearch::Model.client Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true
|
143
|
+
# => #<Elasticsearch::Transport::Client:0x007f96a6dd0d80 @transport=... >
|
144
|
+
#
|
145
|
+
# @note You have to set the client before you call Elasticsearch methods on the model,
|
146
|
+
# or set it directly on the model; see {Elasticsearch::Model::Client::ClassMethods#client}
|
147
|
+
#
|
148
|
+
def client=(client)
|
149
|
+
@client = client
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
extend ClassMethods
|
154
|
+
|
155
|
+
class NotImplemented < NoMethodError; end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
class Question < ActiveRecord::Base
|
5
|
+
include Elasticsearch::Model
|
6
|
+
|
7
|
+
has_many :answers, dependent: :destroy
|
8
|
+
|
9
|
+
index_name 'questions_and_answers'
|
10
|
+
|
11
|
+
mapping do
|
12
|
+
indexes :title
|
13
|
+
indexes :text
|
14
|
+
indexes :author
|
15
|
+
end
|
16
|
+
|
17
|
+
after_commit lambda { __elasticsearch__.index_document }, on: :create
|
18
|
+
after_commit lambda { __elasticsearch__.update_document }, on: :update
|
19
|
+
after_commit lambda { __elasticsearch__.delete_document }, on: :destroy
|
20
|
+
end
|
21
|
+
|
22
|
+
class Answer < ActiveRecord::Base
|
23
|
+
include Elasticsearch::Model
|
24
|
+
|
25
|
+
belongs_to :question
|
26
|
+
|
27
|
+
index_name 'questions_and_answers'
|
28
|
+
|
29
|
+
mapping _parent: { type: 'question', required: true } do
|
30
|
+
indexes :text
|
31
|
+
indexes :author
|
32
|
+
end
|
33
|
+
|
34
|
+
after_commit lambda { __elasticsearch__.index_document(parent: question_id) }, on: :create
|
35
|
+
after_commit lambda { __elasticsearch__.update_document(parent: question_id) }, on: :update
|
36
|
+
after_commit lambda { __elasticsearch__.delete_document(parent: question_id) }, on: :destroy
|
37
|
+
end
|
38
|
+
|
39
|
+
module ParentChildSearchable
|
40
|
+
INDEX_NAME = 'questions_and_answers'
|
41
|
+
|
42
|
+
def create_index!(options={})
|
43
|
+
client = Question.__elasticsearch__.client
|
44
|
+
client.indices.delete index: INDEX_NAME rescue nil if options[:force]
|
45
|
+
|
46
|
+
settings = Question.settings.to_hash.merge Answer.settings.to_hash
|
47
|
+
mappings = Question.mappings.to_hash.merge Answer.mappings.to_hash
|
48
|
+
|
49
|
+
client.indices.create index: INDEX_NAME,
|
50
|
+
body: {
|
51
|
+
settings: settings.to_hash,
|
52
|
+
mappings: mappings.to_hash }
|
53
|
+
end
|
54
|
+
|
55
|
+
extend self
|
56
|
+
end
|
57
|
+
|
58
|
+
module Elasticsearch
|
59
|
+
module Model
|
60
|
+
class ActiveRecordAssociationsParentChildIntegrationTest < Elasticsearch::Test::IntegrationTestCase
|
61
|
+
|
62
|
+
context "ActiveRecord associations with parent/child modelling" do
|
63
|
+
setup do
|
64
|
+
ActiveRecord::Schema.define(version: 1) do
|
65
|
+
create_table :questions do |t|
|
66
|
+
t.string :title
|
67
|
+
t.text :text
|
68
|
+
t.string :author
|
69
|
+
t.timestamps
|
70
|
+
end
|
71
|
+
create_table :answers do |t|
|
72
|
+
t.text :text
|
73
|
+
t.string :author
|
74
|
+
t.references :question
|
75
|
+
t.timestamps
|
76
|
+
end and add_index(:answers, :question_id)
|
77
|
+
end
|
78
|
+
|
79
|
+
Question.delete_all
|
80
|
+
ParentChildSearchable.create_index! force: true
|
81
|
+
|
82
|
+
q_1 = Question.create! title: 'First Question', author: 'John'
|
83
|
+
q_2 = Question.create! title: 'Second Question', author: 'Jody'
|
84
|
+
|
85
|
+
q_1.answers.create! text: 'Lorem Ipsum', author: 'Adam'
|
86
|
+
q_1.answers.create! text: 'Dolor Sit', author: 'Ryan'
|
87
|
+
|
88
|
+
q_2.answers.create! text: 'Amet Et', author: 'John'
|
89
|
+
|
90
|
+
Question.__elasticsearch__.refresh_index!
|
91
|
+
end
|
92
|
+
|
93
|
+
should "find questions by matching answers" do
|
94
|
+
response = Question.search(
|
95
|
+
{ query: {
|
96
|
+
has_child: {
|
97
|
+
type: 'answer',
|
98
|
+
query: {
|
99
|
+
match: {
|
100
|
+
author: 'john'
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
})
|
106
|
+
|
107
|
+
assert_equal 'Second Question', response.records.first.title
|
108
|
+
end
|
109
|
+
|
110
|
+
should "find answers for matching questions" do
|
111
|
+
response = Answer.search(
|
112
|
+
{ query: {
|
113
|
+
has_parent: {
|
114
|
+
parent_type: 'question',
|
115
|
+
query: {
|
116
|
+
match: {
|
117
|
+
author: 'john'
|
118
|
+
}
|
119
|
+
}
|
120
|
+
}
|
121
|
+
}
|
122
|
+
})
|
123
|
+
|
124
|
+
assert_same_elements ['Adam', 'Ryan'], response.records.map(&:author)
|
125
|
+
end
|
126
|
+
|
127
|
+
should "delete answers when the question is deleted" do
|
128
|
+
Question.where(title: 'First Question').each(&:destroy)
|
129
|
+
Question.__elasticsearch__.refresh_index!
|
130
|
+
|
131
|
+
response = Answer.search query: { match_all: {} }
|
132
|
+
|
133
|
+
assert_equal 1, response.results.total
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module Elasticsearch
|
5
|
+
module Model
|
6
|
+
class ActiveRecordAssociationsIntegrationTest < Elasticsearch::Test::IntegrationTestCase
|
7
|
+
|
8
|
+
context "ActiveRecord associations" do
|
9
|
+
setup do
|
10
|
+
|
11
|
+
# ----- Schema definition ---------------------------------------------------------------
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define(version: 1) do
|
14
|
+
create_table :categories do |t|
|
15
|
+
t.string :title
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :categories_posts, id: false do |t|
|
20
|
+
t.references :post, :category
|
21
|
+
end
|
22
|
+
|
23
|
+
create_table :authors do |t|
|
24
|
+
t.string :first_name, :last_name
|
25
|
+
t.timestamps
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table :authorships do |t|
|
29
|
+
t.string :first_name, :last_name
|
30
|
+
t.references :post
|
31
|
+
t.references :author
|
32
|
+
t.timestamps
|
33
|
+
end
|
34
|
+
|
35
|
+
create_table :comments do |t|
|
36
|
+
t.string :text
|
37
|
+
t.string :author
|
38
|
+
t.references :post
|
39
|
+
t.timestamps
|
40
|
+
end and add_index(:comments, :post_id)
|
41
|
+
|
42
|
+
create_table :posts do |t|
|
43
|
+
t.string :title
|
44
|
+
t.text :text
|
45
|
+
t.boolean :published
|
46
|
+
t.timestamps
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# ----- Models definition -------------------------------------------------------------------------
|
51
|
+
|
52
|
+
class Category < ActiveRecord::Base
|
53
|
+
has_and_belongs_to_many :posts
|
54
|
+
end
|
55
|
+
|
56
|
+
class Author < ActiveRecord::Base
|
57
|
+
has_many :authorships
|
58
|
+
|
59
|
+
def full_name
|
60
|
+
[first_name, last_name].compact.join(' ')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Authorship < ActiveRecord::Base
|
65
|
+
belongs_to :author
|
66
|
+
belongs_to :post, touch: true
|
67
|
+
end
|
68
|
+
|
69
|
+
class Comment < ActiveRecord::Base
|
70
|
+
belongs_to :post, touch: true
|
71
|
+
end
|
72
|
+
|
73
|
+
class Post < ActiveRecord::Base
|
74
|
+
has_and_belongs_to_many :categories, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ],
|
75
|
+
after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ]
|
76
|
+
has_many :authorships
|
77
|
+
has_many :authors, through: :authorships
|
78
|
+
has_many :comments
|
79
|
+
end
|
80
|
+
|
81
|
+
# ----- Search integration via Concern module -----------------------------------------------------
|
82
|
+
|
83
|
+
module Searchable
|
84
|
+
extend ActiveSupport::Concern
|
85
|
+
|
86
|
+
included do
|
87
|
+
include Elasticsearch::Model
|
88
|
+
include Elasticsearch::Model::Callbacks
|
89
|
+
|
90
|
+
# Set up the mapping
|
91
|
+
#
|
92
|
+
settings index: { number_of_shards: 1, number_of_replicas: 0 } do
|
93
|
+
mapping do
|
94
|
+
indexes :title, analyzer: 'snowball'
|
95
|
+
indexes :created_at, type: 'date'
|
96
|
+
|
97
|
+
indexes :authors do
|
98
|
+
indexes :first_name
|
99
|
+
indexes :last_name
|
100
|
+
indexes :full_name, type: 'multi_field' do
|
101
|
+
indexes :full_name
|
102
|
+
indexes :raw, analyzer: 'keyword'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
indexes :categories, analyzer: 'keyword'
|
107
|
+
|
108
|
+
indexes :comments, type: 'nested' do
|
109
|
+
indexes :text
|
110
|
+
indexes :author
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Customize the JSON serialization for Elasticsearch
|
116
|
+
#
|
117
|
+
def as_indexed_json(options={})
|
118
|
+
{
|
119
|
+
title: title,
|
120
|
+
text: text,
|
121
|
+
categories: categories.map(&:title),
|
122
|
+
authors: authors.as_json(methods: [:full_name], only: [:full_name, :first_name, :last_name]),
|
123
|
+
comments: comments.as_json(only: [:text, :author])
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
# Update document in the index after touch
|
128
|
+
#
|
129
|
+
after_touch() { __elasticsearch__.index_document }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Include the search integration
|
134
|
+
#
|
135
|
+
Post.__send__ :include, Searchable
|
136
|
+
|
137
|
+
# ----- Reset the index -----------------------------------------------------------------
|
138
|
+
|
139
|
+
Post.delete_all
|
140
|
+
Post.__elasticsearch__.create_index! force: true
|
141
|
+
end
|
142
|
+
|
143
|
+
should "index and find a document" do
|
144
|
+
Post.create! title: 'Test'
|
145
|
+
Post.create! title: 'Testing Coding'
|
146
|
+
Post.create! title: 'Coding'
|
147
|
+
Post.__elasticsearch__.refresh_index!
|
148
|
+
|
149
|
+
response = Post.search('title:test')
|
150
|
+
|
151
|
+
assert_equal 2, response.results.size
|
152
|
+
assert_equal 2, response.records.size
|
153
|
+
|
154
|
+
assert_equal 'Test', response.results.first.title
|
155
|
+
assert_equal 'Test', response.records.first.title
|
156
|
+
end
|
157
|
+
|
158
|
+
should "reindex a document after categories are changed" do
|
159
|
+
# Create categories
|
160
|
+
category_a = Category.where(title: "One").first_or_create!
|
161
|
+
category_b = Category.where(title: "Two").first_or_create!
|
162
|
+
|
163
|
+
# Create post
|
164
|
+
post = Post.create! title: "First Post", text: "This is the first post..."
|
165
|
+
|
166
|
+
# Assign categories
|
167
|
+
post.categories = [category_a, category_b]
|
168
|
+
|
169
|
+
Post.__elasticsearch__.refresh_index!
|
170
|
+
|
171
|
+
query = { query: {
|
172
|
+
filtered: {
|
173
|
+
query: {
|
174
|
+
multi_match: {
|
175
|
+
fields: ['title'],
|
176
|
+
query: 'first'
|
177
|
+
}
|
178
|
+
},
|
179
|
+
filter: {
|
180
|
+
terms: {
|
181
|
+
categories: ['One']
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
response = Post.search query
|
189
|
+
|
190
|
+
assert_equal 1, response.results.size
|
191
|
+
assert_equal 1, response.records.size
|
192
|
+
|
193
|
+
# Remove category "One"
|
194
|
+
post.categories = [category_b]
|
195
|
+
|
196
|
+
Post.__elasticsearch__.refresh_index!
|
197
|
+
response = Post.search query
|
198
|
+
|
199
|
+
assert_equal 0, response.results.size
|
200
|
+
assert_equal 0, response.records.size
|
201
|
+
end
|
202
|
+
|
203
|
+
should "reindex a document after authors are changed" do
|
204
|
+
# Create authors
|
205
|
+
author_a = Author.where(first_name: "John", last_name: "Smith").first_or_create!
|
206
|
+
author_b = Author.where(first_name: "Mary", last_name: "Smith").first_or_create!
|
207
|
+
author_c = Author.where(first_name: "Kobe", last_name: "Griss").first_or_create!
|
208
|
+
|
209
|
+
# Create posts
|
210
|
+
post_1 = Post.create! title: "First Post", text: "This is the first post..."
|
211
|
+
post_2 = Post.create! title: "Second Post", text: "This is the second post..."
|
212
|
+
post_3 = Post.create! title: "Third Post", text: "This is the third post..."
|
213
|
+
|
214
|
+
# Assign authors
|
215
|
+
post_1.authors = [author_a, author_b]
|
216
|
+
post_2.authors = [author_a]
|
217
|
+
post_3.authors = [author_c]
|
218
|
+
|
219
|
+
Post.__elasticsearch__.refresh_index!
|
220
|
+
|
221
|
+
response = Post.search 'authors.full_name:john'
|
222
|
+
|
223
|
+
assert_equal 2, response.results.size
|
224
|
+
assert_equal 2, response.records.size
|
225
|
+
|
226
|
+
post_3.authors << author_a
|
227
|
+
|
228
|
+
Post.__elasticsearch__.refresh_index!
|
229
|
+
|
230
|
+
response = Post.search 'authors.full_name:john'
|
231
|
+
|
232
|
+
assert_equal 3, response.results.size
|
233
|
+
assert_equal 3, response.records.size
|
234
|
+
end if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4
|
235
|
+
|
236
|
+
should "reindex a document after comments are added" do
|
237
|
+
# Create posts
|
238
|
+
post_1 = Post.create! title: "First Post", text: "This is the first post..."
|
239
|
+
post_2 = Post.create! title: "Second Post", text: "This is the second post..."
|
240
|
+
|
241
|
+
# Add comments
|
242
|
+
post_1.comments.create! author: 'John', text: 'Excellent'
|
243
|
+
post_1.comments.create! author: 'Abby', text: 'Good'
|
244
|
+
|
245
|
+
post_2.comments.create! author: 'John', text: 'Terrible'
|
246
|
+
|
247
|
+
Post.__elasticsearch__.refresh_index!
|
248
|
+
|
249
|
+
response = Post.search 'comments.author:john AND comments.text:good'
|
250
|
+
assert_equal 0, response.results.size
|
251
|
+
|
252
|
+
# Add comment
|
253
|
+
post_1.comments.create! author: 'John', text: 'Or rather just good...'
|
254
|
+
|
255
|
+
Post.__elasticsearch__.refresh_index!
|
256
|
+
|
257
|
+
response = Post.search 'comments.author:john AND comments.text:good'
|
258
|
+
assert_equal 0, response.results.size
|
259
|
+
|
260
|
+
response = Post.search \
|
261
|
+
query: {
|
262
|
+
nested: {
|
263
|
+
path: 'comments',
|
264
|
+
query: {
|
265
|
+
bool: {
|
266
|
+
must: [
|
267
|
+
{ match: { 'comments.author' => 'john' } },
|
268
|
+
{ match: { 'comments.text' => 'good' } }
|
269
|
+
]
|
270
|
+
}
|
271
|
+
}
|
272
|
+
}
|
273
|
+
}
|
274
|
+
|
275
|
+
assert_equal 1, response.results.size
|
276
|
+
end if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4
|
277
|
+
|
278
|
+
should "reindex a document after Post#touch" do
|
279
|
+
# Create categories
|
280
|
+
category_a = Category.where(title: "One").first_or_create!
|
281
|
+
|
282
|
+
# Create post
|
283
|
+
post = Post.create! title: "First Post", text: "This is the first post..."
|
284
|
+
|
285
|
+
# Assign category
|
286
|
+
post.categories << category_a
|
287
|
+
|
288
|
+
Post.__elasticsearch__.refresh_index!
|
289
|
+
|
290
|
+
assert_equal 1, Post.search('categories:One').size
|
291
|
+
|
292
|
+
# Update category
|
293
|
+
category_a.update_attribute :title, "Updated"
|
294
|
+
|
295
|
+
# Trigger touch on posts in category
|
296
|
+
category_a.posts.each { |p| p.touch }
|
297
|
+
|
298
|
+
Post.__elasticsearch__.refresh_index!
|
299
|
+
|
300
|
+
assert_equal 0, Post.search('categories:One').size
|
301
|
+
assert_equal 1, Post.search('categories:Updated').size
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|