search_flip 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,252 @@
1
+
2
+ module SearchFlip
3
+ # The SearchFlip::PostFilterable mixin provides chainable methods like
4
+ # #post_where, #post_exists, #post_range, etc to add and apply search
5
+ # filters after aggregations have already been calculated.
6
+ #
7
+ # @example
8
+ # query = ProductIndex.search("harry potter")
9
+ #
10
+ # query = query.aggregate(price_ranges: {
11
+ # range: {
12
+ # field: "price",
13
+ # ranges: [
14
+ # { key: "range1", from: 0, to: 20 },
15
+ # { key: "range2", from: 20, to: 50 },
16
+ # { key: "range3", from: 50, to: 100 }
17
+ # ]
18
+ # }
19
+ # })
20
+ #
21
+ # query = query.post_where(price: 20 ... 50)
22
+
23
+ module PostFilterable
24
+ def self.included(base)
25
+ base.class_eval do
26
+ attr_accessor :post_search_values, :post_must_values, :post_must_not_values, :post_should_values, :post_filter_values
27
+ end
28
+ end
29
+
30
+ # Adds a post query string query to the criteria while using AND as the
31
+ # default operator unless otherwise specified. Check out the
32
+ # ElasticSearch docs for further details.
33
+ #
34
+ # @example
35
+ # CommentIndex.aggregate(:user_id).post_search("message:hello OR message:worl*")
36
+ #
37
+ # @param q [String] The query string query
38
+ #
39
+ # @param options [Hash] Additional options for the query string query, like
40
+ # eg default_operator, default_field, etc.
41
+ #
42
+ # @return [SearchFlip::Criteria] A newly created extended criteria
43
+
44
+ def post_search(q, options = {})
45
+ raise(SearchFlip::NotSupportedError) if SearchFlip.version.to_i < 2
46
+
47
+ fresh.tap do |criteria|
48
+ criteria.post_search_values = (post_search_values || []) + [query_string: { query: q, :default_operator => :AND }.merge(options)] if q.to_s.strip.length > 0
49
+ end
50
+ end
51
+
52
+ # Adds post filters to your criteria for the supplied hash composed of
53
+ # field-to-filter mappings which specify terms, term or range filters,
54
+ # depending on the type of the respective hash value, namely array, range
55
+ # or scalar type like Fixnum, String, etc.
56
+ #
57
+ # @example Array values
58
+ # query = CommentIndex.aggregate("...")
59
+ # query = query.post_where(id: [1, 2, 3], state: ["approved", "declined"])
60
+ #
61
+ # @example Range values
62
+ # query = CommentIndex.aggregate("...")
63
+ # query = query.post_where(created_at: Time.parse("2016-01-01") .. Time.parse("2017-01-01"))
64
+ #
65
+ # @example Scalar types
66
+ # query = CommentIndex.aggregate("...")
67
+ # query = query.post_where(id: 1, message: "hello world")
68
+ #
69
+ # @param hash [Hash] A field-to-filter mapping specifying filter values for
70
+ # the respective fields
71
+ #
72
+ # @return [SearchFlip::Criteria] A newly created extended criteria
73
+
74
+ def post_where(hash)
75
+ hash.inject(fresh) do |memo, (key, value)|
76
+ if value.is_a?(Array)
77
+ memo.post_filter terms: { key => value }
78
+ elsif value.is_a?(Range)
79
+ memo.post_filter range: { key => { gte: value.min, lte: value.max } }
80
+ else
81
+ memo.post_filter term: { key => value }
82
+ end
83
+ end
84
+ end
85
+
86
+ # Adds post filters to exclude documents in accordance to the supplied hash
87
+ # composed of field-to-filter mappings. Check out #post_where for further
88
+ # details.
89
+ #
90
+ # @example Array values
91
+ # query = CommentIndex.aggregate("...")
92
+ # query = query.post_where_not(id: [1, 2, 3])
93
+ #
94
+ # @example Range values
95
+ # query = CommentIndex.aggregate("...")
96
+ # query = query.post_where_not(created_at: Time.parse("2016-01-01") .. Time.parse("2017-01-01"))
97
+ #
98
+ # @example
99
+ # query = CommentIndex.aggregate("...")
100
+ # query = query.post_where_not(state: "approved")
101
+ #
102
+ # @param hash [Hash] A field-to-filter mapping specifying filter values for the
103
+ # respective fields
104
+ #
105
+ # @return [SearchFlip::Criteria] A newly created extended criteria
106
+
107
+ def post_where_not(hash)
108
+ hash.inject(fresh) do |memo, (key,value)|
109
+ if value.is_a?(Array)
110
+ memo.post_must_not terms: { key => value }
111
+ elsif value.is_a?(Range)
112
+ memo.post_must_not range: { key => { gte: value.min, lte: value.max } }
113
+ else
114
+ memo.post_must_not term: { key => value }
115
+ end
116
+ end
117
+ end
118
+
119
+ # Adds raw post filter queries to the criteria.
120
+ #
121
+ # @example Raw post term filter query
122
+ # query = CommentIndex.aggregate("...")
123
+ # query = query.post_filter(term: { state: "new" })
124
+ #
125
+ # @example Raw post range filter query
126
+ # query = CommentIndex.aggregate("...")
127
+ # query = query.post_filter(range: { created_at: { gte: Time.parse("2016-01-01") }})
128
+ #
129
+ # @param args [Array, Hash] The raw filter query arguments
130
+ #
131
+ # @return [SearchFlip::Criteria] A newly created extended criteria
132
+
133
+ def post_filter(*args)
134
+ fresh.tap do |criteria|
135
+ criteria.post_filter_values = (post_filter_values || []) + args
136
+ end
137
+ end
138
+
139
+ # Adds raw post must queries to the criteria.
140
+ #
141
+ # @example Raw post term must query
142
+ # query = CommentIndex.aggregate("...")
143
+ # query = query.post_must(term: { state: "new" })
144
+ #
145
+ # @example Raw post range must query
146
+ # query = CommentIndex.aggregate("...")
147
+ # query = query.post_must(range: { created_at: { gte: Time.parse("2016-01-01") }})
148
+ #
149
+ # @param args [Array, Hash] The raw must query arguments
150
+ #
151
+ # @return [SearchFlip::Criteria] A newly created extended criteria
152
+
153
+ def post_must(*args)
154
+ fresh.tap do |criteria|
155
+ criteria.post_must_values = (post_must_values || []) + args
156
+ end
157
+ end
158
+
159
+ # Adds raw post must_not queries to the criteria.
160
+ #
161
+ # @example Raw post term must_not query
162
+ # query = CommentIndex.aggregate("...")
163
+ # query = query.post_must_not(term: { state: "new" })
164
+ #
165
+ # @example Raw post range must_not query
166
+ # query = CommentIndex.aggregate("...")
167
+ # query = query.post_must_not(range: { created_at: { gte: Time.parse("2016-01-01") }})
168
+ #
169
+ # @param args [Array, Hash] The raw must_not query arguments
170
+ #
171
+ # @return [SearchFlip::Criteria] A newly created extended criteria
172
+
173
+ def post_must_not(*args)
174
+ fresh.tap do |criteria|
175
+ criteria.post_must_not_values = (post_must_not_values || []) + args
176
+ end
177
+ end
178
+
179
+ # Adds raw post should queries to the criteria.
180
+ #
181
+ # @example Raw post term should query
182
+ # query = CommentIndex.aggregate("...")
183
+ # query = query.post_should(term: { state: "new" })
184
+ #
185
+ # @example Raw post range should query
186
+ # query = CommentIndex.aggregate("...")
187
+ # query = query.post_should(range: { created_at: { gte: Time.parse("2016-01-01") }})
188
+ #
189
+ # @param args [Array, Hash] The raw should query arguments
190
+ #
191
+ # @return [SearchFlip::Criteria] A newly created extended criteria
192
+
193
+ def post_should(*args)
194
+ fresh.tap do |criteria|
195
+ criteria.post_should_values = (post_should_values || []) + args
196
+ end
197
+ end
198
+
199
+ # Adds a post range filter to the criteria without being forced to specify
200
+ # the left and right end of the range, such that you can eg simply specify
201
+ # lt, lte, gt and gte. For fully specified ranges, you can easily use
202
+ # #post_where, etc. Check out the ElasticSearch docs for further details
203
+ # regarding the range filter.
204
+ #
205
+ # @example
206
+ # query = CommentIndex.aggregate("...")
207
+ # query = query.post_range(:created_at, gte: Time.parse("2016-01-01"))
208
+ #
209
+ # query = CommentIndex.aggregate("...")
210
+ # query = query.post_range(:likes_count, gt: 10, lt: 100)
211
+ #
212
+ # @param field [Symbol, String] The field name to specify the range for
213
+ # @param options [Hash] The range filter specification, like lt, lte, etc
214
+ #
215
+ # @return [SearchFlip::Criteria] A newly created extended criteria
216
+
217
+ def post_range(field, options = {})
218
+ post_filter range: { field => options }
219
+ end
220
+
221
+ # Adds a post exists filter to the criteria, which selects all documents
222
+ # for which the specified field has a non-null value.
223
+ #
224
+ # @example
225
+ # query = CommentIndex.aggregate("...")
226
+ # query = query.post_exists(:notified_at)
227
+ #
228
+ # @param field [Symbol, String] The field that should have a non-null value
229
+ #
230
+ # @return [SearchFlip::Criteria] A newly created extended criteria
231
+
232
+ def post_exists(field)
233
+ post_filter exists: { field: field }
234
+ end
235
+
236
+ # Adds a post exists not filter to the criteria, which selects all documents
237
+ # for which the specified field's value is null.
238
+ #
239
+ # @example
240
+ # query = CommentIndex.aggregate("...")
241
+ # query = query.post_exists_not(:notified_at)
242
+ #
243
+ # @param field [Symbol, String] The field that should have a null value
244
+ #
245
+ # @return [SearchFlip::Criteria] A newly created extended criteria
246
+
247
+ def post_exists_not(field)
248
+ post_must_not exists: { field: field }
249
+ end
250
+ end
251
+ end
252
+
@@ -0,0 +1,319 @@
1
+
2
+ module SearchFlip
3
+ # The SearchFlip::Response class wraps a raw SearchFlip response and
4
+ # decorates it with methods for aggregations, hits, records, pagination, etc.
5
+
6
+ class Response
7
+ extend Forwardable
8
+
9
+ attr_accessor :criteria, :response
10
+
11
+ # @api private
12
+ #
13
+ # Initializes a new response object for the provided criteria and raw
14
+ # ElasticSearch response.
15
+
16
+ def initialize(criteria, response)
17
+ self.criteria = criteria
18
+ self.response = response
19
+ end
20
+
21
+ # Returns the raw response, ie a hash derived from the ElasticSearch JSON
22
+ # response.
23
+ #
24
+ # @example
25
+ # CommentIndex.search("hello world").raw_response
26
+ # # => {"took"=>3, "timed_out"=>false, "_shards"=>"..."}
27
+ #
28
+ # @return [Hash] The raw response hash
29
+
30
+ def raw_response
31
+ response
32
+ end
33
+
34
+ # Returns the total number of results.
35
+ #
36
+ # @example
37
+ # CommentIndex.search("hello world").total_entries
38
+ # # => 13
39
+ #
40
+ # @return [Fixnum] The total number of results
41
+
42
+ def total_entries
43
+ hits["total"]
44
+ end
45
+
46
+ alias_method :total_count, :total_entries
47
+
48
+ # Returns whether or not the current page is the first page.
49
+ #
50
+ # @example
51
+ # CommentIndex.paginate(page: 1).first_page?
52
+ # # => true
53
+ #
54
+ # CommentIndex.paginate(page: 2).first_page?
55
+ # # => false
56
+ #
57
+ # @return [Boolean] Returns true if the current page is the
58
+ # first page or false otherwise
59
+
60
+ def first_page?
61
+ current_page == 1
62
+ end
63
+
64
+ # Returns whether or not the current page is the last page.
65
+ #
66
+ # @example
67
+ # CommentIndex.paginate(page: 100).last_page?
68
+ # # => true
69
+ #
70
+ # CommentIndex.paginate(page: 1).last_page?
71
+ # # => false
72
+ #
73
+ # @return [Boolean] Returns true if the current page is the
74
+ # last page or false otherwise
75
+
76
+ def last_page?
77
+ current_page == total_pages
78
+ end
79
+
80
+ # Returns whether or not the current page is out of range,
81
+ # ie. smaller than 1 or larger than #total_pages
82
+ #
83
+ # @example
84
+ # CommentIndex.paginate(page: 1_000_000).out_of_range?
85
+ # # => true
86
+ #
87
+ # CommentIndex.paginate(page: 1).out_of_range?
88
+ # # => false
89
+ #
90
+ # @return [Boolean] Returns true if the current page is out
91
+ # of range
92
+
93
+ def out_of_range?
94
+ current_page < 1 || current_page > total_pages
95
+ end
96
+
97
+ # Returns the current page number, useful for pagination.
98
+ #
99
+ # @example
100
+ # CommentIndex.search("hello world").paginate(page: 10).current_page
101
+ # # => 10
102
+ #
103
+ # @return [Fixnum] The current page number
104
+
105
+ def current_page
106
+ 1 + (criteria.offset_value_with_default / criteria.limit_value_with_default.to_f).ceil
107
+ end
108
+
109
+ # Returns the number of total pages for the current pagination settings, ie
110
+ # per page/limit settings.
111
+ #
112
+ # @example
113
+ # CommentIndex.search("hello world").paginate(per_page: 60).total_pages
114
+ # # => 5
115
+ #
116
+ # @return [Fixnum] The total number of pages
117
+
118
+ def total_pages
119
+ [(total_entries / criteria.limit_value_with_default.to_f).ceil, 1].max
120
+ end
121
+
122
+ # Returns the previous page number or nil if no previous page exists, ie if
123
+ # the current page is the first page.
124
+ #
125
+ # @example
126
+ # CommentIndex.search("hello world").paginate(page: 2).previous_page
127
+ # # => 1
128
+ #
129
+ # CommentIndex.search("hello world").paginate(page: 1).previous_page
130
+ # # => nil
131
+ #
132
+ # @return [Fixnum, nil] The previous page number
133
+
134
+ def previous_page
135
+ return nil if current_page <= 1
136
+ return total_pages if current_page > total_pages
137
+
138
+ current_page - 1
139
+ end
140
+
141
+ alias_method :prev_page, :previous_page
142
+
143
+ # Returns the next page number or nil if there is no next page, ie the
144
+ # current page is the last page.
145
+ #
146
+ # @example
147
+ # CommentIndex.search("hello world").paginate(page: 2).next_page
148
+ # # => 3
149
+ #
150
+ # @return [Fixnum, nil] The next page number
151
+
152
+ def next_page
153
+ return nil if current_page >= total_pages
154
+ return 1 if current_page < 1
155
+
156
+ return current_page + 1
157
+ end
158
+
159
+ # Returns the results, ie hits, wrapped in a SearchFlip::Result object
160
+ # which basically is a Hashie::Mash. Check out the Hashie docs for further
161
+ # details.
162
+ #
163
+ # @example
164
+ # CommentIndex.search("hello world").results
165
+ # # => [#<SearchFlip::Result ...>, ...]
166
+ #
167
+ # @return [Array] An array of results
168
+
169
+ def results
170
+ @results ||= hits["hits"].map { |hit| Result.new hit["_source"].merge(hit["highlight"] ? { highlight: hit["highlight"] } : {}) }
171
+ end
172
+
173
+ # Returns the named sugggetion, if a name is specified or alle suggestions.
174
+ #
175
+ # @example
176
+ # query = CommentIndex.suggest(:suggestion, text: "helo", term: { field: "message" })
177
+ # query.suggestions # => {"suggestion"=>[{"text"=>...}, ...]}
178
+ #
179
+ # @example Named suggestions
180
+ # query = CommentIndex.suggest(:suggestion, text: "helo", term: { field: "message" })
181
+ # query.suggestions(:sugestion).first["text"] # => "hello"
182
+ #
183
+ # @return [Hash, Array] The named suggestion or all suggestions
184
+
185
+ def suggestions(name = nil)
186
+ if name
187
+ response["suggest"][name.to_s].first["options"]
188
+ else
189
+ response["suggest"]
190
+ end
191
+ end
192
+
193
+ # Returns the hits returned by ElasticSearch.
194
+ #
195
+ # @example
196
+ # CommentIndex.search("hello world").hits
197
+ # # => {"total"=>3, "max_score"=>2.34, "hits"=>[{...}, ...]}
198
+ #
199
+ # @return [Hash] The hits returned by ElasticSearch
200
+
201
+ def hits
202
+ response["hits"]
203
+ end
204
+
205
+ # Returns the scroll id returned by ElasticSearch, that can be used in the
206
+ # following request to fetch the next batch of records.
207
+ #
208
+ # @example
209
+ # CommentIndex.scroll(timeout: "1m").scroll_id #=> "cXVlcnlUaGVuRmV0Y2..."
210
+ #
211
+ # @return [String] The scroll id returned by ElasticSearch
212
+
213
+ def scroll_id
214
+ response["_scroll_id"]
215
+ end
216
+
217
+ # Returns the database records, usually ActiveRecord objects, depending on
218
+ # the ORM you're using. The records are sorted using the order returned by
219
+ # ElasticSearch.
220
+ #
221
+ # @example
222
+ # CommentIndex.search("hello world").records # => [#<Comment ...>, ...]
223
+ #
224
+ # @return [Array] An array of database records
225
+
226
+ def records(options = {})
227
+ @records ||= begin
228
+ sort_map = ids.each_with_index.each_with_object({}) { |(id, index), hash| hash[id.to_s] = index }
229
+
230
+ scope.to_a.sort_by { |record| sort_map[criteria.target.record_id(record).to_s] }
231
+ end
232
+ end
233
+
234
+ # Builds and returns a scope for the array of ids in the current result set
235
+ # returned by ElasticSearch, including the eager load, preload and includes
236
+ # associations, if specified. A scope is eg an ActiveRecord::Relation,
237
+ # depending on the ORM you're using.
238
+ #
239
+ # @example
240
+ # CommentIndex.preload(:user).scope # => #<Comment::ActiveRecord_Criteria:0x0...>
241
+ #
242
+ # @return The scope for the array of ids in the current result set
243
+
244
+ def scope
245
+ res = criteria.target.fetch_records(ids)
246
+
247
+ res = res.includes(*criteria.includes_values) if criteria.includes_values
248
+ res = res.eager_load(*criteria.eager_load_values) if criteria.eager_load_values
249
+ res = res.preload(*criteria.preload_values) if criteria.preload_values
250
+
251
+ res
252
+ end
253
+
254
+ # Returns the array of ids returned by ElasticSearch for the current result
255
+ # set, ie the ids listed in the hits section of the response.
256
+ #
257
+ # @example
258
+ # CommentIndex.match_all.ids # => [20341, 12942, ...]
259
+ #
260
+ # @return The array of ids in the current result set
261
+
262
+ def ids
263
+ @ids ||= hits["hits"].map { |hit| hit["_id"] }
264
+ end
265
+
266
+ def_delegators :ids, :size, :count, :length
267
+
268
+ # Returns the response time in milliseconds of ElasticSearch specified in
269
+ # the took info of the response.
270
+ #
271
+ # @example
272
+ # CommentIndex.match_all.took # => 6
273
+ #
274
+ # @return [Fixnum] The ElasticSearch response time in milliseconds
275
+
276
+ def took
277
+ response["took"]
278
+ end
279
+
280
+ # Returns a single or all aggregations returned by ElasticSearch, depending
281
+ # on whether or not a name is specified. If no name is specified, the raw
282
+ # aggregation hash is simply returned. Contrary, if a name is specified,
283
+ # only this aggregation is returned. Moreover, if a name is specified and
284
+ # the aggregation includes a buckets section, a post-processed aggregation
285
+ # hash is returned.
286
+ #
287
+ # @example All aggregations
288
+ # CommentIndex.aggregate(:user_id).aggregations
289
+ # # => {"user_id"=>{..., "buckets"=>[{"key"=>4922, "doc_count"=>1129}, ...]}
290
+ #
291
+ # @example Specific and post-processed aggregations
292
+ # CommentIndex.aggregate(:user_id).aggregations(:user_id)
293
+ # # => {4922=>1129, ...}
294
+ #
295
+ # @return [Hash] Specific or all aggregations returned by ElasticSearch
296
+
297
+ def aggregations(name = nil)
298
+ return response["aggregations"] || {} unless name
299
+
300
+ @aggregations ||= {}
301
+
302
+ key = name.to_s
303
+
304
+ return @aggregations[key] if @aggregations.key?(key)
305
+
306
+ @aggregations[key] =
307
+ if response["aggregations"].nil? || response["aggregations"][key].nil?
308
+ Result.new
309
+ elsif response["aggregations"][key]["buckets"].is_a?(Array)
310
+ response["aggregations"][key]["buckets"].each_with_object({}) { |bucket, hash| hash[bucket["key"]] = Result.new(bucket) }
311
+ elsif response["aggregations"][key]["buckets"].is_a?(Hash)
312
+ Result.new response["aggregations"][key]["buckets"]
313
+ else
314
+ Result.new response["aggregations"][key]
315
+ end
316
+ end
317
+ end
318
+ end
319
+
@@ -0,0 +1,12 @@
1
+
2
+ module SearchFlip
3
+ # The SearchFlip::Result class basically is a hash wrapper that uses
4
+ # Hashie::Mash to provide convenient method access to the hash attributes.
5
+
6
+ class Result < Hashie::Mash
7
+ def self.disable_warnings?
8
+ true
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,31 @@
1
+
2
+ require "time"
3
+ require "date"
4
+ require "json"
5
+
6
+ class Time
7
+ def to_json
8
+ iso8601(6).to_json
9
+ end
10
+ end
11
+
12
+ class Date
13
+ def to_json
14
+ iso8601.to_json
15
+ end
16
+ end
17
+
18
+ class DateTime
19
+ def to_json
20
+ iso8601(6).to_json
21
+ end
22
+ end
23
+
24
+ if defined?(ActiveSupport)
25
+ class ActiveSupport::TimeWithZone
26
+ def to_json
27
+ iso8601(6).to_json
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,5 @@
1
+
2
+ module SearchFlip
3
+ VERSION = "1.0.0"
4
+ end
5
+
@@ -0,0 +1,82 @@
1
+
2
+ require "forwardable"
3
+ require "http"
4
+ require "hashie"
5
+ require "oj"
6
+ require "set"
7
+
8
+ require "search_flip/version"
9
+ require "search_flip/json"
10
+ require "search_flip/http_client"
11
+ require "search_flip/config"
12
+ require "search_flip/bulk"
13
+ require "search_flip/filterable"
14
+ require "search_flip/post_filterable"
15
+ require "search_flip/aggregatable"
16
+ require "search_flip/aggregation"
17
+ require "search_flip/criteria"
18
+ require "search_flip/response"
19
+ require "search_flip/result"
20
+ require "search_flip/index"
21
+ require "search_flip/model"
22
+
23
+ module SearchFlip
24
+ class NotSupportedError < StandardError; end
25
+ class ConnectionError < StandardError; end
26
+
27
+ class ResponseError < StandardError
28
+ attr_reader :code, :body
29
+
30
+ def initialize(code:, body:)
31
+ @code = code
32
+ @body = body
33
+ end
34
+
35
+ def to_s
36
+ "#{self.class.name} (#{code}): #{body}"
37
+ end
38
+ end
39
+
40
+ # Uses the ElasticSearch Multi Search API to execute multiple search requests
41
+ # within a single request. Raises SearchFlip::ResponseError in case any
42
+ # errors occur.
43
+ #
44
+ # @example
45
+ # SearchFlip.msearch [ProductIndex.match_all, CommentIndex.match_all]
46
+ #
47
+ # @param criterias [Array<SearchFlip::Criteria>] An array of search
48
+ # queries to execute in parallel
49
+ #
50
+ # @return [Array<SearchFlip::Response>] An array of responses
51
+
52
+ def self.msearch(criterias)
53
+ payload = criterias.flat_map do |criteria|
54
+ [SearchFlip::JSON.generate(index: criteria.target.index_name_with_prefix, type: criteria.target.type_name), SearchFlip::JSON.generate(criteria.request)]
55
+ end
56
+
57
+ payload = payload.join("\n")
58
+ payload << "\n"
59
+
60
+ SearchFlip::HTTPClient.headers(accept: "application/json", content_type: "application/x-ndjson").post("#{SearchFlip::Config[:base_url]}/_msearch", body: payload).parse["responses"].map.with_index do |response, index|
61
+ SearchFlip::Response.new(criterias[index], response)
62
+ end
63
+ end
64
+
65
+ # Used to manipulate, ie add and remove index aliases. Raises an
66
+ # SearchFlip::ResponseError in case any errors occur.
67
+ #
68
+ # @example
69
+ # ElasticSearch.post_aliases(actions: [
70
+ # { remove: { index: "test1", alias: "alias1" }},
71
+ # { add: { index: "test2", alias: "alias1" }}
72
+ # ])
73
+ #
74
+ # @param payload [Hash] The raw request payload
75
+ #
76
+ # @return [SearchFlip::Response] The raw response
77
+
78
+ def self.aliases(payload)
79
+ SearchFlip::HTTPClient.headers(accept: "application/json").post("#{SearchFlip::Config[:base_url]}/_aliases", body: SearchFlip::JSON.generate(payload))
80
+ end
81
+ end
82
+