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.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +313 -0
  4. data/Rakefile +19 -0
  5. data/lib/generators/indexers/indexer/indexer_generator.rb +23 -0
  6. data/lib/generators/indexers/indexer/templates/indexer.rb +12 -0
  7. data/lib/generators/indexers/install/install_generator.rb +19 -0
  8. data/lib/generators/indexers/install/templates/configuration.yml +15 -0
  9. data/lib/generators/indexers/install/templates/initializer.rb +6 -0
  10. data/lib/indexers/collection.rb +181 -0
  11. data/lib/indexers/computed_sorts.rb +19 -0
  12. data/lib/indexers/concern.rb +30 -0
  13. data/lib/indexers/configuration.rb +35 -0
  14. data/lib/indexers/definitions.rb +24 -0
  15. data/lib/indexers/dsl/api.rb +94 -0
  16. data/lib/indexers/dsl/mappings.rb +14 -0
  17. data/lib/indexers/dsl/search.rb +29 -0
  18. data/lib/indexers/dsl/serialization.rb +17 -0
  19. data/lib/indexers/dsl/traitable.rb +38 -0
  20. data/lib/indexers/extensions/active_record/base.rb +20 -0
  21. data/lib/indexers/indexer.rb +132 -0
  22. data/lib/indexers/pagination.rb +33 -0
  23. data/lib/indexers/proxy.rb +22 -0
  24. data/lib/indexers/railtie.rb +23 -0
  25. data/lib/indexers/version.rb +5 -0
  26. data/lib/indexers.rb +82 -0
  27. data/lib/tasks/indexers.rake +16 -0
  28. data/test/dsl_test.rb +127 -0
  29. data/test/dummy/Rakefile +5 -0
  30. data/test/dummy/app/assets/javascripts/application.js +13 -0
  31. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  32. data/test/dummy/app/controllers/application_controller.rb +5 -0
  33. data/test/dummy/app/helpers/application_helper.rb +2 -0
  34. data/test/dummy/app/indexers/product_indexer.rb +55 -0
  35. data/test/dummy/app/indexers/shop_indexer.rb +28 -0
  36. data/test/dummy/app/models/product.rb +5 -0
  37. data/test/dummy/app/models/shop.rb +5 -0
  38. data/test/dummy/app/views/layouts/application.html.erb +12 -0
  39. data/test/dummy/bin/bundle +4 -0
  40. data/test/dummy/bin/rails +5 -0
  41. data/test/dummy/bin/rake +5 -0
  42. data/test/dummy/bin/setup +30 -0
  43. data/test/dummy/config/application.rb +25 -0
  44. data/test/dummy/config/boot.rb +5 -0
  45. data/test/dummy/config/database.yml +10 -0
  46. data/test/dummy/config/database.yml.travis +3 -0
  47. data/test/dummy/config/elasticsearch.yml +15 -0
  48. data/test/dummy/config/environment.rb +5 -0
  49. data/test/dummy/config/environments/development.rb +41 -0
  50. data/test/dummy/config/environments/production.rb +79 -0
  51. data/test/dummy/config/environments/test.rb +42 -0
  52. data/test/dummy/config/initializers/assets.rb +11 -0
  53. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  54. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  55. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  56. data/test/dummy/config/initializers/indexers.rb +65 -0
  57. data/test/dummy/config/initializers/inflections.rb +16 -0
  58. data/test/dummy/config/initializers/mime_types.rb +4 -0
  59. data/test/dummy/config/initializers/session_store.rb +3 -0
  60. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  61. data/test/dummy/config/locales/en.yml +23 -0
  62. data/test/dummy/config/routes.rb +56 -0
  63. data/test/dummy/config/secrets.yml +22 -0
  64. data/test/dummy/config.ru +4 -0
  65. data/test/dummy/db/migrate/20161104164148_create_products.rb +14 -0
  66. data/test/dummy/db/migrate/20161104182219_create_shops.rb +9 -0
  67. data/test/dummy/db/schema.rb +36 -0
  68. data/test/dummy/log/development.log +114 -0
  69. data/test/dummy/log/test.log +20986 -0
  70. data/test/dummy/public/404.html +61 -0
  71. data/test/dummy/public/422.html +61 -0
  72. data/test/dummy/public/500.html +60 -0
  73. data/test/dummy/public/favicon.ico +0 -0
  74. data/test/generator_test.rb +26 -0
  75. data/test/index_test.rb +42 -0
  76. data/test/record_test.rb +25 -0
  77. data/test/search_test.rb +164 -0
  78. data/test/task_test.rb +31 -0
  79. data/test/test_helper.rb +14 -0
  80. 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,12 @@
1
+ Indexers.define :<%= singular_table_name %><%= class_name_option %> do
2
+
3
+ mappings do
4
+ end
5
+
6
+ serialize do |record|
7
+ end
8
+
9
+ search do |*args|
10
+ end
11
+
12
+ 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,15 @@
1
+ default: &default
2
+ hosts: localhost
3
+ port: 9200
4
+
5
+ development:
6
+ <<: *default
7
+
8
+ test:
9
+ <<: *default
10
+
11
+ production:
12
+ <<: *default
13
+
14
+ staging:
15
+ <<: *default
@@ -0,0 +1,6 @@
1
+ Indexers.configure do |config|
2
+
3
+ config.mappings do
4
+ end
5
+
6
+ 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,19 @@
1
+ module Indexers
2
+ class ComputedSorts
3
+
4
+ def add(name, &block)
5
+ registry[name] = block
6
+ end
7
+
8
+ def find(name)
9
+ registry[name]
10
+ end
11
+
12
+ private
13
+
14
+ def registry
15
+ @registry ||= {}
16
+ end
17
+
18
+ end
19
+ 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