es_query_builder 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 90f99bbca380c475dd8e32b11b7d6bf8eaed0847
4
+ data.tar.gz: 5e0218899c63aa6ba9489fe6c254f84b1510f278
5
+ SHA512:
6
+ metadata.gz: be2e9c5a52e306fd76263b23752ea054249d8e5abb793115b8e30cd0150ab8a3e7a5ba2d115b9f0775683d6ed139f1ebbf6eaab2ab202b863606aae63676656c
7
+ data.tar.gz: b1f12714aa7d81e8c3b7f8386dc9a3bd90a0c927dcc94e349813de523c01c7a0f8267cb41ee13b167a743a0266ff7ac869cd96f5c8daed7d518953547d142f63
data/lib/constants.rb ADDED
@@ -0,0 +1,4 @@
1
+ module Constants
2
+ MAX_BULK_INDEX_SIZE = 2000
3
+ FUNCTION_SCORE_METHODS = %w(multiply sum avg first max min)
4
+ end
@@ -0,0 +1,462 @@
1
+ class ElasticSearchQuery
2
+
3
+
4
+ #### All Get Query ================================================================
5
+
6
+ # returns the structure for ids matching
7
+ def self.get_ids_query_structure
8
+ {
9
+ :ids => {
10
+ :values => []
11
+ }
12
+ }
13
+ end
14
+
15
+ # returns constant score filter structure
16
+ def self.get_constant_score_filter_structure filter = {}, boost = 1
17
+ {
18
+ :constant_score => {
19
+ :filter => filter,
20
+ :boost => boost
21
+ }
22
+ }
23
+ end
24
+
25
+ # returns constant score query structure
26
+ def self.get_constant_score_query_structure query = {}, boost = 1
27
+ {
28
+ constant_score: {
29
+ filter: {
30
+ bool: {
31
+ must: query
32
+ }
33
+ },
34
+ boost: boost
35
+ }
36
+ }
37
+ end
38
+
39
+ # returns exists filter
40
+ def self.get_exists_filter field
41
+ {
42
+ constant_score: {
43
+ filter: {
44
+ exists: {
45
+ field: field
46
+ }
47
+ }
48
+ }
49
+ }
50
+ end
51
+
52
+ # With Elasticsearch 6.1 nested filter has been replaced with nested query
53
+ # nested filter structure
54
+ def self.get_nested_filter_structure path, query = {}
55
+ {
56
+ :nested => {
57
+ :path => path,
58
+ :query => query
59
+ }
60
+ }
61
+ end
62
+
63
+ # returns nested query structure
64
+ def self.get_nested_query_structure path, query = {}, score_mode=nil
65
+ raise ArgumentError.new("path has to be a string") unless (path.is_a? String)
66
+ subquery = {
67
+ :nested => {
68
+ :path => path,
69
+ :query => query
70
+ }
71
+ }
72
+ if Constants::FUNCTION_SCORE_METHODS.include? score_mode
73
+ subquery[:nested][:score_mode] = score_mode
74
+ end
75
+ return subquery
76
+ end
77
+
78
+ # returns structure for match_phrase_prefix
79
+ def self.get_match_phrase_prefix_query field, prefix
80
+ raise ArgumentError.new("field and prefix should be strings") unless (field.is_a? String) && (prefix.is_a? String)
81
+ return {
82
+ :match_phrase_prefix => {
83
+ field => prefix
84
+ }
85
+ }
86
+ end
87
+
88
+ # term filter query
89
+ def self.get_term_filter_query(field, value, cache_flag = false)
90
+ {
91
+ term: {
92
+ field => value
93
+ }
94
+ }
95
+ end
96
+
97
+ # term boost query
98
+ def self.get_term_boost_query field, value, boost
99
+ {
100
+ :term => {
101
+ field => {
102
+ :value => value,
103
+ :boost => boost
104
+ }
105
+ }
106
+ }
107
+ end
108
+
109
+ # returns terms_filter_query
110
+ def self.get_terms_filter_query(field, value)
111
+ raise "Cannot append terms query to #{value} which is not an array" unless value.is_a?(Array)
112
+ {
113
+ terms: {
114
+ field => value
115
+ }
116
+ }
117
+ end
118
+
119
+ # returns filtered_structure
120
+ def self.get_filtered_structure
121
+ {
122
+ bool: {
123
+ must: [],
124
+ should: [],
125
+ must_not: [],
126
+ filter: {
127
+ bool: {
128
+ must: [],
129
+ should: [],
130
+ must_not: []
131
+ }
132
+ }
133
+ }
134
+ }
135
+ end
136
+
137
+ # returns range query
138
+ def self.get_range_query field, from, to
139
+ q = {
140
+ :range=> {
141
+ field => {}
142
+ }
143
+ }
144
+ q[:range][field][:from] = from if from.present?
145
+ q[:range][field][:to] = to if to.present?
146
+ return q
147
+ end
148
+
149
+ # returns nested terms query
150
+ def self.get_nested_terms_query(path, field, value)
151
+ raise "Cannot append terms query to #{value} which is not an array" unless value.is_a?(Array)
152
+ {
153
+ nested: {
154
+ path: path,
155
+ query: {
156
+ terms: {
157
+ field => value
158
+ }
159
+ }
160
+ }
161
+ }
162
+ end
163
+
164
+ # returns basic bool structure with should, must and mus_not clause
165
+ def self.get_bool_filter_structure
166
+ {
167
+ bool: {
168
+ must: [],
169
+ should: [],
170
+ must_not: []
171
+ }
172
+ }
173
+ end
174
+
175
+ # returns query bool structure
176
+ def self.get_query_bool_structure
177
+ {
178
+ query:{
179
+ bool: {
180
+ filter:{
181
+ }
182
+ }
183
+ }
184
+ }
185
+ end
186
+
187
+
188
+ # used to append sort query so that the result is sorted based on the sort_fields provided
189
+ def self.get_sort_subquery sort_fields=[]
190
+ raise "Cannot append sort query which is not an array" unless sort_fields.is_a?(Array)
191
+ sort = []
192
+ sort_fields.each do |field|
193
+ if field.is_a? Hash
194
+ field.each do |key, order|
195
+ next unless (order=="asc" || order=="desc")
196
+ sort << {key => {"order" => order}}
197
+ end
198
+ elsif (field.is_a? String) || (field.is_a? Symbol)
199
+ sort << field.to_s
200
+ end
201
+ end
202
+ return sort
203
+ end
204
+
205
+ # constraucts a top_hits structure
206
+ # this aggregator is intended to be used as a sub aggregator,
207
+ # so that the top matching documents can be aggregated per bucket
208
+ # @param name [String] name of the aggregation
209
+ # @param size [Integer] specifying number of top results to be returned
210
+ # @param sort [Array] specifying the sorting order
211
+ # @param source [Array] specifying the data fields to be present in the result
212
+ def self.get_top_hits_aggregations name, size, sort, source = []
213
+ query = {
214
+ name => {
215
+ "top_hits": {
216
+ "size": size
217
+ }
218
+ }
219
+ }.with_indifferent_access
220
+ query[name]["top_hits"]["sort"] = sort if sort.present?
221
+ if source.present?
222
+ query[name]["top_hits"]["_source"] = {
223
+ "include": source
224
+ }
225
+ end
226
+ query
227
+ end
228
+
229
+ # returns a reverse nested structure
230
+ def self.get_reverse_nested_aggs name, aggregations
231
+ {
232
+ name => {
233
+ "reverse_nested": {},
234
+ "aggs": aggregations
235
+ }
236
+ }
237
+ end
238
+
239
+ def self.get_terms_aggregation_structure name, field_name, include_array = [], script = "", size = nil
240
+ query = {
241
+ name => {
242
+ terms: {
243
+ field: field_name
244
+ }
245
+ }
246
+ }
247
+ query[name][:terms][:include] = include_array if include_array.present?
248
+ if script.present?
249
+ query[name][:terms].delete(:field)
250
+ query[name][:terms][:script] = script
251
+ end
252
+ if size.present?
253
+ size = 1 if size == 0 #size 0 is not supported since ES 5
254
+ query[name][:terms][:size] = size
255
+ end
256
+ query
257
+ end
258
+
259
+ # returns a generic metrics aggregation by providing the comparator
260
+ # @param name [String] name of the aggregation
261
+ # @param comparator [String] the metric to be used (eg. cardinality, avg)
262
+ # @param field_name [String] field on which aggregation is used
263
+ def self.get_metrics_aggregations_query name, comparator, field_name
264
+ query = {
265
+ name => {
266
+ comparator => {
267
+ field: field_name
268
+ }
269
+ }
270
+ }
271
+ end
272
+
273
+ # this helps to contruct a structure where we can use aggregations on the nested objects also.
274
+ # @param name [String] name of the aggregation
275
+ # @param path [String] path to the nested object
276
+ # @param field_name [String] field on which aggregation is used
277
+ # @param aggregation [Hash] specifying the aggregations (eg. Average)
278
+ def self.get_nested_aggregation_query name, path, aggregation
279
+ query = {
280
+ name => {
281
+ nested: {
282
+ path: path
283
+ },
284
+ aggregations: aggregation
285
+ }
286
+ }
287
+ end
288
+
289
+ # builds ids query with provided values
290
+ def self.get_ids_filter_query ids
291
+ ids_query_structure = get_ids_query_structure
292
+ ids_query_structure[:ids][:values] = ids
293
+ ids_query_structure
294
+ end
295
+
296
+
297
+ # constructs a structure that defines a single bucket which matches a specified filter
298
+ # @param name [String] name of the aggregation
299
+ # @param aggregation [Hash] specifying the aggregations (eg. Average)
300
+ # @params filter [Hash] the matching condition (eg. { "term": { "type": "t-shirt" } })
301
+ def self.filtered_aggregation name, aggregation, filter
302
+ {
303
+ :aggs => {
304
+ name.intern => {
305
+ filter: filter,
306
+ aggs: aggregation
307
+ }
308
+ }
309
+ }
310
+ end
311
+
312
+ # calculates percentiles based on the field and provided percentile points
313
+ # @param aggregation_name [String] name of the aggregation
314
+ # @param field [String] on which aggregation is to be performed
315
+ # @param percentile_points [Array] percentile points in which we are interested
316
+ def self.percentile_aggregation aggregation_name, field, percentile_points
317
+ {
318
+ aggregation_name.intern => {
319
+ :percentiles => {
320
+ :field => field,
321
+ :percents => percentile_points
322
+ }
323
+ }
324
+ }
325
+ end
326
+
327
+ # used for bucketing the response based on the field and range provided
328
+ # sample range [{ "to" : 100.0 },{ "from" : 100.0, "to" : 200.0 },{ "from" : 200.0 }]
329
+ # @param aggregation_name [String] name of the aggregation
330
+ # @param field [String] on which aggregation is to be performed
331
+ # @param ranges [Array] specifying ranges
332
+ def self.range_aggregation aggregation_name, field, ranges
333
+ {
334
+ aggregation_name.intern => {
335
+ :range => {
336
+ :field => field,
337
+ :ranges => ranges
338
+ }
339
+ }
340
+ }
341
+ end
342
+
343
+ # constructs an aggregation structure based on field_name provided
344
+ # this dynamically builds buckets on the basis of field_name and provides the aggregations accordingly
345
+ # if field_name = genre and genre has values (rock, jazz, thrash metal), then aggregations will be based on these three genres
346
+ # @param name [String] name of the aggregation
347
+ # @param field_name [String] on which aggregation is to be performed
348
+ # @param aggregation [Array] specifying aggregations
349
+ # @param include_array [Array] specifying conditions on the field_name
350
+ # @param script [Hash], to be executed for aggregation
351
+ def self.get_terms_structure_with_aggregation name, field_name, aggregation, include_array = [], script = ""
352
+ query = get_terms_aggregation_structure name, field_name, include_array, script
353
+ query[name][:aggs] = aggregation
354
+ return query
355
+ end
356
+
357
+ def self.function_score query, seed
358
+ {
359
+ function_score: {
360
+ query: query,
361
+ functions: [
362
+ {
363
+ random_score: {
364
+ seed: seed
365
+ }
366
+ }
367
+ ]
368
+ }
369
+ }
370
+ end
371
+
372
+ # returns a structure of dis_max query.
373
+ # @param queries [Array], array of queries used for union
374
+ def self.dis_max_query queries=[]
375
+ raise ArgumentError.new("queries is not an Array") unless queries.instance_of? Array
376
+ return {
377
+ dis_max: {
378
+ queries: queries
379
+ }
380
+ }
381
+ end
382
+
383
+ # to modify the score of documents that are retrieved by a query
384
+ # @param query [Hash]
385
+ # @param functions [Array] specifying the conditions and scores
386
+ # @param boost_mode [String] specifying boost_mode (replace, multiply)
387
+ def self.script_scoring_query(query, functions, boost_mode="replace")
388
+ return query.except(:query).merge({
389
+ query: {
390
+ function_score: {
391
+ query: query[:query],
392
+ functions: functions,
393
+ boost_mode: boost_mode
394
+ }
395
+ }
396
+ })
397
+ end
398
+
399
+ def self.get_script_score_function_structure
400
+ return {
401
+ script_score: {
402
+ script: {
403
+ params: {},
404
+ inline: ''
405
+ }
406
+ }
407
+ }
408
+ end
409
+
410
+ # get nested query to search on nested objects
411
+ def self.get_nested_exists_query field_name
412
+ {
413
+ nested: {
414
+ path: field_name,
415
+ query: {
416
+ match_all: {}
417
+ }
418
+ }
419
+ }
420
+ end
421
+
422
+ # this appends bools structure containing must, should and must_not in an existing query under the filter context
423
+ # if filter is not present in the existing query, the entire bool structure is assigned,
424
+ # if filter is present, then the bool structure is merged with the existing structure under the filter context
425
+ # finally the filter priovided in the method params is appended in the must clause
426
+ # @param query [Hash] main query
427
+ # @param filter [Hash] assigned in the must clause of the main query
428
+ def self.append_query_filter(query, filter)
429
+ bool_query = get_bool_filter_structure
430
+ if query[:query][:bool][:filter].nil?
431
+ query[:query][:bool][:filter] = bool_query
432
+ else
433
+ bool_query[:bool].each { |key, val|
434
+ query[:query][:bool][:filter][:bool][key] = [] unless query[:query][:bool][:filter][:bool].key? (key)
435
+ }
436
+ end
437
+ query[:query][:bool][:filter][:bool][:must].push(filter)
438
+ query
439
+ end
440
+
441
+ # sets the max number of results to be returned by the query
442
+ def self.append_size_filter(query, size)
443
+ query[:size] = size
444
+ query
445
+ end
446
+
447
+ # merges bool queries into the main_query
448
+ # @param main_query [Hash] query to be modified
449
+ # @param query [Hash] query whose bool params are to be merged in main_query
450
+ def self.merge_bool_query(main_query, query)
451
+ query[:bool].each { |key, val|
452
+ if main_query[:bool].key?(key)
453
+ main_query[:bool][key] = Array.new([main_query[:bool][key]]) << val
454
+ main_query[:bool][key].flatten!
455
+ else
456
+ main_query[:bool][key] = val
457
+ end
458
+ }
459
+ main_query
460
+ end
461
+
462
+ end
@@ -0,0 +1,30 @@
1
+ # having methods to fetch data from Elastic search
2
+ class FetchEsData
3
+
4
+ # initializing elastic search host and port
5
+ # @param search_host [String] Elastic Host
6
+ # @param search_port [String] Elastic port
7
+ # @return [FetchEsData] to perform search
8
+ def initialize(search_host, search_port)
9
+ @search_host = search_host
10
+ @search_port = search_port
11
+ end
12
+
13
+
14
+ # fetches data from elastic search
15
+ # @param query [String] the input
16
+ # @param index_name [String] name of the index
17
+ # @param type_name [String] index type
18
+ # @param extension [String] extension to Elastic seach path (eg. '_search', '_msearch')
19
+ # @return [String, Hash]
20
+ def fetch_shortlisted_data_from_es(query:, index_name:, type_name:, extension: '_search')
21
+ uri = URI("http://#{@search_host}:#{@search_port}/#{index_name}/#{type_name}/#{extension}")
22
+ req = Net::HTTP::Post.new(uri, initheader = {'Content-Type' =>'application/json'})
23
+ req.body = "#{query.to_json}\n"
24
+ res = Net::HTTP.start(uri.hostname, uri.port) do |http|
25
+ http.request(req)
26
+ end
27
+ body = JSON.parse(res.body) rescue {}
28
+ return res.code, body
29
+ end
30
+ end
@@ -0,0 +1,8 @@
1
+ require 'elastic_search_query'
2
+ require 'token_query_builder'
3
+ require 'fetch_es_data'
4
+ require 'indexer'
5
+ require 'constants'
6
+
7
+ module HousingEsQueryBuilder
8
+ end
data/lib/indexer.rb ADDED
@@ -0,0 +1,68 @@
1
+ # used to index data in almost realtime to Elastic Search
2
+
3
+ class Indexer
4
+
5
+ # get elastic client object
6
+ # @param host [String] Elastic Host
7
+ # @param port [String] Elastic Port
8
+ # @return [Indexer] to use indexing methods
9
+ def initialize(host, port)
10
+ @client = Elasticsearch::Client.new(host: host, port: port)
11
+ end
12
+
13
+ # expects data in the form of
14
+ # [{update:{_index: index, _type: _doc, _id: 23, data: {doc: data }}},{index: {_index: index2, _type: _doc, _id: 28, data: data}}]
15
+ # @param data [Array] Data to be indexed
16
+ def bulk_index(data)
17
+ check_bulk_index_params(data)
18
+ response = @client.bulk body: data
19
+ response = response.with_indifferent_access
20
+ if response[:errors] == true
21
+ raise "Not able to index with errors as #{(response["items"].map{|t| t["index"]["error"]}.compact)}"
22
+ end
23
+ end
24
+
25
+ # checks whether the record exists in the given index with given type and id
26
+ # @param index_name [String] name of the index
27
+ # @param type_name_name [String] name of the type
28
+ # @param id [String] doc_id
29
+ # @param parent_id [String] parent_id (optional)
30
+ # @return [True] if record found
31
+ # @return [False] if record not found
32
+ def record_exists?(index_name, type_name, id, parent_id = nil)
33
+ options_hash = generate_options_hash(index_name, type_name, id, parent_id)
34
+ @client.exists options_hash
35
+ end
36
+
37
+ # deletes the record if exists in the given index with given type and id,
38
+ # raises DocumentNotFoundException if record is not found
39
+ # @param index_name [String] name of the index
40
+ # @param index_name [String] name of the type
41
+ # @param id [String] doc_id
42
+ # @param parent_id [String] parent_id
43
+ def delete_record(index_name, type_name, id, parent_id = nil)
44
+ if record_exists?(index_name, type_name, id, parent_id)
45
+ @client.delete generate_options_hash(index_name, type_name, id, parent_id)
46
+ end
47
+ end
48
+
49
+
50
+ private
51
+
52
+ def generate_options_hash(name, type, id, parent_id)
53
+ options_hash = {
54
+ index: name,
55
+ type: type,
56
+ id: id
57
+ }
58
+ options_hash[:parent] = parent_id if parent_id
59
+ options_hash
60
+ end
61
+
62
+ def check_bulk_index_params(data)
63
+ raise "Please provide and array of documents" unless data.is_a?(Array)
64
+ count = data.size
65
+ raise "Record count should be less than #{Constants::MAX_BULK_INDEX_SIZE}" if count > Constants::MAX_BULK_INDEX_SIZE
66
+ end
67
+
68
+ end
@@ -0,0 +1,76 @@
1
+ require 'elastic_search_query'
2
+ class TokenQueryBuilder
3
+
4
+ # constructs match filter based on the field_name and analyzer provided
5
+ # @param analyzer [String] Type of analyzer to be used
6
+ # @param field_name [String] field to be searched
7
+ # @param query [String] input data
8
+ # @param opts [Hash] options to be provided for search (eg. {fuzziness: 0, prefix_length: 1, max_expansions: 20, operator: "and"})
9
+ # @return [Hash]
10
+ def self.construct_match_filter(analyzer, field_name, query, opts)
11
+ default_options = {fuzziness: 0, prefix_length: 1, max_expansions: 20, operator: "or"}
12
+ opts = default_options.merge(opts)
13
+ field = analyzer.present? ? "#{field_name}.#{analyzer}": "#{field_name}"
14
+ {
15
+ match: {
16
+ field => {
17
+ query: query,
18
+ operator: opts[:operator],
19
+ fuzziness: opts[:fuzziness],
20
+ prefix_length: opts[:prefix_length]
21
+ }
22
+ }
23
+ }
24
+ end
25
+
26
+ # wraps the query in the constant_score structure
27
+ # @param filters [Array] filters to be included
28
+ # @param boost [Float] to influence the relevance score
29
+ # @return [Hash]
30
+ def self.wrap_constant_score_query(filters: [], boost: 0)
31
+ {
32
+ constant_score: {
33
+ boost: boost,
34
+ filter: {
35
+ bool: {
36
+ should: filters
37
+ }
38
+ }
39
+ }
40
+ }
41
+ end
42
+
43
+ # constructs match filter based on keys array
44
+ # @param analyzer [String] Type of analyzer to be used
45
+ # @param query [String] input data
46
+ # @param opts [Hash] options to be provided for search (eg. {fuzziness: 0, prefix_length: 1, max_expansions: 20, operator: "and"})
47
+ # @param keys [Array] fields to be queried
48
+ # @param boost [Float] to influence the relevance score
49
+ # @return [Hash]
50
+ def self.cs_with_multiple_filter(analyzer, query, opts, keys = [], boost: 0)
51
+ filters = []
52
+ (keys.compact).each do |key|
53
+ filter = construct_match_filter(analyzer, key, query, opts)
54
+ filters.push(filter)
55
+ end
56
+ wrap_constant_score_query(filters: filters, boost: boost)
57
+ end
58
+
59
+ # constructs simgle term query
60
+ def self.cs_with_single_filter(query, key = "")
61
+ ElasticSearchQuery.get_term_filter_query key, query, true
62
+ end
63
+
64
+ # constructs a constant_score wrapped match query based on the analyzer provided
65
+ # @param analyzer [String] Type of analyzer to be used (Analyzer provided here must be defined in the index definition first)
66
+ # @param query [String] input data
67
+ # @param key [String] field to be searched
68
+ # @param alias_key [String] alias of the field to be searched
69
+ # @param boost [Float] to influence relevance score
70
+ # @param options [Hash] options to be provided for search (eg. {fuzziness: 0, prefix_length: 1, max_expansions: 20, operator: "and"})
71
+ # @return [Hash]
72
+ def self.constant_score_match_query(analyzer, query, key, alias_key = nil, boost = 1, options = {})
73
+ cs_with_multiple_filter(analyzer, query, options, [key, alias_key], boost: boost)
74
+ end
75
+
76
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: es_query_builder
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mohib Yousuf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 4.0.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 4.0.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: elasticsearch
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description:
48
+ email: mohib.yousuf@hotmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - lib/constants.rb
54
+ - lib/elastic_search_query.rb
55
+ - lib/fetch_es_data.rb
56
+ - lib/housing_es_query_builder.rb
57
+ - lib/indexer.rb
58
+ - lib/token_query_builder.rb
59
+ homepage: https://github.com/elarahq/es.query.builder
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 2.6.12
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: For Building Elastic Search Queries
83
+ test_files: []