chewy 5.0.0 → 7.1.0

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