indexers 4.1.0.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 +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
|
+
[](http://badge.fury.io/rb/indexers)
|
2
|
+
[](https://codeclimate.com/github/mmontossi/indexers)
|
3
|
+
[](https://travis-ci.org/mmontossi/indexers)
|
4
|
+
[](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
|