chewy 0.10.1 → 7.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (243) hide show
  1. checksums.yaml +5 -5
  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 +74 -0
  7. data/.rubocop.yml +28 -23
  8. data/.rubocop_todo.yml +110 -22
  9. data/CHANGELOG.md +480 -298
  10. data/CODE_OF_CONDUCT.md +14 -0
  11. data/CONTRIBUTING.md +63 -0
  12. data/Gemfile +3 -5
  13. data/Guardfile +3 -1
  14. data/LICENSE.txt +1 -1
  15. data/README.md +571 -333
  16. data/chewy.gemspec +12 -15
  17. data/gemfiles/rails.5.2.activerecord.gemfile +11 -0
  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 +48 -77
  22. data/lib/chewy/errors.rb +4 -10
  23. data/lib/chewy/fields/base.rb +88 -16
  24. data/lib/chewy/fields/root.rb +15 -21
  25. data/lib/chewy/index/actions.rb +67 -38
  26. data/lib/chewy/{type → index}/adapter/active_record.rb +18 -4
  27. data/lib/chewy/{type → index}/adapter/base.rb +11 -12
  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/index/crutch.rb +40 -0
  32. data/lib/chewy/index/import/bulk_builder.rb +311 -0
  33. data/lib/chewy/{type → index}/import/bulk_request.rb +10 -9
  34. data/lib/chewy/{type → index}/import/journal_builder.rb +11 -12
  35. data/lib/chewy/{type → index}/import/routine.rb +19 -18
  36. data/lib/chewy/{type → index}/import.rb +82 -36
  37. data/lib/chewy/{type → index}/mapping.rb +63 -62
  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/settings.rb +2 -0
  42. data/lib/chewy/index/specification.rb +13 -10
  43. data/lib/chewy/{type → index}/syncer.rb +62 -63
  44. data/lib/chewy/{type → index}/witchcraft.rb +15 -9
  45. data/lib/chewy/{type → index}/wrapper.rb +16 -6
  46. data/lib/chewy/index.rb +68 -93
  47. data/lib/chewy/journal.rb +25 -14
  48. data/lib/chewy/minitest/helpers.rb +91 -18
  49. data/lib/chewy/minitest/search_index_receiver.rb +29 -33
  50. data/lib/chewy/multi_search.rb +62 -0
  51. data/lib/chewy/railtie.rb +8 -24
  52. data/lib/chewy/rake_helper.rb +141 -112
  53. data/lib/chewy/rspec/build_query.rb +12 -0
  54. data/lib/chewy/rspec/helpers.rb +55 -0
  55. data/lib/chewy/rspec/update_index.rb +58 -49
  56. data/lib/chewy/rspec.rb +2 -0
  57. data/lib/chewy/runtime.rb +1 -1
  58. data/lib/chewy/search/loader.rb +19 -41
  59. data/lib/chewy/search/parameters/allow_partial_search_results.rb +27 -0
  60. data/lib/chewy/search/parameters/collapse.rb +16 -0
  61. data/lib/chewy/search/parameters/concerns/query_storage.rb +6 -5
  62. data/lib/chewy/search/parameters/ignore_unavailable.rb +27 -0
  63. data/lib/chewy/search/parameters/indices.rb +78 -0
  64. data/lib/chewy/search/parameters/none.rb +1 -3
  65. data/lib/chewy/search/parameters/order.rb +6 -19
  66. data/lib/chewy/search/parameters/source.rb +5 -1
  67. data/lib/chewy/search/parameters/track_total_hits.rb +16 -0
  68. data/lib/chewy/search/parameters.rb +28 -8
  69. data/lib/chewy/search/query_proxy.rb +9 -2
  70. data/lib/chewy/search/request.rb +207 -157
  71. data/lib/chewy/search/response.rb +5 -5
  72. data/lib/chewy/search/scoping.rb +7 -8
  73. data/lib/chewy/search/scrolling.rb +14 -13
  74. data/lib/chewy/search.rb +7 -26
  75. data/lib/chewy/stash.rb +27 -29
  76. data/lib/chewy/strategy/active_job.rb +2 -2
  77. data/lib/chewy/strategy/atomic.rb +1 -1
  78. data/lib/chewy/strategy/atomic_no_refresh.rb +18 -0
  79. data/lib/chewy/strategy/base.rb +10 -0
  80. data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +148 -0
  81. data/lib/chewy/strategy/delayed_sidekiq/worker.rb +52 -0
  82. data/lib/chewy/strategy/delayed_sidekiq.rb +17 -0
  83. data/lib/chewy/strategy/lazy_sidekiq.rb +64 -0
  84. data/lib/chewy/strategy/sidekiq.rb +3 -2
  85. data/lib/chewy/strategy.rb +6 -19
  86. data/lib/chewy/version.rb +1 -1
  87. data/lib/chewy.rb +37 -80
  88. data/lib/generators/chewy/install_generator.rb +1 -1
  89. data/lib/tasks/chewy.rake +26 -32
  90. data/migration_guide.md +56 -0
  91. data/spec/chewy/config_spec.rb +27 -57
  92. data/spec/chewy/fields/base_spec.rb +457 -174
  93. data/spec/chewy/fields/root_spec.rb +24 -32
  94. data/spec/chewy/fields/time_fields_spec.rb +5 -5
  95. data/spec/chewy/index/actions_spec.rb +425 -60
  96. data/spec/chewy/{type → index}/adapter/active_record_spec.rb +110 -44
  97. data/spec/chewy/{type → index}/adapter/object_spec.rb +21 -6
  98. data/spec/chewy/index/aliases_spec.rb +3 -3
  99. data/spec/chewy/index/import/bulk_builder_spec.rb +494 -0
  100. data/spec/chewy/{type → index}/import/bulk_request_spec.rb +5 -12
  101. data/spec/chewy/{type → index}/import/journal_builder_spec.rb +22 -30
  102. data/spec/chewy/{type → index}/import/routine_spec.rb +19 -19
  103. data/spec/chewy/{type → index}/import_spec.rb +154 -95
  104. data/spec/chewy/index/mapping_spec.rb +135 -0
  105. data/spec/chewy/index/observe/active_record_methods_spec.rb +68 -0
  106. data/spec/chewy/index/observe/callback_spec.rb +139 -0
  107. data/spec/chewy/index/observe_spec.rb +143 -0
  108. data/spec/chewy/index/settings_spec.rb +3 -1
  109. data/spec/chewy/index/specification_spec.rb +32 -33
  110. data/spec/chewy/{type → index}/syncer_spec.rb +14 -19
  111. data/spec/chewy/{type → index}/witchcraft_spec.rb +34 -21
  112. data/spec/chewy/index/wrapper_spec.rb +100 -0
  113. data/spec/chewy/index_spec.rb +99 -114
  114. data/spec/chewy/journal_spec.rb +56 -101
  115. data/spec/chewy/minitest/helpers_spec.rb +122 -14
  116. data/spec/chewy/minitest/search_index_receiver_spec.rb +24 -26
  117. data/spec/chewy/multi_search_spec.rb +84 -0
  118. data/spec/chewy/rake_helper_spec.rb +325 -101
  119. data/spec/chewy/rspec/build_query_spec.rb +34 -0
  120. data/spec/chewy/rspec/helpers_spec.rb +61 -0
  121. data/spec/chewy/rspec/update_index_spec.rb +106 -102
  122. data/spec/chewy/runtime_spec.rb +2 -2
  123. data/spec/chewy/search/loader_spec.rb +19 -53
  124. data/spec/chewy/search/pagination/kaminari_examples.rb +3 -5
  125. data/spec/chewy/search/pagination/kaminari_spec.rb +1 -1
  126. data/spec/chewy/search/parameters/collapse_spec.rb +5 -0
  127. data/spec/chewy/search/parameters/ignore_unavailable_spec.rb +67 -0
  128. data/spec/chewy/search/parameters/indices_spec.rb +99 -0
  129. data/spec/chewy/search/parameters/none_spec.rb +1 -1
  130. data/spec/chewy/search/parameters/order_spec.rb +18 -11
  131. data/spec/chewy/search/parameters/query_storage_examples.rb +67 -21
  132. data/spec/chewy/search/parameters/search_after_spec.rb +4 -1
  133. data/spec/chewy/search/parameters/source_spec.rb +8 -2
  134. data/spec/chewy/search/parameters/track_total_hits_spec.rb +5 -0
  135. data/spec/chewy/search/parameters_spec.rb +39 -8
  136. data/spec/chewy/search/query_proxy_spec.rb +68 -17
  137. data/spec/chewy/search/request_spec.rb +360 -149
  138. data/spec/chewy/search/response_spec.rb +35 -25
  139. data/spec/chewy/search/scrolling_spec.rb +28 -26
  140. data/spec/chewy/search_spec.rb +73 -53
  141. data/spec/chewy/stash_spec.rb +16 -26
  142. data/spec/chewy/strategy/active_job_spec.rb +23 -10
  143. data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
  144. data/spec/chewy/strategy/atomic_spec.rb +9 -10
  145. data/spec/chewy/strategy/delayed_sidekiq_spec.rb +190 -0
  146. data/spec/chewy/strategy/lazy_sidekiq_spec.rb +214 -0
  147. data/spec/chewy/strategy/sidekiq_spec.rb +14 -10
  148. data/spec/chewy/strategy_spec.rb +19 -15
  149. data/spec/chewy_spec.rb +17 -110
  150. data/spec/spec_helper.rb +7 -22
  151. data/spec/support/active_record.rb +43 -5
  152. metadata +123 -198
  153. data/.travis.yml +0 -53
  154. data/Appraisals +0 -79
  155. data/LEGACY_DSL.md +0 -497
  156. data/gemfiles/rails.4.0.activerecord.gemfile +0 -14
  157. data/gemfiles/rails.4.1.activerecord.gemfile +0 -14
  158. data/gemfiles/rails.4.2.activerecord.gemfile +0 -15
  159. data/gemfiles/rails.4.2.mongoid.5.1.gemfile +0 -15
  160. data/gemfiles/rails.5.0.activerecord.gemfile +0 -15
  161. data/gemfiles/rails.5.0.mongoid.6.0.gemfile +0 -15
  162. data/gemfiles/rails.5.1.activerecord.gemfile +0 -15
  163. data/gemfiles/rails.5.1.mongoid.6.1.gemfile +0 -15
  164. data/gemfiles/sequel.4.45.gemfile +0 -11
  165. data/lib/chewy/backports/deep_dup.rb +0 -46
  166. data/lib/chewy/backports/duplicable.rb +0 -91
  167. data/lib/chewy/query/compose.rb +0 -68
  168. data/lib/chewy/query/criteria.rb +0 -191
  169. data/lib/chewy/query/filters.rb +0 -227
  170. data/lib/chewy/query/loading.rb +0 -111
  171. data/lib/chewy/query/nodes/and.rb +0 -25
  172. data/lib/chewy/query/nodes/base.rb +0 -17
  173. data/lib/chewy/query/nodes/bool.rb +0 -34
  174. data/lib/chewy/query/nodes/equal.rb +0 -34
  175. data/lib/chewy/query/nodes/exists.rb +0 -20
  176. data/lib/chewy/query/nodes/expr.rb +0 -28
  177. data/lib/chewy/query/nodes/field.rb +0 -110
  178. data/lib/chewy/query/nodes/has_child.rb +0 -15
  179. data/lib/chewy/query/nodes/has_parent.rb +0 -15
  180. data/lib/chewy/query/nodes/has_relation.rb +0 -59
  181. data/lib/chewy/query/nodes/match_all.rb +0 -11
  182. data/lib/chewy/query/nodes/missing.rb +0 -20
  183. data/lib/chewy/query/nodes/not.rb +0 -25
  184. data/lib/chewy/query/nodes/or.rb +0 -25
  185. data/lib/chewy/query/nodes/prefix.rb +0 -19
  186. data/lib/chewy/query/nodes/query.rb +0 -20
  187. data/lib/chewy/query/nodes/range.rb +0 -63
  188. data/lib/chewy/query/nodes/raw.rb +0 -15
  189. data/lib/chewy/query/nodes/regexp.rb +0 -35
  190. data/lib/chewy/query/nodes/script.rb +0 -20
  191. data/lib/chewy/query/pagination.rb +0 -25
  192. data/lib/chewy/query.rb +0 -1098
  193. data/lib/chewy/search/pagination/will_paginate.rb +0 -43
  194. data/lib/chewy/search/parameters/types.rb +0 -20
  195. data/lib/chewy/strategy/resque.rb +0 -27
  196. data/lib/chewy/strategy/shoryuken.rb +0 -40
  197. data/lib/chewy/type/actions.rb +0 -43
  198. data/lib/chewy/type/adapter/mongoid.rb +0 -69
  199. data/lib/chewy/type/adapter/sequel.rb +0 -95
  200. data/lib/chewy/type/crutch.rb +0 -32
  201. data/lib/chewy/type/import/bulk_builder.rb +0 -122
  202. data/lib/chewy/type/observe.rb +0 -78
  203. data/lib/chewy/type.rb +0 -117
  204. data/lib/sequel/plugins/chewy_observe.rb +0 -78
  205. data/spec/chewy/query/criteria_spec.rb +0 -700
  206. data/spec/chewy/query/filters_spec.rb +0 -201
  207. data/spec/chewy/query/loading_spec.rb +0 -124
  208. data/spec/chewy/query/nodes/and_spec.rb +0 -12
  209. data/spec/chewy/query/nodes/bool_spec.rb +0 -14
  210. data/spec/chewy/query/nodes/equal_spec.rb +0 -32
  211. data/spec/chewy/query/nodes/exists_spec.rb +0 -18
  212. data/spec/chewy/query/nodes/has_child_spec.rb +0 -59
  213. data/spec/chewy/query/nodes/has_parent_spec.rb +0 -59
  214. data/spec/chewy/query/nodes/match_all_spec.rb +0 -11
  215. data/spec/chewy/query/nodes/missing_spec.rb +0 -16
  216. data/spec/chewy/query/nodes/not_spec.rb +0 -13
  217. data/spec/chewy/query/nodes/or_spec.rb +0 -12
  218. data/spec/chewy/query/nodes/prefix_spec.rb +0 -16
  219. data/spec/chewy/query/nodes/query_spec.rb +0 -12
  220. data/spec/chewy/query/nodes/range_spec.rb +0 -32
  221. data/spec/chewy/query/nodes/raw_spec.rb +0 -11
  222. data/spec/chewy/query/nodes/regexp_spec.rb +0 -43
  223. data/spec/chewy/query/nodes/script_spec.rb +0 -15
  224. data/spec/chewy/query/pagination/kaminari_spec.rb +0 -5
  225. data/spec/chewy/query/pagination/will_paginate_spec.rb +0 -5
  226. data/spec/chewy/query/pagination_spec.rb +0 -39
  227. data/spec/chewy/query_spec.rb +0 -636
  228. data/spec/chewy/search/pagination/will_paginate_examples.rb +0 -63
  229. data/spec/chewy/search/pagination/will_paginate_spec.rb +0 -23
  230. data/spec/chewy/search/parameters/indices_boost_spec.rb +0 -83
  231. data/spec/chewy/search/parameters/types_spec.rb +0 -5
  232. data/spec/chewy/strategy/resque_spec.rb +0 -46
  233. data/spec/chewy/strategy/shoryuken_spec.rb +0 -64
  234. data/spec/chewy/type/actions_spec.rb +0 -50
  235. data/spec/chewy/type/adapter/mongoid_spec.rb +0 -372
  236. data/spec/chewy/type/adapter/sequel_spec.rb +0 -472
  237. data/spec/chewy/type/import/bulk_builder_spec.rb +0 -279
  238. data/spec/chewy/type/mapping_spec.rb +0 -142
  239. data/spec/chewy/type/observe_spec.rb +0 -137
  240. data/spec/chewy/type/wrapper_spec.rb +0 -98
  241. data/spec/chewy/type_spec.rb +0 -55
  242. data/spec/support/mongoid.rb +0 -93
  243. data/spec/support/sequel.rb +0 -80
@@ -23,7 +23,7 @@ module Chewy
23
23
  #
24
24
  # @return [Integer]
25
25
  def total
26
- @total ||= hits_root['total'] || 0
26
+ @total ||= hits_root.fetch('total', {}).fetch('value', 0)
27
27
  end
28
28
 
29
29
  # Response `max_score` field.
@@ -64,12 +64,12 @@ module Chewy
64
64
  end
65
65
  alias_method :aggregations, :aggs
66
66
 
67
- # {Chewy::Type} wrappers collection instantiated on top of hits.
67
+ # {Chewy::Index} wrappers collection instantiated on top of hits.
68
68
  #
69
- # @return [Array<Chewy::Type>]
69
+ # @return [Array<Chewy::Index>]
70
70
  def wrappers
71
71
  @wrappers ||= hits.map do |hit|
72
- @loader.derive_type(hit['_index'], hit['_type']).build(hit)
72
+ @loader.derive_index(hit['_index']).build(hit)
73
73
  end
74
74
  end
75
75
 
@@ -102,7 +102,7 @@ module Chewy
102
102
  # end
103
103
  # @see #wrappers
104
104
  # @see #objects
105
- # @return [{Chewy::Type => Object}] a hash with wrappers as keys and ORM/ODM objects as values
105
+ # @return [{Chewy::Index => Object}] a hash with wrappers as keys and ORM/ODM objects as values
106
106
  def object_hash
107
107
  @object_hash ||= wrappers.zip(objects).to_h
108
108
  end
@@ -9,17 +9,16 @@ module Chewy
9
9
  # query(match: {name: name})
10
10
  # end
11
11
  #
12
- # define_type :user do
13
- # def self.by_age(age)
14
- # filter(term: {age: age})
15
- # end
12
+ #
13
+ # def self.by_age(age)
14
+ # filter(term: {age: age})
16
15
  # end
17
16
  # end
18
17
  #
19
18
  # UsersIndex.limit(10).by_name('Martin')
20
19
  # # => <UsersIndex::Query {..., :body=>{:size=>10, :query=>{:match=>{:name=>"Martin"}}}}>
21
- # UsersIndex::User.limit(10).by_name('Martin').by_age(42)
22
- # # => <UsersIndex::User::Query {..., :body=>{:size=>10, :query=>{:bool=>{
20
+ # UsersIndex.limit(10).by_name('Martin').by_age(42)
21
+ # # => <UsersIndex::Query {..., :body=>{:size=>10, :query=>{:bool=>{
23
22
  # # :must=>{:match=>{:name=>"Martin"}},
24
23
  # # :filter=>{:term=>{:age=>42}}}}}}>
25
24
  module Scoping
@@ -28,9 +27,9 @@ module Chewy
28
27
  module ClassMethods
29
28
  # The scopes stack.
30
29
  #
31
- # @return [Array<Chewy::Search::Reques>] array of scopes
30
+ # @return [Array<Chewy::Search::Request>] array of scopes
32
31
  def scopes
33
- Thread.current[:chewy_scopes] ||= []
32
+ Chewy.current[:chewy_scopes] ||= []
34
33
  end
35
34
  end
36
35
 
@@ -28,19 +28,23 @@ module Chewy
28
28
  return enum_for(:scroll_batches, batch_size: batch_size, scroll: scroll) unless block_given?
29
29
 
30
30
  result = perform(size: batch_size, scroll: scroll)
31
- total = [raw_limit_value, result.fetch('hits', {}).fetch('total', 0)].compact.min
31
+ total = [raw_limit_value, result.fetch('hits', {}).fetch('total', {}).fetch('value', 0)].compact.min
32
32
  last_batch_size = total % batch_size
33
33
  fetched = 0
34
+ scroll_id = nil
34
35
 
35
36
  loop do
36
37
  hits = result.fetch('hits', {}).fetch('hits', [])
37
38
  fetched += hits.size
38
39
  hits = hits.first(last_batch_size) if last_batch_size != 0 && fetched >= total
39
40
  yield(hits) if hits.present?
40
- break if fetched >= total
41
41
  scroll_id = result['_scroll_id']
42
+ break if fetched >= total
43
+
42
44
  result = perform_scroll(scroll: scroll, scroll_id: scroll_id)
43
45
  end
46
+ ensure
47
+ Chewy.client.clear_scroll(body: {scroll_id: scroll_id}) if scroll_id
44
48
  end
45
49
 
46
50
  # @!method scroll_hits(batch_size: 1000, scroll: '1m')
@@ -58,17 +62,17 @@ module Chewy
58
62
  # @example
59
63
  # PlaceIndex.scroll_hits.map { |hit| hit['_id'] }
60
64
  # @return [Enumerator] a standard ruby Enumerator
61
- def scroll_hits(**options)
65
+ def scroll_hits(**options, &block)
62
66
  return enum_for(:scroll_hits, **options) unless block_given?
63
67
 
64
68
  scroll_batches(**options).each do |batch|
65
- batch.each { |hit| yield hit }
69
+ batch.each(&block)
66
70
  end
67
71
  end
68
72
 
69
73
  # @!method scroll_wrappers(batch_size: 1000, scroll: '1m')
70
74
  # Iterates through the documents of the scope in batches. Yields
71
- # each hit wrapped with {Chewy::Type}.
75
+ # each hit wrapped with {Chewy::Index}.
72
76
  #
73
77
  # @param batch_size [Integer] batch size obviously, replaces `size` query parameter
74
78
  # @param scroll [String] cursor expiration time
@@ -76,7 +80,7 @@ module Chewy
76
80
  # @overload scroll_wrappers(batch_size: 1000, scroll: '1m')
77
81
  # @example
78
82
  # PlaceIndex.scroll_wrappers { |object| p object.id }
79
- # @yieldparam object [Chewy::Type] block is executed for each hit object
83
+ # @yieldparam object [Chewy::Index] block is executed for each hit object
80
84
  #
81
85
  # @overload scroll_wrappers(batch_size: 1000, scroll: '1m')
82
86
  # @example
@@ -86,7 +90,7 @@ module Chewy
86
90
  return enum_for(:scroll_wrappers, **options) unless block_given?
87
91
 
88
92
  scroll_hits(**options).each do |hit|
89
- yield loader.derive_type(hit['_index'], hit['_type']).build(hit)
93
+ yield loader.derive_index(hit['_index']).build(hit)
90
94
  end
91
95
  end
92
96
 
@@ -110,12 +114,12 @@ module Chewy
110
114
  # @example
111
115
  # PlaceIndex.scroll_objects.map { |record| record.id }
112
116
  # @return [Enumerator] a standard ruby Enumerator
113
- def scroll_objects(**options)
117
+ def scroll_objects(**options, &block)
114
118
  return enum_for(:scroll_objects, **options) unless block_given?
115
119
 
116
120
  except(:source, :stored_fields, :script_fields, :docvalue_fields)
117
121
  .source(false).scroll_batches(**options).each do |batch|
118
- loader.load(batch).each { |object| yield object }
122
+ loader.load(batch).each(&block)
119
123
  end
120
124
  end
121
125
  alias_method :scroll_records, :scroll_objects
@@ -124,10 +128,7 @@ module Chewy
124
128
  private
125
129
 
126
130
  def perform_scroll(body)
127
- ActiveSupport::Notifications.instrument 'search_query.chewy',
128
- request: body, indexes: _indexes, types: _types,
129
- index: _indexes.one? ? _indexes.first : _indexes,
130
- type: _types.one? ? _types.first : _types do
131
+ ActiveSupport::Notifications.instrument 'search_query.chewy', notification_payload(request: body) do
131
132
  Chewy.client.scroll(body)
132
133
  end
133
134
  end
data/lib/chewy/search.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'chewy/search/scoping'
2
- require 'chewy/query'
3
2
  require 'chewy/search/scrolling'
4
3
  require 'chewy/search/query_proxy'
5
4
  require 'chewy/search/parameters'
@@ -7,31 +6,22 @@ require 'chewy/search/response'
7
6
  require 'chewy/search/loader'
8
7
  require 'chewy/search/request'
9
8
  require 'chewy/search/pagination/kaminari'
10
- require 'chewy/search/pagination/will_paginate'
11
9
 
12
10
  module Chewy
13
11
  # This module being included to any provides an interface to the
14
- # request DSL. By default it is included to {Chewy::Index} and
15
- # {Chewy::Type}.
12
+ # request DSL. By default it is included to {Chewy::Index}.
16
13
  #
17
14
  # The class used as a request DSL provider is
18
- # inherited from {Chewy::Search::Request} by default, but if you
19
- # need ES < 2.0 DSL support - you can switch it to {Chewy::Query}
20
- # using {Chewy::Config#search_class}
15
+ # inherited from {Chewy::Search::Request}
21
16
  #
22
- # Also, the search class is refined with one of the pagination-
23
- # providing modules: {Chewy::Search::Pagination::Kaminari} or
24
- # {Chewy::Search::Pagination::WillPaginate}.
17
+ # Also, the search class is refined with the pagination module {Chewy::Search::Pagination::Kaminari}.
25
18
  #
26
19
  # @example
27
20
  # PlacesIndex.query(match: {name: 'Moscow'})
28
- # PlacesIndex::City.query(match: {name: 'Moscow'})
29
21
  # @see Chewy::Index
30
- # @see Chewy::Type
31
22
  # @see Chewy::Search::Request
32
23
  # @see Chewy::Search::ClassMethods
33
24
  # @see Chewy::Search::Pagination::Kaminari
34
- # @see Chewy::Search::Pagination::WillPaginate
35
25
  module Search
36
26
  extend ActiveSupport::Concern
37
27
 
@@ -58,20 +48,15 @@ module Chewy
58
48
  # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html
59
49
  # @return [Hash] the request result
60
50
  def search_string(query, options = {})
61
- options = options.merge(
62
- index: all._indexes.map(&:index_name),
63
- type: all._types.map(&:type_name),
64
- q: query
65
- )
51
+ options = options.merge(all.render.slice(:index).merge(q: query))
66
52
  Chewy.client.search(options)
67
53
  end
68
54
 
69
- # Delegates methods from the request class to the index or type class
55
+ # Delegates methods from the request class to the index class
70
56
  #
71
57
  # @example
72
58
  # PlacesIndex.query(match: {name: 'Moscow'})
73
- # PlacesIndex::City.query(match: {name: 'Moscow'})
74
- def method_missing(name, *args, &block)
59
+ ruby2_keywords def method_missing(name, *args, &block)
75
60
  if search_class::DELEGATED_METHODS.include?(name)
76
61
  all.send(name, *args, &block)
77
62
  else
@@ -91,12 +76,7 @@ module Chewy
91
76
 
92
77
  def build_search_class(base)
93
78
  search_class = Class.new(base)
94
- if self < Chewy::Type
95
- index_scopes = index.scopes - scopes
96
79
 
97
- delegate_scoped index, search_class, index_scopes
98
- delegate_scoped index, self, index_scopes
99
- end
100
80
  delegate_scoped self, search_class, scopes
101
81
  const_set('Query', search_class)
102
82
  end
@@ -107,6 +87,7 @@ module Chewy
107
87
  define_method method do |*args, &block|
108
88
  scoping { source.public_send(method, *args, &block) }
109
89
  end
90
+ ruby2_keywords method
110
91
  end
111
92
  end
112
93
  end
data/lib/chewy/stash.rb CHANGED
@@ -1,44 +1,39 @@
1
1
  module Chewy
2
2
  # This class is the main storage for Chewy service data,
3
- # Now index raw specifications are stored in the `chewy_stash`
4
- # index. In the future the journal will be moved here as well.
3
+ # Now index raw specifications are stored in the `chewy_specifications`
4
+ # index.
5
+ # Journal entries are stored in `chewy_journal`
5
6
  #
6
7
  # @see Chewy::Index::Specification
7
- class Stash < Chewy::Index
8
- index_name 'chewy_stash'
8
+ module Stash
9
+ class Specification < Chewy::Index
10
+ index_name 'chewy_specifications'
9
11
 
10
- define_type :specification do
11
12
  default_import_options journal: false
12
13
 
13
- field :value, index: 'no'
14
+ field :specification, type: 'binary'
14
15
  end
15
16
 
16
- define_type :journal do # rubocop:disable Metrics/BlockLength
17
- default_import_options journal: false
18
-
19
- field :index_name, type: 'string', index: 'not_analyzed'
20
- field :type_name, type: 'string', index: 'not_analyzed'
21
- field :action, type: 'string', index: 'not_analyzed'
22
- field :references, type: 'string', index: 'no'
23
- field :created_at, type: 'date'
17
+ class Journal < Chewy::Index
18
+ index_name 'chewy_journal'
24
19
 
25
20
  # Loads all entries since the specified time.
26
21
  #
27
22
  # @param since_time [Time, DateTime] a timestamp from which we load a journal
28
23
  # @param only [Chewy::Index, Array<Chewy::Index>] journal entries related to these indices will be loaded only
29
24
  def self.entries(since_time, only: [])
30
- self.for(only).filter(range: {created_at: {gt: since_time}})
25
+ self.for(only).filter(range: {created_at: {gt: since_time}}).filter.minimum_should_match(1)
31
26
  end
32
27
 
33
28
  # Cleans up all the journal entries until the specified time. If nothing is
34
29
  # specified - cleans up everything.
35
30
  #
36
- # @param since_time [Time, DateTime] the time top boundary
31
+ # @param until_time [Time, DateTime] Clean everything before that date
37
32
  # @param only [Chewy::Index, Array<Chewy::Index>] indexes to clean up journal entries for
38
- def self.clean(until_time = nil, only: [])
33
+ def self.clean(until_time = nil, only: [], delete_by_query_options: {})
39
34
  scope = self.for(only)
40
35
  scope = scope.filter(range: {created_at: {lte: until_time}}) if until_time
41
- scope.delete_all
36
+ scope.delete_all(**delete_by_query_options)
42
37
  end
43
38
 
44
39
  # Selects all the journal entries for the specified indices.
@@ -46,24 +41,27 @@ module Chewy
46
41
  # @param indices [Chewy::Index, Array<Chewy::Index>]
47
42
  def self.for(*something)
48
43
  something = something.flatten.compact
49
- types = something.flat_map { |s| Chewy.derive_types(s) }
50
- return none if something.present? && types.blank?
44
+ indexes = something.flat_map { |s| Chewy.derive_name(s) }
45
+ return none if something.present? && indexes.blank?
46
+
51
47
  scope = all
52
- types.group_by(&:index).each do |index, index_types|
53
- scope = scope.or(
54
- filter(term: {index_name: index.derivable_name})
55
- .filter(terms: {type_name: index_types.map(&:type_name)})
56
- )
48
+ indexes.each do |index|
49
+ scope = scope.or(filter(term: {index_name: index.derivable_name}))
57
50
  end
58
51
  scope
59
52
  end
60
53
 
61
- def type
62
- @type ||= Chewy.derive_type("#{index_name}##{type_name}")
63
- end
54
+ default_import_options journal: false
55
+
56
+ field :index_name, type: 'keyword'
57
+ field :action, type: 'keyword'
58
+ field :references, type: 'binary'
59
+ field :created_at, type: 'date'
64
60
 
65
61
  def references
66
- @references ||= Array.wrap(@attributes['references']).map { |r| JSON.load(r) } # rubocop:disable Security/JSONLoad
62
+ @references ||= Array.wrap(@attributes['references']).map do |item|
63
+ JSON.load(Base64.decode64(item)) # rubocop:disable Security/JSONLoad
64
+ end
67
65
  end
68
66
  end
69
67
  end
@@ -11,11 +11,11 @@ module Chewy
11
11
  #
12
12
  class ActiveJob < Atomic
13
13
  class Worker < ::ActiveJob::Base
14
- queue_as :chewy
14
+ queue_as { Chewy.settings.dig(:active_job, :queue) || 'chewy' }
15
15
 
16
16
  def perform(type, ids, options = {})
17
17
  options[:refresh] = !Chewy.disable_refresh_async if Chewy.disable_refresh_async
18
- type.constantize.import!(ids, options)
18
+ type.constantize.import!(ids, **options)
19
19
  end
20
20
  end
21
21
 
@@ -18,7 +18,7 @@ module Chewy
18
18
 
19
19
  def update(type, objects, _options = {})
20
20
  @stash[type] ||= []
21
- @stash[type] |= type.send(:build_root).id ? Array.wrap(objects) : type.adapter.identify(objects)
21
+ @stash[type] |= type.root.id ? Array.wrap(objects) : type.adapter.identify(objects)
22
22
  end
23
23
 
24
24
  def leave
@@ -0,0 +1,18 @@
1
+ module Chewy
2
+ class Strategy
3
+ # This strategy works like atomic but import objects with `refresh=false` parameter.
4
+ #
5
+ # Chewy.strategy(:atomic_no_refresh) do
6
+ # User.all.map(&:save) # Does nothing here
7
+ # Post.all.map(&:save) # And here
8
+ # # It imports all the changed users and posts right here
9
+ # # before block leaving with bulk ES API, kinda optimization
10
+ # end
11
+ #
12
+ class AtomicNoRefresh < Atomic
13
+ def leave
14
+ @stash.all? { |type, ids| type.import!(ids, refresh: false) }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -22,6 +22,16 @@ module Chewy
22
22
  # strategies stack
23
23
  #
24
24
  def leave; end
25
+
26
+ # This method called when some model record is created or updated.
27
+ # Normally it will just evaluate all the Chewy callbacks and pass results
28
+ # to current strategy's update method.
29
+ # However it's possible to override it to achieve delayed evaluation of
30
+ # callbacks, e.g. using sidekiq.
31
+ #
32
+ def update_chewy_indices(object)
33
+ object.run_chewy_callbacks
34
+ end
25
35
  end
26
36
  end
27
37
  end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../index'
4
+
5
+ # The class is responsible for accumulating in redis [type, ids]
6
+ # that were requested to be reindexed during `latency` seconds.
7
+ # The reindex job is going to be scheduled after a `latency` seconds.
8
+ # that job is going to read accumulated [type, ids] from the redis
9
+ # and reindex all them at once.
10
+ module Chewy
11
+ class Strategy
12
+ class DelayedSidekiq
13
+ require_relative 'worker'
14
+
15
+ class Scheduler
16
+ DEFAULT_TTL = 60 * 60 * 24 # in seconds
17
+ DEFAULT_LATENCY = 10
18
+ DEFAULT_MARGIN = 2
19
+ DEFAULT_QUEUE = 'chewy'
20
+ KEY_PREFIX = 'chewy:delayed_sidekiq'
21
+ FALLBACK_FIELDS = 'all'
22
+ FIELDS_IDS_SEPARATOR = ';'
23
+ IDS_SEPARATOR = ','
24
+
25
+ def initialize(type, ids, options = {})
26
+ @type = type
27
+ @ids = ids
28
+ @options = options
29
+ end
30
+
31
+ # the diagram:
32
+ #
33
+ # inputs:
34
+ # latency == 2
35
+ # reindex_time = Time.current
36
+ #
37
+ # Parallel OR Sequential triggers of reindex: | What is going on in reindex store (Redis):
38
+ # --------------------------------------------------------------------------------------------------
39
+ # |
40
+ # process 1 (reindex_time): | chewy:delayed_sidekiq:CitiesIndex:1679347866 = [1]
41
+ # Schedule.new(CitiesIndex, [1]).postpone | chewy:delayed_sidekiq:timechunks = [{ score: 1679347866, "chewy:delayed_sidekiq:CitiesIndex:1679347866"}]
42
+ # | & schedule a DelayedSidekiq::Worker at 1679347869 (at + 3)
43
+ # | it will zpop chewy:delayed_sidekiq:timechunks up to 1679347866 score and reindex all ids with zpoped keys
44
+ # | chewy:delayed_sidekiq:CitiesIndex:1679347866
45
+ # |
46
+ # |
47
+ # process 2 (reindex_time): | chewy:delayed_sidekiq:CitiesIndex:1679347866 = [1, 2]
48
+ # Schedule.new(CitiesIndex, [2]).postpone | chewy:delayed_sidekiq:timechunks = [{ score: 1679347866, "chewy:delayed_sidekiq:CitiesIndex:1679347866"}]
49
+ # | & do not schedule a new worker
50
+ # |
51
+ # |
52
+ # process 1 (reindex_time + (latency - 1).seconds): | chewy:delayed_sidekiq:CitiesIndex:1679347866 = [1, 2, 3]
53
+ # Schedule.new(CitiesIndex, [3]).postpone | chewy:delayed_sidekiq:timechunks = [{ score: 1679347866, "chewy:delayed_sidekiq:CitiesIndex:1679347866"}]
54
+ # | & do not schedule a new worker
55
+ # |
56
+ # |
57
+ # process 2 (reindex_time + (latency + 1).seconds): | chewy:delayed_sidekiq:CitiesIndex:1679347866 = [1, 2, 3]
58
+ # Schedule.new(CitiesIndex, [4]).postpone | chewy:delayed_sidekiq:CitiesIndex:1679347868 = [4]
59
+ # | chewy:delayed_sidekiq:timechunks = [
60
+ # | { score: 1679347866, "chewy:delayed_sidekiq:CitiesIndex:1679347866"}
61
+ # | { score: 1679347868, "chewy:delayed_sidekiq:CitiesIndex:1679347868"}
62
+ # | ]
63
+ # | & schedule a DelayedSidekiq::Worker at 1679347871 (at + 3)
64
+ # | it will zpop chewy:delayed_sidekiq:timechunks up to 1679347868 score and reindex all ids with zpoped keys
65
+ # | chewy:delayed_sidekiq:CitiesIndex:1679347866 (in case of failed previous reindex),
66
+ # | chewy:delayed_sidekiq:CitiesIndex:1679347868
67
+ def postpone
68
+ ::Sidekiq.redis do |redis|
69
+ # warning: Redis#sadd will always return an Integer in Redis 5.0.0. Use Redis#sadd? instead
70
+ if redis.respond_to?(:sadd?)
71
+ redis.sadd?(timechunk_key, serialize_data)
72
+ else
73
+ redis.sadd(timechunk_key, serialize_data)
74
+ end
75
+
76
+ redis.expire(timechunk_key, ttl)
77
+
78
+ unless redis.zrank(timechunks_key, timechunk_key)
79
+ redis.zadd(timechunks_key, at, timechunk_key)
80
+ redis.expire(timechunks_key, ttl)
81
+
82
+ ::Sidekiq::Client.push(
83
+ 'queue' => sidekiq_queue,
84
+ 'at' => at + margin,
85
+ 'class' => Chewy::Strategy::DelayedSidekiq::Worker,
86
+ 'args' => [type_name, at]
87
+ )
88
+ end
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ attr_reader :type, :ids, :options
95
+
96
+ # this method returns predictable value that jumps by latency value
97
+ # another words each latency seconds it return the same value
98
+ def at
99
+ @at ||= begin
100
+ schedule_at = latency.seconds.from_now.to_f
101
+
102
+ (schedule_at - (schedule_at % latency)).to_i
103
+ end
104
+ end
105
+
106
+ def fields
107
+ options[:update_fields].presence || [FALLBACK_FIELDS]
108
+ end
109
+
110
+ def timechunks_key
111
+ "#{KEY_PREFIX}:#{type_name}:timechunks"
112
+ end
113
+
114
+ def timechunk_key
115
+ "#{KEY_PREFIX}:#{type_name}:#{at}"
116
+ end
117
+
118
+ def serialize_data
119
+ [ids.join(IDS_SEPARATOR), fields.join(IDS_SEPARATOR)].join(FIELDS_IDS_SEPARATOR)
120
+ end
121
+
122
+ def type_name
123
+ type.name
124
+ end
125
+
126
+ def latency
127
+ strategy_config.latency || DEFAULT_LATENCY
128
+ end
129
+
130
+ def margin
131
+ strategy_config.margin || DEFAULT_MARGIN
132
+ end
133
+
134
+ def ttl
135
+ strategy_config.ttl || DEFAULT_TTL
136
+ end
137
+
138
+ def sidekiq_queue
139
+ Chewy.settings.dig(:sidekiq, :queue) || DEFAULT_QUEUE
140
+ end
141
+
142
+ def strategy_config
143
+ type.strategy_config.delayed_sidekiq
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chewy
4
+ class Strategy
5
+ class DelayedSidekiq
6
+ class Worker
7
+ include ::Sidekiq::Worker
8
+
9
+ def perform(type, score, options = {})
10
+ options[:refresh] = !Chewy.disable_refresh_async if Chewy.disable_refresh_async
11
+
12
+ ::Sidekiq.redis do |redis|
13
+ timechunks_key = "#{Scheduler::KEY_PREFIX}:#{type}:timechunks"
14
+ timechunk_keys = redis.zrangebyscore(timechunks_key, -1, score)
15
+ members = timechunk_keys.flat_map { |timechunk_key| redis.smembers(timechunk_key) }.compact
16
+
17
+ # extract ids and fields & do the reset of records
18
+ ids, fields = extract_ids_and_fields(members)
19
+ options[:update_fields] = fields if fields
20
+
21
+ index = type.constantize
22
+ index.strategy_config.delayed_sidekiq.reindex_wrapper.call do
23
+ options.any? ? index.import!(ids, **options) : index.import!(ids)
24
+ end
25
+
26
+ redis.del(timechunk_keys)
27
+ redis.zremrangebyscore(timechunks_key, -1, score)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def extract_ids_and_fields(members)
34
+ ids = []
35
+ fields = []
36
+
37
+ members.each do |member|
38
+ member_ids, member_fields = member.split(Scheduler::FIELDS_IDS_SEPARATOR).map do |v|
39
+ v.split(Scheduler::IDS_SEPARATOR)
40
+ end
41
+ ids |= member_ids
42
+ fields |= member_fields
43
+ end
44
+
45
+ fields = nil if fields.include?(Scheduler::FALLBACK_FIELDS)
46
+
47
+ [ids.map(&:to_i), fields]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chewy
4
+ class Strategy
5
+ class DelayedSidekiq < Sidekiq
6
+ require_relative 'delayed_sidekiq/scheduler'
7
+
8
+ def leave
9
+ @stash.each do |type, ids|
10
+ next if ids.empty?
11
+
12
+ DelayedSidekiq::Scheduler.new(type, ids).postpone
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end