elasticsearch-model-queryable 0.1.5
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 +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
|