indexers 4.1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +313 -0
- data/Rakefile +19 -0
- data/lib/generators/indexers/indexer/indexer_generator.rb +23 -0
- data/lib/generators/indexers/indexer/templates/indexer.rb +12 -0
- data/lib/generators/indexers/install/install_generator.rb +19 -0
- data/lib/generators/indexers/install/templates/configuration.yml +15 -0
- data/lib/generators/indexers/install/templates/initializer.rb +6 -0
- data/lib/indexers/collection.rb +181 -0
- data/lib/indexers/computed_sorts.rb +19 -0
- data/lib/indexers/concern.rb +30 -0
- data/lib/indexers/configuration.rb +35 -0
- data/lib/indexers/definitions.rb +24 -0
- data/lib/indexers/dsl/api.rb +94 -0
- data/lib/indexers/dsl/mappings.rb +14 -0
- data/lib/indexers/dsl/search.rb +29 -0
- data/lib/indexers/dsl/serialization.rb +17 -0
- data/lib/indexers/dsl/traitable.rb +38 -0
- data/lib/indexers/extensions/active_record/base.rb +20 -0
- data/lib/indexers/indexer.rb +132 -0
- data/lib/indexers/pagination.rb +33 -0
- data/lib/indexers/proxy.rb +22 -0
- data/lib/indexers/railtie.rb +23 -0
- data/lib/indexers/version.rb +5 -0
- data/lib/indexers.rb +82 -0
- data/lib/tasks/indexers.rake +16 -0
- data/test/dsl_test.rb +127 -0
- data/test/dummy/Rakefile +5 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/indexers/product_indexer.rb +55 -0
- data/test/dummy/app/indexers/shop_indexer.rb +28 -0
- data/test/dummy/app/models/product.rb +5 -0
- data/test/dummy/app/models/shop.rb +5 -0
- data/test/dummy/app/views/layouts/application.html.erb +12 -0
- data/test/dummy/bin/bundle +4 -0
- data/test/dummy/bin/rails +5 -0
- data/test/dummy/bin/rake +5 -0
- data/test/dummy/bin/setup +30 -0
- data/test/dummy/config/application.rb +25 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +10 -0
- data/test/dummy/config/database.yml.travis +3 -0
- data/test/dummy/config/elasticsearch.yml +15 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/indexers.rb +65 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/migrate/20161104164148_create_products.rb +14 -0
- data/test/dummy/db/migrate/20161104182219_create_shops.rb +9 -0
- data/test/dummy/db/schema.rb +36 -0
- data/test/dummy/log/development.log +114 -0
- data/test/dummy/log/test.log +20986 -0
- data/test/dummy/public/404.html +61 -0
- data/test/dummy/public/422.html +61 -0
- data/test/dummy/public/500.html +60 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/generator_test.rb +26 -0
- data/test/index_test.rb +42 -0
- data/test/record_test.rb +25 -0
- data/test/search_test.rb +164 -0
- data/test/task_test.rb +31 -0
- data/test/test_helper.rb +14 -0
- metadata +237 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 596797c42610ddbebfd01c173d2d386d6c2d5cd8
|
4
|
+
data.tar.gz: 02a005767c01ef463b72c99343234e6c5a16145f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 595bfc3426cfd08b942ad4ba599d2eefdaf13af30c6312e7c25113aae37a2b2832a71ad733686793bf21da2ff8cd25c4229f3947c8d78000bf5c86fa5c222bbc
|
7
|
+
data.tar.gz: 9a52c3bfcc3388583988ea9d245a515ce28fd8e0a810252627eebf883450d113660cb101e1aa1462742756516b3c77d41b38aa3a538129c11e56c463dfb671bb
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Mathías Montossi
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,313 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/indexers.svg)](http://badge.fury.io/rb/indexers)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/mmontossi/indexers/badges/gpa.svg)](https://codeclimate.com/github/mmontossi/indexers)
|
3
|
+
[![Build Status](https://travis-ci.org/mmontossi/indexers.svg)](https://travis-ci.org/mmontossi/indexers)
|
4
|
+
[![Dependency Status](https://gemnasium.com/mmontossi/indexers.svg)](https://gemnasium.com/mmontossi/indexers)
|
5
|
+
|
6
|
+
# Indexers
|
7
|
+
|
8
|
+
Dsl to delegate searches to elasticsearch in rails.
|
9
|
+
|
10
|
+
## Why
|
11
|
+
|
12
|
+
I did this gem to:
|
13
|
+
|
14
|
+
- Gain control of the queries without losing simplicity.
|
15
|
+
- Have out of the box integration with activerecord and pagers.
|
16
|
+
- Deal with the just in time nature of elasticsearch.
|
17
|
+
- Integrate activerecord includes on it.
|
18
|
+
- Have a convention of how to use suggestions.
|
19
|
+
|
20
|
+
## Install
|
21
|
+
|
22
|
+
Put this line in your Gemfile:
|
23
|
+
```ruby
|
24
|
+
gem 'indexers'
|
25
|
+
```
|
26
|
+
|
27
|
+
Then bundle:
|
28
|
+
```
|
29
|
+
$ bundle
|
30
|
+
```
|
31
|
+
|
32
|
+
To install Redis you can use homebrew:
|
33
|
+
```
|
34
|
+
$ brew install elasticsearch24
|
35
|
+
```
|
36
|
+
|
37
|
+
NOTE: This gem is tested agains version 2.4.
|
38
|
+
|
39
|
+
## Configuration
|
40
|
+
|
41
|
+
Generate the configuration file:
|
42
|
+
```
|
43
|
+
$ bundle exec rails g indexers:install
|
44
|
+
```
|
45
|
+
|
46
|
+
Set the global settings:
|
47
|
+
```ruby
|
48
|
+
Indexers.configure do |config|
|
49
|
+
|
50
|
+
config.mappings do
|
51
|
+
name do
|
52
|
+
type 'string'
|
53
|
+
fields do
|
54
|
+
raw do
|
55
|
+
type 'string'
|
56
|
+
index 'not_analyzed'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
category type: 'string'
|
61
|
+
price type: 'long'
|
62
|
+
currency type: 'string'
|
63
|
+
product_suggestions do
|
64
|
+
type 'completion'
|
65
|
+
analyzer 'simple'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
If you need to personalize the analysis, you can it here:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
Indexers.configure do |config|
|
76
|
+
|
77
|
+
config.analysis do
|
78
|
+
filter do
|
79
|
+
ngram do
|
80
|
+
type 'nGram'
|
81
|
+
min_gram 2
|
82
|
+
max_gram 20
|
83
|
+
end
|
84
|
+
end
|
85
|
+
analyzer do
|
86
|
+
ngram do
|
87
|
+
type 'custom'
|
88
|
+
tokenizer 'standard'
|
89
|
+
filter %w(lowercase ngram)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
NOTE: You may want to personalize the generated config/elasticsearch.yml.
|
98
|
+
|
99
|
+
### Definitions
|
100
|
+
|
101
|
+
Generate an index:
|
102
|
+
```
|
103
|
+
$ bundle exec rails g indexers:indexer products
|
104
|
+
```
|
105
|
+
|
106
|
+
Define the mappings, serialization and search in the index:
|
107
|
+
```ruby
|
108
|
+
Indexers.define :product do
|
109
|
+
|
110
|
+
mappings do
|
111
|
+
properties :name, :category, :price, :product_suggestions
|
112
|
+
end
|
113
|
+
|
114
|
+
serialize do |record|
|
115
|
+
extract record, :name, :category, :price
|
116
|
+
product_suggestions do
|
117
|
+
input [record.name, transliterate(record.name)].uniq
|
118
|
+
output record.name
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
search do |*args|
|
123
|
+
options = args.extract_options!
|
124
|
+
term = args.first
|
125
|
+
query do
|
126
|
+
if term.present?
|
127
|
+
multi_match do
|
128
|
+
query term
|
129
|
+
type 'phrase_prefix'
|
130
|
+
fields %w(name category)
|
131
|
+
end
|
132
|
+
else
|
133
|
+
match_all
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
### Traits
|
142
|
+
|
143
|
+
You can dry complex searches or serializations using traits:
|
144
|
+
```ruby
|
145
|
+
Indexers.define :product do
|
146
|
+
|
147
|
+
search do |*args|
|
148
|
+
options = args.extract_options!
|
149
|
+
shop = options[:shop]
|
150
|
+
term = args.first
|
151
|
+
query do
|
152
|
+
filtered do
|
153
|
+
traits :shop
|
154
|
+
query do
|
155
|
+
if term.present?
|
156
|
+
multi_match do
|
157
|
+
query term
|
158
|
+
type 'phrase_prefix'
|
159
|
+
fields %w(name category)
|
160
|
+
end
|
161
|
+
else
|
162
|
+
match_all
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
trait :shop do
|
170
|
+
filter do
|
171
|
+
bool do
|
172
|
+
must do
|
173
|
+
if shop
|
174
|
+
term do
|
175
|
+
_parent shop.id
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
NOTE: The binding is persisted, there is no need to redefine variables.
|
187
|
+
|
188
|
+
### Indexing
|
189
|
+
|
190
|
+
The index will be updated every time a record is created, updated or destroyed:
|
191
|
+
```ruby
|
192
|
+
product = Product.create(name: 'Les Paul', category: 'Gibson')
|
193
|
+
```
|
194
|
+
|
195
|
+
You can force this actions manually with:
|
196
|
+
```ruby
|
197
|
+
product.index
|
198
|
+
product.reindex
|
199
|
+
product.unindex
|
200
|
+
```
|
201
|
+
|
202
|
+
### Rake tasks
|
203
|
+
|
204
|
+
At any time you can build/rebuild your indexers using:
|
205
|
+
```
|
206
|
+
$ bundle exec rake indexers:index
|
207
|
+
$ bundle exec rake indexers:reindex
|
208
|
+
$ bundle exec rake indexers:unindex
|
209
|
+
```
|
210
|
+
|
211
|
+
### Search
|
212
|
+
|
213
|
+
Use the included search method in the model:
|
214
|
+
```ruby
|
215
|
+
products = Product.search('Les Paul')
|
216
|
+
```
|
217
|
+
|
218
|
+
The result can be used as a collection in views:
|
219
|
+
```erb
|
220
|
+
<%= render products %>
|
221
|
+
```
|
222
|
+
|
223
|
+
### Includes
|
224
|
+
|
225
|
+
Similar to using activerecod:
|
226
|
+
```ruby
|
227
|
+
Product.search.includes(:shop)
|
228
|
+
```
|
229
|
+
|
230
|
+
### Pagination
|
231
|
+
|
232
|
+
Works the same as [pagers gem](https://github.com/mmontossi/pagers):
|
233
|
+
```ruby
|
234
|
+
Products.search.page(1, padding: 4, length: 30)
|
235
|
+
```
|
236
|
+
|
237
|
+
You can force a record to be part of the results by id:
|
238
|
+
```ruby
|
239
|
+
Products.search.page(1, with: 4)
|
240
|
+
```
|
241
|
+
|
242
|
+
Or the opposite:
|
243
|
+
```ruby
|
244
|
+
Products.search.page(4, without: 4)
|
245
|
+
```
|
246
|
+
|
247
|
+
And you can send the collection directly to the view helper:
|
248
|
+
```erb
|
249
|
+
<%= paginate products %>
|
250
|
+
```
|
251
|
+
|
252
|
+
### Order
|
253
|
+
|
254
|
+
Same as using activerecord:
|
255
|
+
```ruby
|
256
|
+
Product.search.order(name: :asc)
|
257
|
+
```
|
258
|
+
|
259
|
+
You can use a computed sort by declare it in the configuration:
|
260
|
+
```ruby
|
261
|
+
Indexers.configure do |config|
|
262
|
+
|
263
|
+
config.computed_sort :price do |direction|
|
264
|
+
type 'number'
|
265
|
+
script do
|
266
|
+
inline "if (_source.currency == 'UYU') { doc['price'].value * 30 }"
|
267
|
+
end
|
268
|
+
order direction
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
```
|
273
|
+
|
274
|
+
### Suggestions
|
275
|
+
|
276
|
+
You need to first define the logic in the configuration:
|
277
|
+
```ruby
|
278
|
+
Indexers.configure do |config|
|
279
|
+
|
280
|
+
config.suggestions do |name, term, options={}|
|
281
|
+
type = name.to_s.singularize
|
282
|
+
text (term || '')
|
283
|
+
completion do
|
284
|
+
field "#{type}_suggestions"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
291
|
+
Then you can get suggestions using the suggest method:
|
292
|
+
```ruby
|
293
|
+
Indexers.suggest :product, 'gibson'
|
294
|
+
```
|
295
|
+
|
296
|
+
The result is an array of hashes with a text property:
|
297
|
+
```ruby
|
298
|
+
[{ text: 'Les Paul' }, ...]
|
299
|
+
```
|
300
|
+
|
301
|
+
## Contributing
|
302
|
+
|
303
|
+
Any issue, pull request, comment of any kind is more than welcome!
|
304
|
+
|
305
|
+
I will mainly ensure compatibility to PostgreSQL, AWS, Redis, Elasticsearch, FreeBSD and Memcached.
|
306
|
+
|
307
|
+
## Credits
|
308
|
+
|
309
|
+
This gem is maintained and funded by [mmontossi](https://github.com/mmontossi).
|
310
|
+
|
311
|
+
## License
|
312
|
+
|
313
|
+
It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
Bundler::GemHelper.install_tasks
|
8
|
+
|
9
|
+
require 'rake/testtask'
|
10
|
+
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = false
|
16
|
+
t.warning = false
|
17
|
+
end
|
18
|
+
|
19
|
+
task default: :test
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module Indexers
|
4
|
+
module Generators
|
5
|
+
class IndexerGenerator < Rails::Generators::NamedBase
|
6
|
+
|
7
|
+
source_root File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
def create_index_file
|
10
|
+
template 'indexer.rb', File.join('app/indexers', class_path, "#{file_name}_indexer.rb")
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def class_name_option
|
16
|
+
if class_path.any?
|
17
|
+
", class_name: '#{class_name}'"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module Indexers
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
|
7
|
+
source_root File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
def create_initializer_file
|
10
|
+
copy_file 'initializer.rb', 'config/initializers/indexers.rb'
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_configuration_file
|
14
|
+
copy_file 'configuration.yml', 'config/elasticsearch.yml'
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
module Indexers
|
2
|
+
class Collection
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :indexer, :args, :options
|
6
|
+
|
7
|
+
delegate :model, to: :indexer
|
8
|
+
delegate :model_name, to: :model
|
9
|
+
delegate :each, :map, :size, :length, :count, :[], :to_a, to: :records
|
10
|
+
|
11
|
+
alias_method :to_ary, :to_a
|
12
|
+
|
13
|
+
def initialize(indexer, args, options)
|
14
|
+
@loaded = false
|
15
|
+
@indexer = indexer
|
16
|
+
@args = args
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
def includes(*args)
|
21
|
+
chain includes: args
|
22
|
+
end
|
23
|
+
|
24
|
+
def page(number, options={})
|
25
|
+
length = page_option(options, :length, 10)
|
26
|
+
padding = page_option(options, :padding, 0)
|
27
|
+
current_page = [number.to_i, 1].max
|
28
|
+
values = Module.new do
|
29
|
+
define_method :page_length do
|
30
|
+
length
|
31
|
+
end
|
32
|
+
define_method :padding do
|
33
|
+
padding
|
34
|
+
end
|
35
|
+
define_method :current_page do
|
36
|
+
current_page
|
37
|
+
end
|
38
|
+
end
|
39
|
+
overrides = {
|
40
|
+
from: ((length * (current_page - 1)) + padding),
|
41
|
+
size: length
|
42
|
+
}
|
43
|
+
%i(with without).each do |name|
|
44
|
+
if options.has_key?(name)
|
45
|
+
overrides[name] = options[name]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
chain Pagination, values, overrides
|
49
|
+
end
|
50
|
+
|
51
|
+
def order(options)
|
52
|
+
mappings = Indexers.configuration.mappings
|
53
|
+
values = []
|
54
|
+
options.each do |property, direction|
|
55
|
+
if block = Indexers.computed_sorts.find(property)
|
56
|
+
values << { _script: Dsl::Api.new(direction, &block).to_h }
|
57
|
+
elsif property == :id
|
58
|
+
values << { _uid: { order: direction } }
|
59
|
+
elsif mappings.has_key?(property) && mappings[property][:type] == 'string'
|
60
|
+
values << { "#{property}.raw" => { order: direction } }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
if values.any?
|
64
|
+
chain sort: values
|
65
|
+
else
|
66
|
+
chain
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def response
|
71
|
+
if @loaded == true
|
72
|
+
@response
|
73
|
+
else
|
74
|
+
@loaded = true
|
75
|
+
@response = indexer.search(query)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def query
|
80
|
+
@query ||= begin
|
81
|
+
pagination = options.slice(:from, :size, :sort)
|
82
|
+
without_ids = fetch_ids(options[:without])
|
83
|
+
body = Dsl::Search.new(indexer, args.append(options), &indexer.options[:search]).to_h[:query]
|
84
|
+
request = Dsl::Search.new do
|
85
|
+
if without_ids.any?
|
86
|
+
query do
|
87
|
+
filtered do
|
88
|
+
filter do
|
89
|
+
bool do
|
90
|
+
must_not do
|
91
|
+
without_ids.each do |id|
|
92
|
+
term do
|
93
|
+
_id id
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
query body
|
100
|
+
end
|
101
|
+
end
|
102
|
+
else
|
103
|
+
query body
|
104
|
+
end
|
105
|
+
%i(from size).each do |name|
|
106
|
+
if pagination.has_key?(name)
|
107
|
+
send name, pagination[name]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
if pagination.has_key?(:sort)
|
111
|
+
sort pagination[:sort]
|
112
|
+
else
|
113
|
+
sort do
|
114
|
+
_uid do
|
115
|
+
order 'desc'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
request.to_h
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def records
|
127
|
+
@records ||= begin
|
128
|
+
hit_ids = response['hits']['hits'].map{ |hit| hit['_id'].to_i }
|
129
|
+
missing_ids = (fetch_ids(options[:with]) - hit_ids)
|
130
|
+
if missing_ids.any?
|
131
|
+
last_index = -(missing_ids.length + 1)
|
132
|
+
ids = (missing_ids.sort.reverse + hit_ids.to(last_index))
|
133
|
+
else
|
134
|
+
ids = hit_ids
|
135
|
+
end
|
136
|
+
includes = options.fetch(:includes, [])
|
137
|
+
indexer.model.includes(includes).where(id: ids).sort do |a,b|
|
138
|
+
ids.index(a.id) <=> ids.index(b.id)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def page_option(source, name, default)
|
144
|
+
source[name] || begin
|
145
|
+
if Rails.configuration.cache_classes == false
|
146
|
+
Rails.application.eager_load!
|
147
|
+
end
|
148
|
+
if defined?(Pagers)
|
149
|
+
Pagers.configuration.send name
|
150
|
+
else
|
151
|
+
default
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def fetch_ids(source)
|
157
|
+
case source
|
158
|
+
when Fixnum,String
|
159
|
+
[source.to_i]
|
160
|
+
when ActiveRecord::Base
|
161
|
+
[source.id]
|
162
|
+
when ActiveRecord::Relation
|
163
|
+
source.ids
|
164
|
+
when Array
|
165
|
+
source.map{ |value| fetch_ids(value) }.flatten
|
166
|
+
else
|
167
|
+
[]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def chain(*extensions)
|
172
|
+
overrides = extensions.extract_options!
|
173
|
+
collection = Collection.new(indexer, args, options.merge(overrides))
|
174
|
+
extensions.each do |extension|
|
175
|
+
collection.extend extension
|
176
|
+
end
|
177
|
+
collection
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Indexers
|
2
|
+
module Concern
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
after_commit :index, on: :create
|
7
|
+
after_commit :reindex, on: :update
|
8
|
+
after_commit :unindex, on: :destroy
|
9
|
+
end
|
10
|
+
|
11
|
+
%i(index reindex unindex).each do |name|
|
12
|
+
define_method name do
|
13
|
+
self.class.indexer.send name, self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
|
19
|
+
def search(*args)
|
20
|
+
options = args.extract_options!
|
21
|
+
Collection.new indexer, args, options
|
22
|
+
end
|
23
|
+
|
24
|
+
def indexer
|
25
|
+
@indexer ||= Indexers.definitions.find(name.parameterize('_').to_sym)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Indexers
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
attr_accessor :hosts, :log, :trace
|
5
|
+
|
6
|
+
def mappings(&block)
|
7
|
+
if block_given?
|
8
|
+
@mappings = Dsl::Api.new(&block).to_h
|
9
|
+
else
|
10
|
+
@mappings
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def analysis(&block)
|
15
|
+
if block_given?
|
16
|
+
@analysis = { analysis: Dsl::Api.new(&block).to_h }
|
17
|
+
else
|
18
|
+
@analysis
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def suggestions(&block)
|
23
|
+
if block_given?
|
24
|
+
@suggestions = block
|
25
|
+
else
|
26
|
+
@suggestions
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def computed_sort(*args, &block)
|
31
|
+
Indexers.computed_sorts.add *args, &block
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|