chewy 5.1.0 → 7.2.7

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