chewy 0.10.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +240 -0
  3. data/.rubocop.yml +25 -25
  4. data/Appraisals +12 -10
  5. data/CHANGELOG.md +252 -263
  6. data/Gemfile +5 -1
  7. data/LICENSE.txt +1 -1
  8. data/README.md +142 -78
  9. data/chewy.gemspec +10 -12
  10. data/gemfiles/{rails.4.2.mongoid.5.1.gemfile → rails.5.2.activerecord.gemfile} +6 -4
  11. data/gemfiles/{rails.4.2.activerecord.gemfile → rails.5.2.mongoid.6.4.gemfile} +6 -4
  12. data/gemfiles/{rails.4.0.activerecord.gemfile → rails.6.0.activerecord.gemfile} +6 -3
  13. data/gemfiles/rails.6.1.activerecord.gemfile +19 -0
  14. data/gemfiles/sequel.4.45.gemfile +2 -2
  15. data/lib/chewy.rb +2 -1
  16. data/lib/chewy/backports/duplicable.rb +1 -1
  17. data/lib/chewy/config.rb +10 -39
  18. data/lib/chewy/fields/base.rb +40 -28
  19. data/lib/chewy/fields/root.rb +18 -11
  20. data/lib/chewy/index.rb +3 -1
  21. data/lib/chewy/index/actions.rb +27 -15
  22. data/lib/chewy/index/settings.rb +2 -0
  23. data/lib/chewy/index/specification.rb +12 -10
  24. data/lib/chewy/minitest/helpers.rb +6 -6
  25. data/lib/chewy/minitest/search_index_receiver.rb +17 -17
  26. data/lib/chewy/multi_search.rb +62 -0
  27. data/lib/chewy/railtie.rb +4 -4
  28. data/lib/chewy/rake_helper.rb +5 -5
  29. data/lib/chewy/rspec/update_index.rb +3 -5
  30. data/lib/chewy/search.rb +4 -11
  31. data/lib/chewy/search/loader.rb +1 -1
  32. data/lib/chewy/search/pagination/will_paginate.rb +4 -2
  33. data/lib/chewy/search/parameters.rb +24 -6
  34. data/lib/chewy/search/parameters/allow_partial_search_results.rb +27 -0
  35. data/lib/chewy/search/parameters/concerns/query_storage.rb +4 -3
  36. data/lib/chewy/search/parameters/indices.rb +123 -0
  37. data/lib/chewy/search/parameters/none.rb +1 -3
  38. data/lib/chewy/search/request.rb +100 -74
  39. data/lib/chewy/search/scrolling.rb +7 -6
  40. data/lib/chewy/stash.rb +30 -21
  41. data/lib/chewy/strategy/active_job.rb +1 -1
  42. data/lib/chewy/strategy/atomic.rb +1 -1
  43. data/lib/chewy/strategy/sidekiq.rb +1 -1
  44. data/lib/chewy/type.rb +5 -2
  45. data/lib/chewy/type/adapter/active_record.rb +1 -1
  46. data/lib/chewy/type/adapter/base.rb +9 -9
  47. data/lib/chewy/type/adapter/mongoid.rb +2 -4
  48. data/lib/chewy/type/adapter/orm.rb +7 -4
  49. data/lib/chewy/type/adapter/sequel.rb +5 -7
  50. data/lib/chewy/type/crutch.rb +1 -1
  51. data/lib/chewy/type/import.rb +13 -11
  52. data/lib/chewy/type/import/bulk_builder.rb +1 -1
  53. data/lib/chewy/type/import/bulk_request.rb +4 -2
  54. data/lib/chewy/type/import/journal_builder.rb +3 -3
  55. data/lib/chewy/type/import/routine.rb +3 -3
  56. data/lib/chewy/type/mapping.rb +42 -36
  57. data/lib/chewy/type/observe.rb +16 -12
  58. data/lib/chewy/type/syncer.rb +15 -14
  59. data/lib/chewy/type/witchcraft.rb +11 -7
  60. data/lib/chewy/type/wrapper.rb +14 -4
  61. data/lib/chewy/version.rb +1 -1
  62. data/lib/sequel/plugins/chewy_observe.rb +4 -19
  63. data/migration_guide.md +18 -0
  64. data/spec/chewy/config_spec.rb +16 -21
  65. data/spec/chewy/fields/base_spec.rb +70 -70
  66. data/spec/chewy/fields/root_spec.rb +56 -9
  67. data/spec/chewy/index/actions_spec.rb +63 -7
  68. data/spec/chewy/index/specification_spec.rb +25 -16
  69. data/spec/chewy/index_spec.rb +75 -45
  70. data/spec/chewy/journal_spec.rb +33 -29
  71. data/spec/chewy/minitest/search_index_receiver_spec.rb +11 -9
  72. data/spec/chewy/multi_search_spec.rb +85 -0
  73. data/spec/chewy/rake_helper_spec.rb +123 -95
  74. data/spec/chewy/rspec/update_index_spec.rb +47 -46
  75. data/spec/chewy/runtime_spec.rb +2 -2
  76. data/spec/chewy/search/pagination/kaminari_spec.rb +7 -3
  77. data/spec/chewy/search/pagination/will_paginate_spec.rb +9 -3
  78. data/spec/chewy/search/parameters/indices_spec.rb +190 -0
  79. data/spec/chewy/search/parameters/none_spec.rb +1 -1
  80. data/spec/chewy/search/parameters_spec.rb +21 -4
  81. data/spec/chewy/search/request_spec.rb +101 -70
  82. data/spec/chewy/search/response_spec.rb +27 -17
  83. data/spec/chewy/search/scrolling_spec.rb +25 -16
  84. data/spec/chewy/search_spec.rb +49 -35
  85. data/spec/chewy/stash_spec.rb +15 -13
  86. data/spec/chewy/strategy/active_job_spec.rb +15 -2
  87. data/spec/chewy/strategy/shoryuken_spec.rb +8 -2
  88. data/spec/chewy/strategy/sidekiq_spec.rb +6 -2
  89. data/spec/chewy/type/adapter/active_record_spec.rb +16 -4
  90. data/spec/chewy/type/import/bulk_builder_spec.rb +9 -94
  91. data/spec/chewy/type/import/journal_builder_spec.rb +17 -15
  92. data/spec/chewy/type/import_spec.rb +6 -0
  93. data/spec/chewy/type/mapping_spec.rb +51 -18
  94. data/spec/chewy/type/observe_spec.rb +4 -4
  95. data/spec/chewy/type/witchcraft_spec.rb +31 -0
  96. data/spec/chewy/type/wrapper_spec.rb +3 -1
  97. data/spec/chewy_spec.rb +0 -7
  98. data/spec/spec_helper.rb +5 -1
  99. data/spec/support/active_record.rb +20 -0
  100. metadata +46 -116
  101. data/.travis.yml +0 -53
  102. data/LEGACY_DSL.md +0 -497
  103. data/gemfiles/rails.4.1.activerecord.gemfile +0 -14
  104. data/gemfiles/rails.5.0.activerecord.gemfile +0 -15
  105. data/gemfiles/rails.5.0.mongoid.6.0.gemfile +0 -15
  106. data/gemfiles/rails.5.1.activerecord.gemfile +0 -15
  107. data/gemfiles/rails.5.1.mongoid.6.1.gemfile +0 -15
  108. data/lib/chewy/query.rb +0 -1098
  109. data/lib/chewy/query/compose.rb +0 -68
  110. data/lib/chewy/query/criteria.rb +0 -191
  111. data/lib/chewy/query/filters.rb +0 -227
  112. data/lib/chewy/query/loading.rb +0 -111
  113. data/lib/chewy/query/nodes/and.rb +0 -25
  114. data/lib/chewy/query/nodes/base.rb +0 -17
  115. data/lib/chewy/query/nodes/bool.rb +0 -34
  116. data/lib/chewy/query/nodes/equal.rb +0 -34
  117. data/lib/chewy/query/nodes/exists.rb +0 -20
  118. data/lib/chewy/query/nodes/expr.rb +0 -28
  119. data/lib/chewy/query/nodes/field.rb +0 -110
  120. data/lib/chewy/query/nodes/has_child.rb +0 -15
  121. data/lib/chewy/query/nodes/has_parent.rb +0 -15
  122. data/lib/chewy/query/nodes/has_relation.rb +0 -59
  123. data/lib/chewy/query/nodes/match_all.rb +0 -11
  124. data/lib/chewy/query/nodes/missing.rb +0 -20
  125. data/lib/chewy/query/nodes/not.rb +0 -25
  126. data/lib/chewy/query/nodes/or.rb +0 -25
  127. data/lib/chewy/query/nodes/prefix.rb +0 -19
  128. data/lib/chewy/query/nodes/query.rb +0 -20
  129. data/lib/chewy/query/nodes/range.rb +0 -63
  130. data/lib/chewy/query/nodes/raw.rb +0 -15
  131. data/lib/chewy/query/nodes/regexp.rb +0 -35
  132. data/lib/chewy/query/nodes/script.rb +0 -20
  133. data/lib/chewy/query/pagination.rb +0 -25
  134. data/spec/chewy/query/criteria_spec.rb +0 -700
  135. data/spec/chewy/query/filters_spec.rb +0 -201
  136. data/spec/chewy/query/loading_spec.rb +0 -124
  137. data/spec/chewy/query/nodes/and_spec.rb +0 -12
  138. data/spec/chewy/query/nodes/bool_spec.rb +0 -14
  139. data/spec/chewy/query/nodes/equal_spec.rb +0 -32
  140. data/spec/chewy/query/nodes/exists_spec.rb +0 -18
  141. data/spec/chewy/query/nodes/has_child_spec.rb +0 -59
  142. data/spec/chewy/query/nodes/has_parent_spec.rb +0 -59
  143. data/spec/chewy/query/nodes/match_all_spec.rb +0 -11
  144. data/spec/chewy/query/nodes/missing_spec.rb +0 -16
  145. data/spec/chewy/query/nodes/not_spec.rb +0 -13
  146. data/spec/chewy/query/nodes/or_spec.rb +0 -12
  147. data/spec/chewy/query/nodes/prefix_spec.rb +0 -16
  148. data/spec/chewy/query/nodes/query_spec.rb +0 -12
  149. data/spec/chewy/query/nodes/range_spec.rb +0 -32
  150. data/spec/chewy/query/nodes/raw_spec.rb +0 -11
  151. data/spec/chewy/query/nodes/regexp_spec.rb +0 -43
  152. data/spec/chewy/query/nodes/script_spec.rb +0 -15
  153. data/spec/chewy/query/pagination/kaminari_spec.rb +0 -5
  154. data/spec/chewy/query/pagination/will_paginate_spec.rb +0 -5
  155. data/spec/chewy/query/pagination_spec.rb +0 -39
  156. data/spec/chewy/query_spec.rb +0 -636
  157. data/spec/chewy/search/parameters/indices_boost_spec.rb +0 -83
@@ -1,14 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 4.1.0"
6
- gem "activesupport", "~> 4.1.0"
7
- gem "resque", require: false
8
- gem "shoryuken", require: false
9
- gem "sidekiq", require: false
10
- gem "kaminari", "~> 0.17.0", require: false
11
- gem "will_paginate", require: false
12
- gem "parallel", require: false
13
-
14
- gemspec path: "../"
@@ -1,15 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 5.0.0"
6
- gem "activesupport", "~> 5.0.0"
7
- gem "activejob", "~> 5.0.0"
8
- gem "resque", require: false
9
- gem "shoryuken", require: false
10
- gem "sidekiq", require: false
11
- gem "kaminari-core", "~> 1.0.0", require: false
12
- gem "will_paginate", require: false
13
- gem "parallel", require: false
14
-
15
- gemspec path: "../"
@@ -1,15 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "mongoid", "~> 6.0.0"
6
- gem "activesupport", "~> 5.0.0"
7
- gem "activejob", "~> 5.0.0"
8
- gem "resque", require: false
9
- gem "shoryuken", require: false
10
- gem "sidekiq", require: false
11
- gem "kaminari-core", "~> 1.0.0", require: false
12
- gem "will_paginate", require: false
13
- gem "parallel", require: false
14
-
15
- gemspec path: "../"
@@ -1,15 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 5.1.0"
6
- gem "activesupport", "~> 5.1.0"
7
- gem "activejob", "~> 5.1.0"
8
- gem "resque", require: false
9
- gem "shoryuken", require: false
10
- gem "sidekiq", require: false
11
- gem "kaminari-core", "~> 1.0.0", require: false
12
- gem "will_paginate", require: false
13
- gem "parallel", require: false
14
-
15
- gemspec path: "../"
@@ -1,15 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "mongoid", "~> 6.1.0"
6
- gem "activesupport", "~> 5.1.0"
7
- gem "activejob", "~> 5.1.0"
8
- gem "resque", require: false
9
- gem "shoryuken", require: false
10
- gem "sidekiq", require: false
11
- gem "kaminari-core", "~> 1.0.0", require: false
12
- gem "will_paginate", require: false
13
- gem "parallel", require: false
14
-
15
- gemspec path: "../"
data/lib/chewy/query.rb DELETED
@@ -1,1098 +0,0 @@
1
- require 'chewy/query/criteria'
2
- require 'chewy/query/filters'
3
- require 'chewy/query/loading'
4
- require 'chewy/query/pagination'
5
-
6
- module Chewy
7
- # Query allows you to create ES search requests with convenient
8
- # chainable DSL. Queries are lazy evaluated and might be merged.
9
- # The same DSL is used for whole index or individual types query build.
10
- #
11
- # UsersIndex.filter{ age < 42 }.query(text: {name: 'Alex'}).limit(20)
12
- # UsersIndex::User.filter{ age < 42 }.query(text: {name: 'Alex'}).limit(20)
13
- #
14
- class Query
15
- include Enumerable
16
- include Loading
17
- include Pagination
18
- include Chewy::Search::Scoping
19
-
20
- DELEGATED_METHODS = %i[
21
- explain query_mode filter_mode post_filter_mode
22
- timeout limit offset highlight min_score rescore facets script_score
23
- boost_factor weight random_score field_value_factor decay aggregations
24
- suggest none strategy query filter post_filter boost_mode
25
- score_mode order reorder only types delete_all find total
26
- total_count total_entries unlimited script_fields track_scores preference
27
- ].to_set.freeze
28
-
29
- delegate :each, :count, :size, to: :_collection
30
- alias_method :to_ary, :to_a
31
-
32
- attr_reader :_indexes, :_types, :options, :criteria
33
-
34
- def initialize(*indexes_or_types_and_options)
35
- @options = indexes_or_types_and_options.extract_options!
36
- @_types = indexes_or_types_and_options.select { |klass| klass < Chewy::Type }
37
- @_indexes = indexes_or_types_and_options.select { |klass| klass < Chewy::Index }
38
- @_indexes |= @_types.map(&:index)
39
- @criteria = Criteria.new
40
- end
41
-
42
- # Comparation with other query or collection
43
- # If other is collection - search request is executed and
44
- # result is used for comparation
45
- #
46
- # UsersIndex.filter(term: {name: 'Johny'}) == UsersIndex.filter(term: {name: 'Johny'}) # => true
47
- # UsersIndex.filter(term: {name: 'Johny'}) == UsersIndex.filter(term: {name: 'Johny'}).to_a # => true
48
- # UsersIndex.filter(term: {name: 'Johny'}) == UsersIndex.filter(term: {name: 'Winnie'}) # => false
49
- #
50
- def ==(other)
51
- super || other.is_a?(self.class) ? other.criteria == criteria : other == to_a
52
- end
53
-
54
- # Adds <tt>explain</tt> parameter to search request.
55
- #
56
- # UsersIndex.filter(term: {name: 'Johny'}).explain
57
- # UsersIndex.filter(term: {name: 'Johny'}).explain(true)
58
- # UsersIndex.filter(term: {name: 'Johny'}).explain(false)
59
- #
60
- # Calling explain without any arguments sets explanation flag to true.
61
- # With <tt>explain: true</tt>, every result object has <tt>_explanation</tt>
62
- # method
63
- #
64
- # UsersIndex::User.filter(term: {name: 'Johny'}).explain.first._explanation # => {...}
65
- #
66
- def explain(value = nil)
67
- chain { criteria.update_request_options explain: (value.nil? ? true : value) }
68
- end
69
-
70
- # Adds <tt>script_fields</tt> parameter to search request.
71
- # UsersIndex.script_fields(
72
- # distance: {
73
- # params: {
74
- # lat: 37.569976,
75
- # lon: -122.351591
76
- # },
77
- # script: "doc['coordinates'].distanceInMiles(lat, lon)"
78
- # }
79
- # )
80
- def script_fields(value)
81
- chain { criteria.update_script_fields(value) }
82
- end
83
-
84
- # Sets query compilation mode for search request.
85
- # Not used if only one filter for search is specified.
86
- # Possible values:
87
- #
88
- # * <tt>:must</tt>
89
- # Default value. Query compiles into a bool <tt>must</tt> query.
90
- #
91
- # Ex:
92
- #
93
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}})
94
- # # => {body: {
95
- # query: {bool: {must: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
96
- # }}
97
- #
98
- # * <tt>:should</tt>
99
- # Query compiles into a bool <tt>should</tt> query.
100
- #
101
- # Ex:
102
- #
103
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(:should)
104
- # # => {body: {
105
- # query: {bool: {should: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
106
- # }}
107
- #
108
- # * Any acceptable <tt>minimum_should_match</tt> value (1, '2', '75%')
109
- # Query compiles into a bool <tt>should</tt> query with <tt>minimum_should_match</tt> set.
110
- #
111
- # Ex:
112
- #
113
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode('50%')
114
- # # => {body: {
115
- # query: {bool: {
116
- # should: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
117
- # minimum_should_match: '50%'
118
- # }}
119
- # }}
120
- #
121
- # * <tt>:dis_max</tt>
122
- # Query compiles into a <tt>dis_max</tt> query.
123
- #
124
- # Ex:
125
- #
126
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(:dis_max)
127
- # # => {body: {
128
- # query: {dis_max: {queries: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
129
- # }}
130
- #
131
- # * Any Float value (0.0, 0.7, 1.0)
132
- # Query compiles into a <tt>dis_max</tt> query with <tt>tie_breaker</tt> option set.
133
- #
134
- # Ex:
135
- #
136
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(0.7)
137
- # # => {body: {
138
- # query: {dis_max: {
139
- # queries: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
140
- # tie_breaker: 0.7
141
- # }}
142
- # }}
143
- #
144
- # Default value for <tt>:query_mode</tt> might be changed
145
- # with <tt>Chewy.query_mode</tt> config option.
146
- #
147
- # Chewy.query_mode = :dis_max
148
- # Chewy.query_mode = '50%'
149
- #
150
- def query_mode(value)
151
- chain { criteria.update_options query_mode: value }
152
- end
153
-
154
- # Sets query compilation mode for search request.
155
- # Not used if only one filter for search is specified.
156
- # Possible values:
157
- #
158
- # * <tt>:and</tt>
159
- # Default value. Filter compiles into an <tt>and</tt> filter.
160
- #
161
- # Ex:
162
- #
163
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }
164
- # # => {body: {query: {filtered: {
165
- # query: {...},
166
- # filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
167
- # }}}}
168
- #
169
- # * <tt>:or</tt>
170
- # Filter compiles into an <tt>or</tt> filter.
171
- #
172
- # Ex:
173
- #
174
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:or)
175
- # # => {body: {query: {filtered: {
176
- # query: {...},
177
- # filter: {or: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
178
- # }}}}
179
- #
180
- # * <tt>:must</tt>
181
- # Filter compiles into a bool <tt>must</tt> filter.
182
- #
183
- # Ex:
184
- #
185
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:must)
186
- # # => {body: {query: {filtered: {
187
- # query: {...},
188
- # filter: {bool: {must: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
189
- # }}}}
190
- #
191
- # * <tt>:should</tt>
192
- # Filter compiles into a bool <tt>should</tt> filter.
193
- #
194
- # Ex:
195
- #
196
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:should)
197
- # # => {body: {query: {filtered: {
198
- # query: {...},
199
- # filter: {bool: {should: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
200
- # }}}}
201
- #
202
- # * Any acceptable <tt>minimum_should_match</tt> value (1, '2', '75%')
203
- # Filter compiles into bool <tt>should</tt> filter with <tt>minimum_should_match</tt> set.
204
- #
205
- # Ex:
206
- #
207
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode('50%')
208
- # # => {body: {query: {filtered: {
209
- # query: {...},
210
- # filter: {bool: {
211
- # should: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
212
- # minimum_should_match: '50%'
213
- # }}
214
- # }}}}
215
- #
216
- # Default value for <tt>:filter_mode</tt> might be changed
217
- # with <tt>Chewy.filter_mode</tt> config option.
218
- #
219
- # Chewy.filter_mode = :should
220
- # Chewy.filter_mode = '50%'
221
- #
222
- def filter_mode(value)
223
- chain { criteria.update_options filter_mode: value }
224
- end
225
-
226
- # Acts the same way as `filter_mode`, but used for `post_filter`.
227
- # Note that it fallbacks by default to `Chewy.filter_mode` if
228
- # `Chewy.post_filter_mode` is nil.
229
- #
230
- # UsersIndex.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode(:and)
231
- # UsersIndex.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode(:should)
232
- # UsersIndex.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode('50%')
233
- #
234
- def post_filter_mode(value)
235
- chain { criteria.update_options post_filter_mode: value }
236
- end
237
-
238
- # A search timeout, bounding the search request to be executed within the
239
- # specified time value and bail with the hits accumulated up to that point
240
- # when expired. Defaults to no timeout.
241
- #
242
- # By default, the coordinating node waits to receive a response from all
243
- # shards. If one node is having trouble, it could slow down the response to
244
- # all search requests.
245
- #
246
- # The timeout parameter tells the coordinating node how long it should wait
247
- # before giving up and just returning the results that it already has. It
248
- # can be better to return some results than none at all.
249
- #
250
- # The response to a search request will indicate whether the search timed
251
- # out and how many shards responded successfully:
252
- #
253
- # ...
254
- # "timed_out": true,
255
- # "_shards": {
256
- # "total": 5,
257
- # "successful": 4,
258
- # "failed": 1
259
- # },
260
- # ...
261
- #
262
- # The primary shard assigned to perform the index operation might not be
263
- # available when the index operation is executed. Some reasons for this
264
- # might be that the primary shard is currently recovering from a gateway or
265
- # undergoing relocation. By default, the index operation will wait on the
266
- # primary shard to become available for up to 1 minute before failing and
267
- # responding with an error. The timeout parameter can be used to explicitly
268
- # specify how long it waits.
269
- #
270
- # UsersIndex.timeout("5000ms")
271
- #
272
- # Timeout is not a circuit breaker.
273
- #
274
- # It should be noted that this timeout does not halt the execution of the
275
- # query, it merely tells the coordinating node to return the results
276
- # collected so far and to close the connection. In the background, other
277
- # shards may still be processing the query even though results have been
278
- # sent.
279
- #
280
- # Use the timeout because it is important to your SLA, not because you want
281
- # to abort the execution of long running queries.
282
- #
283
- def timeout(value)
284
- chain { criteria.update_request_options timeout: value }
285
- end
286
-
287
- # Sets elasticsearch <tt>size</tt> search request param
288
- # Default value is set in the elasticsearch and is 10.
289
- #
290
- # UsersIndex.filter{ name == 'Johny' }.limit(100)
291
- # # => {body: {
292
- # query: {...},
293
- # size: 100
294
- # }}
295
- #
296
- def limit(value = nil, &block)
297
- chain { criteria.update_request_options size: block || Integer(value) }
298
- end
299
-
300
- # Sets elasticsearch <tt>from</tt> search request param
301
- #
302
- # UsersIndex.filter{ name == 'Johny' }.offset(300)
303
- # # => {body: {
304
- # query: {...},
305
- # from: 300
306
- # }}
307
- #
308
- def offset(value = nil, &block)
309
- chain { criteria.update_request_options from: block || Integer(value) }
310
- end
311
-
312
- # Elasticsearch highlight query option support
313
- #
314
- # UsersIndex.query(...).highlight(fields: { ... })
315
- #
316
- def highlight(value)
317
- chain { criteria.update_request_options highlight: value }
318
- end
319
-
320
- # Elasticsearch rescore query option support
321
- #
322
- # UsersIndex.query(...).rescore(query: { ... })
323
- #
324
- def rescore(value)
325
- chain { criteria.update_request_options rescore: value }
326
- end
327
-
328
- # Elasticsearch minscore option support
329
- #
330
- # UsersIndex.query(...).min_score(0.5)
331
- #
332
- def min_score(value)
333
- chain { criteria.update_request_options min_score: value }
334
- end
335
-
336
- # Elasticsearch track_scores option support
337
- #
338
- # UsersIndex.query(...).track_scores(true)
339
- #
340
- def track_scores(value)
341
- chain { criteria.update_request_options track_scores: value }
342
- end
343
-
344
- # Adds facets section to the search request.
345
- # All the chained facets a merged and added to the
346
- # search request
347
- #
348
- # UsersIndex.facets(tags: {terms: {field: 'tags'}}).facets(ages: {terms: {field: 'age'}})
349
- # # => {body: {
350
- # query: {...},
351
- # facets: {tags: {terms: {field: 'tags'}}, ages: {terms: {field: 'age'}}}
352
- # }}
353
- #
354
- # If called parameterless - returns result facets from ES performing request.
355
- # Returns empty hash if no facets was requested or resulted.
356
- #
357
- def facets(params = nil)
358
- raise RemovedFeature, 'removed in elasticsearch 2.0' if Runtime.version >= '2.0'
359
- if params
360
- chain { criteria.update_facets params }
361
- else
362
- _response['facets'] || {}
363
- end
364
- end
365
-
366
- # Adds a script function to score the search request. All scores are
367
- # added to the search request and combinded according to
368
- # <tt>boost_mode</tt> and <tt>score_mode</tt>
369
- #
370
- # UsersIndex.script_score("doc['boost'].value", params: { modifier: 2 })
371
- # # => {body:
372
- # query: {
373
- # function_score: {
374
- # query: { ...},
375
- # functions: [{
376
- # script_score: {
377
- # script: "doc['boost'].value * modifier",
378
- # params: { modifier: 2 }
379
- # }
380
- # }
381
- # }]
382
- # } } }
383
- def script_score(script, options = {})
384
- scoring = {script_score: {script: script}.merge(options)}
385
- chain { criteria.update_scores scoring }
386
- end
387
-
388
- # Adds a boost factor to the search request. All scores are
389
- # added to the search request and combinded according to
390
- # <tt>boost_mode</tt> and <tt>score_mode</tt>
391
- #
392
- # This probably only makes sense if you specify a filter
393
- # for the boost factor as well
394
- #
395
- # UsersIndex.boost_factor(23, filter: { term: { foo: :bar} })
396
- # # => {body:
397
- # query: {
398
- # function_score: {
399
- # query: { ...},
400
- # functions: [{
401
- # boost_factor: 23,
402
- # filter: { term: { foo: :bar } }
403
- # }]
404
- # } } }
405
- def boost_factor(factor, options = {})
406
- scoring = options.merge(boost_factor: factor.to_i)
407
- chain { criteria.update_scores scoring }
408
- end
409
-
410
- # Add a weight scoring function to the search. All scores are
411
- # added to the search request and combinded according to
412
- # <tt>boost_mode</tt> and <tt>score_mode</tt>
413
- #
414
- # This probably only makes sense if you specify a filter
415
- # for the weight as well.
416
- #
417
- # UsersIndex.weight(23, filter: { term: { foo: :bar} })
418
- # # => {body:
419
- # query: {
420
- # function_score: {
421
- # query: { ...},
422
- # functions: [{
423
- # weight: 23,
424
- # filter: { term: { foo: :bar } }
425
- # }]
426
- # } } }
427
- def weight(factor, options = {})
428
- scoring = options.merge(weight: factor.to_i)
429
- chain { criteria.update_scores scoring }
430
- end
431
-
432
- # Adds a random score to the search request. All scores are
433
- # added to the search request and combinded according to
434
- # <tt>boost_mode</tt> and <tt>score_mode</tt>
435
- #
436
- # This probably only makes sense if you specify a filter
437
- # for the random score as well.
438
- #
439
- # If you do not pass in a seed value, Time.now will be used
440
- #
441
- # UsersIndex.random_score(23, filter: { foo: :bar})
442
- # # => {body:
443
- # query: {
444
- # function_score: {
445
- # query: { ...},
446
- # functions: [{
447
- # random_score: { seed: 23 },
448
- # filter: { foo: :bar }
449
- # }]
450
- # } } }
451
- def random_score(seed = Time.now, options = {})
452
- scoring = options.merge(random_score: {seed: seed.to_i})
453
- chain { criteria.update_scores scoring }
454
- end
455
-
456
- # Add a field value scoring to the search. All scores are
457
- # added to the search request and combinded according to
458
- # <tt>boost_mode</tt> and <tt>score_mode</tt>
459
- #
460
- # This function is only available in Elasticsearch 1.2 and
461
- # greater
462
- #
463
- # UsersIndex.field_value_factor(
464
- # {
465
- # field: :boost,
466
- # factor: 1.2,
467
- # modifier: :sqrt
468
- # }, filter: { foo: :bar})
469
- # # => {body:
470
- # query: {
471
- # function_score: {
472
- # query: { ...},
473
- # functions: [{
474
- # field_value_factor: {
475
- # field: :boost,
476
- # factor: 1.2,
477
- # modifier: :sqrt
478
- # },
479
- # filter: { foo: :bar }
480
- # }]
481
- # } } }
482
- def field_value_factor(settings, options = {})
483
- scoring = options.merge(field_value_factor: settings)
484
- chain { criteria.update_scores scoring }
485
- end
486
-
487
- # Add a decay scoring to the search. All scores are
488
- # added to the search request and combinded according to
489
- # <tt>boost_mode</tt> and <tt>score_mode</tt>
490
- #
491
- # The parameters have default values, but those may not
492
- # be very useful for most applications.
493
- #
494
- # UsersIndex.decay(
495
- # :gauss,
496
- # :field,
497
- # origin: '11, 12',
498
- # scale: '2km',
499
- # offset: '5km',
500
- # decay: 0.4,
501
- # filter: { foo: :bar})
502
- # # => {body:
503
- # query: {
504
- # gauss: {
505
- # query: { ...},
506
- # functions: [{
507
- # gauss: {
508
- # field: {
509
- # origin: '11, 12',
510
- # scale: '2km',
511
- # offset: '5km',
512
- # decay: 0.4
513
- # }
514
- # },
515
- # filter: { foo: :bar }
516
- # }]
517
- # } } }
518
- def decay(function, field, options = {})
519
- field_options = options.extract!(:origin, :scale, :offset, :decay).delete_if { |_, v| v.nil? }
520
- scoring = options.merge(function => {
521
- field => field_options
522
- })
523
- chain { criteria.update_scores scoring }
524
- end
525
-
526
- # Sets <tt>preference</tt> for request.
527
- # For instance, one can use <tt>preference=_primary</tt> to execute only on the primary shards.
528
- #
529
- # scope = UsersIndex.preference(:_primary)
530
- #
531
- def preference(value)
532
- chain { criteria.update_search_options preference: value }
533
- end
534
-
535
- # Sets elasticsearch <tt>aggregations</tt> search request param
536
- #
537
- # UsersIndex.filter{ name == 'Johny' }.aggregations(category_id: {terms: {field: 'category_ids'}})
538
- # # => {body: {
539
- # query: {...},
540
- # aggregations: {
541
- # terms: {
542
- # field: 'category_ids'
543
- # }
544
- # }
545
- # }}
546
- #
547
- def aggregations(params = nil)
548
- @_named_aggs ||= _build_named_aggs
549
- @_fully_qualified_named_aggs ||= _build_fqn_aggs
550
- if params
551
- params = {params => @_named_aggs[params]} if params.is_a?(Symbol)
552
- params = {params => _get_fully_qualified_named_agg(params)} if params.is_a?(String) && params =~ /\A\S+#\S+\.\S+\z/
553
- chain { criteria.update_aggregations params }
554
- else
555
- _response['aggregations'] || {}
556
- end
557
- end
558
- alias_method :aggs, :aggregations
559
-
560
- # In this simplest of implementations each named aggregation must be uniquely named
561
- def _build_named_aggs
562
- named_aggs = {}
563
- @_indexes.each do |index|
564
- index.types.each do |type|
565
- type._agg_defs.each do |agg_name, prc|
566
- named_aggs[agg_name] = prc.call
567
- end
568
- end
569
- end
570
- named_aggs
571
- end
572
-
573
- def _build_fqn_aggs
574
- named_aggs = {}
575
- @_indexes.each do |index|
576
- named_aggs[index.to_s.downcase] ||= {}
577
- index.types.each do |type|
578
- named_aggs[index.to_s.downcase][type.to_s.downcase] ||= {}
579
- type._agg_defs.each do |agg_name, prc|
580
- named_aggs[index.to_s.downcase][type.to_s.downcase][agg_name.to_s.downcase] = prc.call
581
- end
582
- end
583
- end
584
- named_aggs
585
- end
586
-
587
- def _get_fully_qualified_named_agg(str)
588
- parts = str.scan(/\A(\S+)#(\S+)\.(\S+)\z/).first
589
- idx = "#{parts[0]}index"
590
- type = "#{idx}::#{parts[1]}"
591
- agg_name = parts[2]
592
- @_fully_qualified_named_aggs[idx][type][agg_name]
593
- end
594
-
595
- # Sets elasticsearch <tt>suggest</tt> search request param
596
- #
597
- # UsersIndex.suggest(name: {text: 'Joh', term: {field: 'name'}})
598
- # # => {body: {
599
- # query: {...},
600
- # suggest: {
601
- # text: 'Joh',
602
- # term: {
603
- # field: 'name'
604
- # }
605
- # }
606
- # }}
607
- #
608
- def suggest(params = nil)
609
- if params
610
- chain { criteria.update_suggest params }
611
- else
612
- _response['suggest'] || {}
613
- end
614
- end
615
-
616
- # Marks the criteria as having zero documents. This scope always returns empty array
617
- # without touching the elasticsearch server.
618
- # All the chained calls of methods don't affect the result
619
- #
620
- # UsersIndex.none.to_a
621
- # # => []
622
- # UsersIndex.query(text: {name: 'Johny'}).none.to_a
623
- # # => []
624
- # UsersIndex.none.query(text: {name: 'Johny'}).to_a
625
- # # => []
626
- #
627
- def none
628
- chain { criteria.update_options none: true }
629
- end
630
-
631
- # Setups strategy for top-level filtered query
632
- #
633
- # UsersIndex.filter { name == 'Johny'}.strategy(:leap_frog)
634
- # # => {body: {
635
- # query: { filtered: {
636
- # filter: { term: { name: 'Johny' } },
637
- # strategy: 'leap_frog'
638
- # } }
639
- # }}
640
- #
641
- def strategy(value = nil)
642
- chain { criteria.update_options strategy: value }
643
- end
644
-
645
- # Adds one or more query to the search request
646
- # Internally queries are stored as an array
647
- # While the full query compilation this array compiles
648
- # according to <tt>:query_mode</tt> option value
649
- #
650
- # By default it joines inside <tt>must</tt> query
651
- # See <tt>#query_mode</tt> chainable method for more info.
652
- #
653
- # UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}})
654
- # UsersIndex::User.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}})
655
- # # => {body: {
656
- # query: {bool: {must: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
657
- # }}
658
- #
659
- # If only one query was specified, it will become a result
660
- # query as is, without joining.
661
- #
662
- # UsersIndex.query(text: {name: 'Johny'})
663
- # # => {body: {
664
- # query: {text: {name: 'Johny'}}
665
- # }}
666
- #
667
- def query(params)
668
- chain { criteria.update_queries params }
669
- end
670
-
671
- # Adds one or more filter to the search request
672
- # Internally filters are stored as an array
673
- # While the full query compilation this array compiles
674
- # according to <tt>:filter_mode</tt> option value
675
- #
676
- # By default it joins inside <tt>and</tt> filter
677
- # See <tt>#filter_mode</tt> chainable method for more info.
678
- #
679
- # Also this method supports block DSL.
680
- # See <tt>Chewy::Query::Filters</tt> for more info.
681
- #
682
- # UsersIndex.filter(term: {name: 'Johny'}).filter(range: {age: {lte: 42}})
683
- # UsersIndex::User.filter(term: {name: 'Johny'}).filter(range: {age: {lte: 42}})
684
- # UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }
685
- # # => {body: {query: {filtered: {
686
- # query: {...},
687
- # filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
688
- # }}}}
689
- #
690
- # If only one filter was specified, it will become a result
691
- # filter as is, without joining.
692
- #
693
- # UsersIndex.filter(term: {name: 'Johny'})
694
- # # => {body: {query: {filtered: {
695
- # query: {...},
696
- # filter: {term: {name: 'Johny'}}
697
- # }}}}
698
- #
699
- def filter(params = nil, &block)
700
- params = Filters.new(&block).__render__ if block
701
- chain { criteria.update_filters params }
702
- end
703
-
704
- # Adds one or more post_filter to the search request
705
- # Internally post_filters are stored as an array
706
- # While the full query compilation this array compiles
707
- # according to <tt>:post_filter_mode</tt> option value
708
- #
709
- # By default it joins inside <tt>and</tt> filter
710
- # See <tt>#post_filter_mode</tt> chainable method for more info.
711
- #
712
- # Also this method supports block DSL.
713
- # See <tt>Chewy::Query::Filters</tt> for more info.
714
- #
715
- # UsersIndex.post_filter(term: {name: 'Johny'}).post_filter(range: {age: {lte: 42}})
716
- # UsersIndex::User.post_filter(term: {name: 'Johny'}).post_filter(range: {age: {lte: 42}})
717
- # UsersIndex.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }
718
- # # => {body: {
719
- # post_filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
720
- # }}
721
- #
722
- # If only one post_filter was specified, it will become a result
723
- # post_filter as is, without joining.
724
- #
725
- # UsersIndex.post_filter(term: {name: 'Johny'})
726
- # # => {body: {
727
- # post_filter: {term: {name: 'Johny'}}
728
- # }}
729
- #
730
- def post_filter(params = nil, &block)
731
- params = Filters.new(&block).__render__ if block
732
- chain { criteria.update_post_filters params }
733
- end
734
-
735
- # Sets the boost mode for custom scoring/boosting.
736
- # Not used if no score functions are specified
737
- # Possible values:
738
- #
739
- # * <tt>:multiply</tt>
740
- # Default value. Query score and function result are multiplied.
741
- #
742
- # Ex:
743
- #
744
- # UsersIndex.boost_mode('multiply').script_score('doc['boost'].value')
745
- # # => {body: {query: function_score: {
746
- # query: {...},
747
- # boost_mode: 'multiply',
748
- # functions: [ ... ]
749
- # }}}
750
- #
751
- # * <tt>:replace</tt>
752
- # Only function result is used, query score is ignored.
753
- #
754
- # * <tt>:sum</tt>
755
- # Query score and function score are added.
756
- #
757
- # * <tt>:avg</tt>
758
- # Average of query and function score.
759
- #
760
- # * <tt>:max</tt>
761
- # Max of query and function score.
762
- #
763
- # * <tt>:min</tt>
764
- # Min of query and function score.
765
- #
766
- # Default value for <tt>:boost_mode</tt> might be changed
767
- # with <tt>Chewy.score_mode</tt> config option.
768
- def boost_mode(value)
769
- chain { criteria.update_options boost_mode: value }
770
- end
771
-
772
- # Sets the scoring mode for combining function scores/boosts
773
- # Not used if no score functions are specified.
774
- # Possible values:
775
- #
776
- # * <tt>:multiply</tt>
777
- # Default value. Scores are multiplied.
778
- #
779
- # Ex:
780
- #
781
- # UsersIndex.score_mode('multiply').script_score('doc['boost'].value')
782
- # # => {body: {query: function_score: {
783
- # query: {...},
784
- # score_mode: 'multiply',
785
- # functions: [ ... ]
786
- # }}}
787
- #
788
- # * <tt>:sum</tt>
789
- # Scores are summed.
790
- #
791
- # * <tt>:avg</tt>
792
- # Scores are averaged.
793
- #
794
- # * <tt>:first</tt>
795
- # The first function that has a matching filter is applied.
796
- #
797
- # * <tt>:max</tt>
798
- # Maximum score is used.
799
- #
800
- # * <tt>:min</tt>
801
- # Minimum score is used
802
- #
803
- # Default value for <tt>:score_mode</tt> might be changed
804
- # with <tt>Chewy.score_mode</tt> config option.
805
- #
806
- # Chewy.score_mode = :first
807
- #
808
- def score_mode(value)
809
- chain { criteria.update_options score_mode: value }
810
- end
811
-
812
- # Sets search request sorting
813
- #
814
- # UsersIndex.order(:first_name, :last_name).order(age: :desc).order(price: {order: :asc, mode: :avg})
815
- # # => {body: {
816
- # query: {...},
817
- # sort: ['first_name', 'last_name', {age: 'desc'}, {price: {order: 'asc', mode: 'avg'}}]
818
- # }}
819
- #
820
- def order(*params)
821
- chain { criteria.update_sort params }
822
- end
823
-
824
- # Cleans up previous search sorting and sets the new one
825
- #
826
- # UsersIndex.order(:first_name, :last_name).order(age: :desc).reorder(price: {order: :asc, mode: :avg})
827
- # # => {body: {
828
- # query: {...},
829
- # sort: [{price: {order: 'asc', mode: 'avg'}}]
830
- # }}
831
- #
832
- def reorder(*params)
833
- chain { criteria.update_sort params, purge: true }
834
- end
835
-
836
- # Sets search request field list
837
- #
838
- # UsersIndex.only(:first_name, :last_name).only(:age)
839
- # # => {body: {
840
- # query: {...},
841
- # fields: ['first_name', 'last_name', 'age']
842
- # }}
843
- #
844
- def only(*params)
845
- chain { criteria.update_fields params }
846
- end
847
-
848
- # Cleans up previous search field list and sets the new one
849
- #
850
- # UsersIndex.only(:first_name, :last_name).only!(:age)
851
- # # => {body: {
852
- # query: {...},
853
- # fields: ['age']
854
- # }}
855
- #
856
- def only!(*params)
857
- chain { criteria.update_fields params, purge: true }
858
- end
859
-
860
- # Specify types participating in the search result
861
- # Works via <tt>types</tt> filter. Always merged with another filters
862
- # with the <tt>and</tt> filter.
863
- #
864
- # UsersIndex.types(:admin, :manager).filters{ name == 'Johny' }.filters{ age <= 42 }
865
- # # => {body: {query: {filtered: {
866
- # query: {...},
867
- # filter: {and: [
868
- # {or: [
869
- # {type: {value: 'admin'}},
870
- # {type: {value: 'manager'}}
871
- # ]},
872
- # {term: {name: 'Johny'}},
873
- # {range: {age: {lte: 42}}}
874
- # ]}
875
- # }}}}
876
- #
877
- # UsersIndex.types(:admin, :manager).filters{ name == 'Johny' }.filters{ age <= 42 }.filter_mode(:or)
878
- # # => {body: {query: {filtered: {
879
- # query: {...},
880
- # filter: {and: [
881
- # {or: [
882
- # {type: {value: 'admin'}},
883
- # {type: {value: 'manager'}}
884
- # ]},
885
- # {or: [
886
- # {term: {name: 'Johny'}},
887
- # {range: {age: {lte: 42}}}
888
- # ]}
889
- # ]}
890
- # }}}}
891
- #
892
- def types(*params)
893
- chain { criteria.update_types params }
894
- end
895
-
896
- # Acts the same way as <tt>types</tt>, but cleans up previously set types
897
- #
898
- # UsersIndex.types(:admin).types!(:manager)
899
- # # => {body: {query: {filtered: {
900
- # query: {...},
901
- # filter: {type: {value: 'manager'}}
902
- # }}}}
903
- #
904
- def types!(*params)
905
- chain { criteria.update_types params, purge: true }
906
- end
907
-
908
- # Sets <tt>search_type</tt> for request.
909
- # For instance, one can use <tt>search_type=count</tt> to fetch only total count of documents or to fetch only aggregations without fetching documents.
910
- #
911
- # scope = UsersIndex.search_type(:count)
912
- # scope.count == 0 # no documents actually fetched
913
- # scope.total == 10 # but we know a total count of them
914
- #
915
- # scope = UsersIndex.aggs(max_age: { max: { field: 'age' } }).search_type(:count)
916
- # max_age = scope.aggs['max_age']['value']
917
- #
918
- def search_type(value)
919
- chain { criteria.update_search_options search_type: value }
920
- end
921
-
922
- # Merges two queries.
923
- # Merges all the values in criteria with the same rules as values added manually.
924
- #
925
- # scope1 = UsersIndex.filter{ name == 'Johny' }
926
- # scope2 = UsersIndex.filter{ age <= 42 }
927
- # scope3 = UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }
928
- #
929
- # scope1.merge(scope2) == scope3 # => true
930
- #
931
- def merge(other)
932
- chain { criteria.merge!(other.criteria) }
933
- end
934
-
935
- # Deletes all documents matching a query.
936
- #
937
- # UsersIndex.delete_all
938
- # UsersIndex.filter{ age <= 42 }.delete_all
939
- # UsersIndex::User.delete_all
940
- # UsersIndex::User.filter{ age <= 42 }.delete_all
941
- #
942
- def delete_all
943
- if Runtime.version >= '2.0'
944
- plugins = Chewy.client.nodes.info(plugins: true)['nodes'].values.map { |item| item['plugins'] }.flatten
945
- raise PluginMissing, 'install delete-by-query plugin' unless plugins.find { |item| item['name'] == 'delete-by-query' }
946
- end
947
-
948
- request = chain { criteria.update_options simple: true }.send(:_request)
949
-
950
- ActiveSupport::Notifications.instrument 'delete_query.chewy',
951
- request: request, indexes: _indexes, types: _types,
952
- index: _indexes.one? ? _indexes.first : _indexes,
953
- type: _types.one? ? _types.first : _types do
954
- if Runtime.version >= '2.0'
955
- path = Elasticsearch::API::Utils.__pathify(
956
- Elasticsearch::API::Utils.__listify(request[:index]),
957
- Elasticsearch::API::Utils.__listify(request[:type]),
958
- '/_query'
959
- )
960
- Chewy.client.perform_request(Elasticsearch::API::HTTP_DELETE, path, {}, request[:body]).body
961
- else
962
- Chewy.client.delete_by_query(request)
963
- end
964
- end
965
- end
966
-
967
- # Find all documents matching a query.
968
- #
969
- # UsersIndex.find(42)
970
- # UsersIndex.filter{ age <= 42 }.find(42)
971
- # UsersIndex::User.find(42)
972
- # UsersIndex::User.filter{ age <= 42 }.find(42)
973
- #
974
- # In all the previous examples find will return a single object.
975
- # To get a collection - pass an array of ids.
976
- #
977
- # UsersIndex::User.find(42, 7, 3) # array of objects with ids in [42, 7, 3]
978
- # UsersIndex::User.find([8, 13]) # array of objects with ids in [8, 13]
979
- # UsersIndex::User.find([42]) # array of the object with id == 42
980
- #
981
- def find(*ids)
982
- results = chain { criteria.update_options simple: true }.filter { _id == ids.flatten }.to_a
983
-
984
- raise Chewy::DocumentNotFound, "Could not find documents for ids #{ids.flatten}" if results.empty?
985
- ids.one? && !ids.first.is_a?(Array) ? results.first : results
986
- end
987
-
988
- # Returns true if there are at least one document that matches the query
989
- #
990
- # PlacesIndex.query(...).filter(...).exists?
991
- #
992
- def exists?
993
- search_type(:count).total > 0
994
- end
995
-
996
- # Sets limit to be equal to total documents count
997
- #
998
- # PlacesIndex.query(...).filter(...).unlimited
999
- #
1000
-
1001
- def unlimited
1002
- count_query = search_type(:count)
1003
- offset(0).limit { count_query.total }
1004
- end
1005
-
1006
- # Returns request total time elapsed as reported by elasticsearch
1007
- #
1008
- # UsersIndex.query(...).filter(...).took
1009
- #
1010
- def took
1011
- _response['took']
1012
- end
1013
-
1014
- # Returns request timed_out as reported by elasticsearch
1015
- #
1016
- # The timed_out value tells us whether the query timed out or not.
1017
- #
1018
- # By default, search requests do not timeout. If low response times are more
1019
- # important to you than complete results, you can specify a timeout as 10 or
1020
- # "10ms" (10 milliseconds), or "1s" (1 second). See #timeout method.
1021
- #
1022
- # UsersIndex.query(...).filter(...).timed_out
1023
- #
1024
- def timed_out
1025
- _response['timed_out']
1026
- end
1027
-
1028
- protected
1029
-
1030
- def initialize_clone(origin)
1031
- @criteria = origin.criteria.clone
1032
- reset
1033
- end
1034
-
1035
- private
1036
-
1037
- def chain(&block)
1038
- clone.tap { |q| q.instance_exec(&block) }
1039
- end
1040
-
1041
- def reset
1042
- @_request, @_response, @_results, @_collection = nil
1043
- end
1044
-
1045
- def _request
1046
- @_request ||= begin
1047
- request = criteria.request_body
1048
- request[:index] = _indexes_hash.keys
1049
- request[:type] = _types.map(&:type_name)
1050
- request
1051
- end
1052
- end
1053
-
1054
- def _response
1055
- @_response ||= ActiveSupport::Notifications.instrument 'search_query.chewy',
1056
- request: _request, indexes: _indexes, types: _types,
1057
- index: _indexes.one? ? _indexes.first : _indexes,
1058
- type: _types.one? ? _types.first : _types do
1059
- begin
1060
- Chewy.client.search(_request)
1061
- rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
1062
- raise e if e.message !~ /IndexMissingException/ && e.message !~ /index_not_found_exception/
1063
- {}
1064
- end
1065
- end
1066
- end
1067
-
1068
- def _results
1069
- @_results ||= (criteria.none? || _response == {} ? [] : _response['hits']['hits']).map do |hit|
1070
- _derive_type(hit['_index'], hit['_type']).build(hit)
1071
- end
1072
- end
1073
-
1074
- def _collection
1075
- @_collection ||= begin
1076
- _load_objects! if criteria.options[:preload]
1077
- if criteria.options[:preload] && criteria.options[:loaded_objects]
1078
- _results.map(&:_object)
1079
- else
1080
- _results
1081
- end
1082
- end
1083
- end
1084
-
1085
- def _derive_type(index, type)
1086
- (@types_cache ||= {})[[index, type]] ||= _derive_index(index).type(type)
1087
- end
1088
-
1089
- def _derive_index(index_name)
1090
- (@derive_index ||= {})[index_name] ||= _indexes_hash[index_name] ||
1091
- _indexes_hash[_indexes_hash.keys.sort_by(&:length).reverse.detect { |name| index_name.start_with?(name) }]
1092
- end
1093
-
1094
- def _indexes_hash
1095
- @_indexes_hash ||= _indexes.index_by(&:index_name)
1096
- end
1097
- end
1098
- end