indexers 4.1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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