indexes 0.0.1 → 4.0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +78 -58
- data/lib/generators/{indexes → index}/index_generator.rb +1 -1
- data/lib/generators/{indexes → index}/templates/index.rb +1 -1
- data/lib/generators/indexes/{install_generator.rb → install/install_generator.rb} +1 -1
- data/lib/generators/indexes/{templates → install/templates}/initializer.rb +0 -0
- data/lib/indexes.rb +12 -29
- data/lib/indexes/collection.rb +42 -51
- data/lib/indexes/configuration.rb +1 -1
- data/lib/indexes/definitions.rb +25 -0
- data/lib/indexes/dsl/{serializer.rb → serialization.rb} +3 -3
- data/lib/indexes/index.rb +3 -3
- data/lib/indexes/proxy.rb +2 -2
- data/lib/indexes/railtie.rb +3 -3
- data/lib/indexes/version.rb +1 -1
- data/test/dummy/app/indexes/products_index.rb +2 -2
- data/test/dummy/app/indexes/shops_index.rb +2 -2
- data/test/dummy/config/initializers/indexes.rb +1 -1
- data/test/dummy/log/test.log +2087 -0
- data/test/{generators_test.rb → generator_test.rb} +5 -5
- data/test/{indexes_test.rb → index_test.rb} +2 -2
- data/test/search_test.rb +3 -3
- data/test/{tasks_test.rb → task_test.rb} +1 -1
- metadata +14 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5563006cc938b7b0590e7979fc6c4ef5da6ef18
|
4
|
+
data.tar.gz: 64a3d9f1ced530ead478aae6354d4b374341bbfa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd39393556426f5ffcde4aa3281089dd681b5dfd6eb52ef34270a99ed7a2d12fea661caab391bd32a23f33605aa9fe11c29f5e3add2f34f54d7fadd5b673e0be
|
7
|
+
data.tar.gz: 2e8fb3183fe2713156897df745e5fc392b35f303890639a2112fe9abe35eedd7a47cfc89c0fdd9dddc18ec5c6c6c3c38d8b0c3d9a2c302db53133032ef9e1f5e
|
data/README.md
CHANGED
@@ -38,7 +38,7 @@ NOTE: This gem is tested agains version 2.4.
|
|
38
38
|
|
39
39
|
## Configuration
|
40
40
|
|
41
|
-
|
41
|
+
Generate the configuration file:
|
42
42
|
```
|
43
43
|
$ bundle exec rails g indexes:install
|
44
44
|
```
|
@@ -70,43 +70,17 @@ Indexes.configure do |config|
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
config.analysis do
|
74
|
-
filter do
|
75
|
-
ngram do
|
76
|
-
type 'nGram'
|
77
|
-
min_gram 2
|
78
|
-
max_gram 20
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
config.suggestions do |name, term, options={}|
|
84
|
-
type = name.to_s.singularize
|
85
|
-
text (term || '')
|
86
|
-
completion do
|
87
|
-
field "#{type}_suggestions"
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
config.add_computed_sort :price do |direction|
|
92
|
-
_script do
|
93
|
-
type 'number'
|
94
|
-
script do
|
95
|
-
inline "if (_source.currency == 'UYU') { doc['price'].value * 30 }"
|
96
|
-
end
|
97
|
-
order direction
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
73
|
end
|
102
74
|
```
|
103
75
|
|
76
|
+
### Definitions
|
77
|
+
|
104
78
|
Generate an index:
|
105
79
|
```
|
106
|
-
$ bundle exec rails g
|
80
|
+
$ bundle exec rails g index products
|
107
81
|
```
|
108
82
|
|
109
|
-
Define the index:
|
83
|
+
Define the mappings, serialization and search in the index:
|
110
84
|
```ruby
|
111
85
|
Indexes.define :products do
|
112
86
|
|
@@ -114,8 +88,8 @@ Indexes.define :products do
|
|
114
88
|
properties :name, :category, :price, :product_suggestions
|
115
89
|
end
|
116
90
|
|
117
|
-
|
118
|
-
|
91
|
+
serialization do |record|
|
92
|
+
extract record, :name, :category, :price
|
119
93
|
product_suggestions do
|
120
94
|
input [record.name, transliterate(record.name)].uniq
|
121
95
|
output record.name
|
@@ -141,97 +115,143 @@ Indexes.define :products do
|
|
141
115
|
end
|
142
116
|
```
|
143
117
|
|
144
|
-
|
118
|
+
If you need to personalize the analysis, you have to add it to the configuration:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
Indexes.configure do |config|
|
122
|
+
|
123
|
+
config.analysis do
|
124
|
+
filter do
|
125
|
+
ngram do
|
126
|
+
type 'nGram'
|
127
|
+
min_gram 2
|
128
|
+
max_gram 20
|
129
|
+
end
|
130
|
+
end
|
131
|
+
analyzer do
|
132
|
+
ngram do
|
133
|
+
type 'custom'
|
134
|
+
tokenizer 'standard'
|
135
|
+
filter %w(lowercase ngram)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
```
|
145
142
|
|
146
143
|
### Indexing
|
147
144
|
|
148
|
-
|
145
|
+
The index will be updated every time a record is created, updated or destroyed:
|
149
146
|
```ruby
|
150
147
|
product = Product.create(name: 'Les Paul', category: 'Gibson')
|
151
148
|
```
|
152
149
|
|
153
|
-
You can force
|
150
|
+
You can force this actions manually with:
|
154
151
|
```ruby
|
155
152
|
product.index
|
156
153
|
product.reindex
|
157
154
|
product.unindex
|
158
155
|
```
|
159
156
|
|
160
|
-
|
161
|
-
```
|
162
|
-
$ bundle exec rake indexes:rebuild
|
163
|
-
```
|
157
|
+
### Rake tasks
|
164
158
|
|
165
|
-
|
159
|
+
At any time you can build/rebuild your indexes using:
|
166
160
|
```
|
167
161
|
$ bundle exec rake indexes:build
|
162
|
+
$ bundle exec rake indexes:rebuild
|
168
163
|
```
|
169
164
|
|
170
165
|
### Search
|
171
166
|
|
172
|
-
|
167
|
+
Use the included search method in the model:
|
173
168
|
```ruby
|
174
|
-
products = Product.search(
|
169
|
+
products = Product.search('Les Paul')
|
175
170
|
```
|
176
171
|
|
177
|
-
|
172
|
+
The result can be used as a collection in views:
|
178
173
|
```erb
|
179
174
|
<%= render products %>
|
180
175
|
```
|
181
176
|
|
182
177
|
### Includes
|
183
178
|
|
184
|
-
|
179
|
+
Similar to using activerecod:
|
185
180
|
```ruby
|
186
|
-
Product.search(
|
181
|
+
Product.search.includes(:shop)
|
187
182
|
```
|
188
183
|
|
189
184
|
### With / Without
|
190
185
|
|
191
186
|
You can force a record to be part of the results by id:
|
192
187
|
```ruby
|
193
|
-
|
188
|
+
Products.search.with(4)
|
194
189
|
```
|
195
190
|
|
196
191
|
Or the opposite:
|
197
192
|
```ruby
|
198
|
-
|
193
|
+
Products.search.without(4)
|
199
194
|
```
|
200
195
|
|
201
196
|
### Pagination
|
202
197
|
|
203
198
|
Works the same as [pagers gem](https://github.com/mmontossi/pagers):
|
204
199
|
```ruby
|
205
|
-
|
200
|
+
Products.search.page(1, padding: 4, length: 30)
|
206
201
|
```
|
207
202
|
|
208
|
-
And you can send the collection directly to the helper
|
203
|
+
And you can send the collection directly to the view helper:
|
209
204
|
```erb
|
210
205
|
<%= paginate products %>
|
211
206
|
```
|
212
207
|
|
213
208
|
### Order
|
214
209
|
|
215
|
-
|
210
|
+
Same as using activerecord:
|
216
211
|
```ruby
|
217
|
-
|
212
|
+
Product.search.order(name: :asc)
|
218
213
|
```
|
219
214
|
|
220
|
-
|
215
|
+
You can use a computed sort by declare it in the configuration:
|
221
216
|
```ruby
|
222
|
-
|
223
|
-
```
|
217
|
+
Indexes.configure do |config|
|
224
218
|
|
225
|
-
|
219
|
+
config.computed_sort :price do |direction|
|
220
|
+
_script do
|
221
|
+
type 'number'
|
222
|
+
script do
|
223
|
+
inline "if (_source.currency == 'UYU') { doc['price'].value * 30 }"
|
224
|
+
end
|
225
|
+
order direction
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
```
|
226
231
|
|
227
232
|
### Suggestions
|
228
233
|
|
229
|
-
|
234
|
+
You need to first define the logic in the configuration:
|
235
|
+
```ruby
|
236
|
+
Indexes.configure do |config|
|
237
|
+
|
238
|
+
config.suggestions do |name, term, options={}|
|
239
|
+
type = name.to_s.singularize
|
240
|
+
text (term || '')
|
241
|
+
completion do
|
242
|
+
field "#{type}_suggestions"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
249
|
+
Then you can get suggestions using the suggest method:
|
230
250
|
```ruby
|
231
251
|
Indexes.suggest :products, 'gibson'
|
232
252
|
```
|
233
253
|
|
234
|
-
|
254
|
+
The result is an array of hashes with a text property:
|
235
255
|
```ruby
|
236
256
|
[{ text: 'Les Paul' }, ...]
|
237
257
|
```
|
File without changes
|
data/lib/indexes.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
-
require 'generators/indexes/index_generator'
|
2
|
-
require 'generators/indexes/install_generator'
|
3
1
|
require 'indexes/dsl/api'
|
4
2
|
require 'indexes/dsl/mappings'
|
5
3
|
require 'indexes/dsl/search'
|
6
|
-
require 'indexes/dsl/
|
4
|
+
require 'indexes/dsl/serialization'
|
5
|
+
require 'indexes/collection'
|
7
6
|
require 'indexes/concern'
|
8
7
|
require 'indexes/configuration'
|
8
|
+
require 'indexes/definitions'
|
9
9
|
require 'indexes/index'
|
10
10
|
require 'indexes/pagination'
|
11
11
|
require 'indexes/proxy'
|
12
12
|
require 'indexes/railtie'
|
13
|
-
require 'indexes/collection'
|
14
13
|
require 'indexes/version'
|
15
14
|
|
16
15
|
module Indexes
|
@@ -41,22 +40,12 @@ module Indexes
|
|
41
40
|
@configuration ||= Configuration.new
|
42
41
|
end
|
43
42
|
|
44
|
-
def
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
def add(*args)
|
49
|
-
index = Index.new(*args)
|
50
|
-
registry[index.name] = index
|
43
|
+
def definitions
|
44
|
+
@definitions ||= Definitions.new
|
51
45
|
end
|
52
46
|
|
53
|
-
def
|
54
|
-
|
55
|
-
end
|
56
|
-
alias_method :[], :find
|
57
|
-
|
58
|
-
def each(&block)
|
59
|
-
registry.values.sort.each &block
|
47
|
+
def define(*args, &block)
|
48
|
+
Proxy.new *args, &block
|
60
49
|
end
|
61
50
|
|
62
51
|
def build
|
@@ -66,11 +55,7 @@ module Indexes
|
|
66
55
|
body: { settings: configuration.analysis }
|
67
56
|
)
|
68
57
|
end
|
69
|
-
each &:build
|
70
|
-
end
|
71
|
-
|
72
|
-
def exist?(type)
|
73
|
-
client.indices.exists? index: namespace, type: type
|
58
|
+
definitions.each &:build
|
74
59
|
end
|
75
60
|
|
76
61
|
def rebuild
|
@@ -78,6 +63,10 @@ module Indexes
|
|
78
63
|
build
|
79
64
|
end
|
80
65
|
|
66
|
+
def exist?(type)
|
67
|
+
client.indices.exists? index: namespace, type: type
|
68
|
+
end
|
69
|
+
|
81
70
|
def destroy
|
82
71
|
if client.indices.exists?(index: namespace)
|
83
72
|
client.indices.delete index: namespace
|
@@ -92,11 +81,5 @@ module Indexes
|
|
92
81
|
response['suggestions'].first['options'].map &:symbolize_keys
|
93
82
|
end
|
94
83
|
|
95
|
-
private
|
96
|
-
|
97
|
-
def registry
|
98
|
-
@registry ||= {}
|
99
|
-
end
|
100
|
-
|
101
84
|
end
|
102
85
|
end
|
data/lib/indexes/collection.rb
CHANGED
@@ -8,17 +8,29 @@ module Indexes
|
|
8
8
|
|
9
9
|
alias_method :to_ary, :to_a
|
10
10
|
|
11
|
-
def initialize(index,
|
11
|
+
def initialize(index, args=[], options={}, &block)
|
12
12
|
@loaded = false
|
13
13
|
@index = index
|
14
|
-
@options = args.extract_options!
|
15
14
|
@args = args
|
15
|
+
@options = options
|
16
16
|
@block = block
|
17
17
|
end
|
18
18
|
|
19
|
+
def with(ids)
|
20
|
+
chain with: ids
|
21
|
+
end
|
22
|
+
|
23
|
+
def without(ids)
|
24
|
+
chain without: ids
|
25
|
+
end
|
26
|
+
|
27
|
+
def includes(*args)
|
28
|
+
chain includes: args
|
29
|
+
end
|
30
|
+
|
19
31
|
def page(number, options={})
|
20
|
-
length = (
|
21
|
-
padding = (
|
32
|
+
length = page_option(options, :length, 10)
|
33
|
+
padding = page_option(options, :padding, 0)
|
22
34
|
current_page = [number.to_i, 1].max
|
23
35
|
values = Module.new do
|
24
36
|
define_method :page_length do
|
@@ -31,32 +43,28 @@ module Indexes
|
|
31
43
|
current_page
|
32
44
|
end
|
33
45
|
end
|
34
|
-
|
46
|
+
chain(
|
47
|
+
Pagination,
|
48
|
+
values,
|
35
49
|
from: ((length * (current_page - 1)) + padding),
|
36
50
|
size: length
|
37
|
-
|
38
|
-
%i(with without).each do |name|
|
39
|
-
if options.has_key?(name)
|
40
|
-
overrides[name] = options[name]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
chain Pagination, values, overrides
|
51
|
+
)
|
44
52
|
end
|
45
53
|
|
46
54
|
def order(options)
|
47
55
|
mappings = Indexes.configuration.mappings
|
48
|
-
|
56
|
+
values = []
|
49
57
|
options.each do |property, direction|
|
50
58
|
if block = Indexes.configuration.computed_sorts[property]
|
51
|
-
|
59
|
+
values << Dsl::Api.new(direction, &block).to_h
|
52
60
|
elsif property == :id
|
53
|
-
|
61
|
+
values << { _uid: { order: direction } }
|
54
62
|
elsif mappings.has_key?(property) && mappings[property][:type] == 'string'
|
55
|
-
|
63
|
+
values << { "#{property}.raw" => { order: direction } }
|
56
64
|
end
|
57
65
|
end
|
58
|
-
if
|
59
|
-
chain sort:
|
66
|
+
if values.any?
|
67
|
+
chain sort: values
|
60
68
|
else
|
61
69
|
chain
|
62
70
|
end
|
@@ -75,7 +83,7 @@ module Indexes
|
|
75
83
|
@query ||= begin
|
76
84
|
pagination = options.slice(:from, :size, :sort)
|
77
85
|
without_ids = fetch_ids(options[:without])
|
78
|
-
body = Dsl::Search.new(args
|
86
|
+
body = Dsl::Search.new(args, &block).to_h[:query]
|
79
87
|
request = Dsl::Search.new do
|
80
88
|
if without_ids.any?
|
81
89
|
query do
|
@@ -97,9 +105,9 @@ module Indexes
|
|
97
105
|
else
|
98
106
|
query body
|
99
107
|
end
|
100
|
-
%i(from size).each do |
|
101
|
-
if pagination.has_key?(
|
102
|
-
send
|
108
|
+
%i(from size).each do |name|
|
109
|
+
if pagination.has_key?(name)
|
110
|
+
send name, pagination[name]
|
103
111
|
end
|
104
112
|
end
|
105
113
|
if pagination.has_key?(:sort)
|
@@ -122,47 +130,30 @@ module Indexes
|
|
122
130
|
|
123
131
|
def records
|
124
132
|
@records ||= begin
|
133
|
+
hit_ids = response['hits']['hits'].map{ |hit| hit['_id'].to_i }
|
134
|
+
missing_ids = (fetch_ids(options[:with]) - hit_ids)
|
125
135
|
if missing_ids.any?
|
126
136
|
last_index = -(missing_ids.length + 1)
|
127
137
|
ids = (missing_ids.sort.reverse + hit_ids.to(last_index))
|
128
138
|
else
|
129
139
|
ids = hit_ids
|
130
140
|
end
|
141
|
+
includes = options.fetch(:includes, [])
|
131
142
|
index.model.includes(includes).where(id: ids).sort do |a,b|
|
132
143
|
ids.index(a.id) <=> ids.index(b.id)
|
133
144
|
end
|
134
145
|
end
|
135
146
|
end
|
136
147
|
|
137
|
-
def
|
138
|
-
|
139
|
-
end
|
140
|
-
|
141
|
-
def hit_ids
|
142
|
-
@hit_ids ||= response['hits']['hits'].map{ |hit| hit['_id'].to_i }
|
143
|
-
end
|
144
|
-
|
145
|
-
def missing_ids
|
146
|
-
@missing_ids ||= (with_ids - hit_ids)
|
147
|
-
end
|
148
|
-
|
149
|
-
def includes
|
150
|
-
@inclues ||= begin
|
151
|
-
if options.has_key?(:includes)
|
152
|
-
Array options[:includes]
|
153
|
-
else
|
154
|
-
[]
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def fetch_option(options, name)
|
160
|
-
options[name] || begin
|
148
|
+
def page_option(source, name, default)
|
149
|
+
source[name] || begin
|
161
150
|
if Rails.configuration.cache_classes == false
|
162
151
|
Rails.application.eager_load!
|
163
152
|
end
|
164
153
|
if defined?(Pagers)
|
165
|
-
Pagers.
|
154
|
+
Pagers.configuration.send name
|
155
|
+
else
|
156
|
+
default
|
166
157
|
end
|
167
158
|
end
|
168
159
|
end
|
@@ -184,11 +175,11 @@ module Indexes
|
|
184
175
|
|
185
176
|
def chain(*extensions)
|
186
177
|
overrides = extensions.extract_options!
|
187
|
-
Collection.new(index,
|
188
|
-
|
189
|
-
|
190
|
-
end
|
178
|
+
collection = Collection.new(index, args, options.merge(overrides), &block)
|
179
|
+
extensions.each do |extension|
|
180
|
+
collection.extend extension
|
191
181
|
end
|
182
|
+
collection
|
192
183
|
end
|
193
184
|
|
194
185
|
end
|