chewy 6.0.0 → 7.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) 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/dependabot.yml +42 -0
  7. data/.github/workflows/ruby.yml +48 -0
  8. data/.rubocop.yml +16 -8
  9. data/.rubocop_todo.yml +110 -22
  10. data/CHANGELOG.md +385 -105
  11. data/CODE_OF_CONDUCT.md +14 -0
  12. data/CONTRIBUTING.md +63 -0
  13. data/Gemfile +4 -10
  14. data/Guardfile +3 -1
  15. data/README.md +494 -275
  16. data/chewy.gemspec +5 -20
  17. data/gemfiles/base.gemfile +12 -0
  18. data/gemfiles/rails.6.1.activerecord.gemfile +10 -15
  19. data/gemfiles/rails.7.0.activerecord.gemfile +14 -0
  20. data/gemfiles/rails.7.1.activerecord.gemfile +14 -0
  21. data/lib/chewy/config.rb +58 -50
  22. data/lib/chewy/elastic_client.rb +31 -0
  23. data/lib/chewy/errors.rb +7 -10
  24. data/lib/chewy/fields/base.rb +79 -13
  25. data/lib/chewy/fields/root.rb +4 -14
  26. data/lib/chewy/index/actions.rb +54 -37
  27. data/lib/chewy/{type → index}/adapter/active_record.rb +30 -6
  28. data/lib/chewy/{type → index}/adapter/base.rb +2 -3
  29. data/lib/chewy/{type → index}/adapter/object.rb +27 -31
  30. data/lib/chewy/{type → index}/adapter/orm.rb +17 -18
  31. data/lib/chewy/index/aliases.rb +14 -5
  32. data/lib/chewy/index/crutch.rb +40 -0
  33. data/lib/chewy/index/import/bulk_builder.rb +311 -0
  34. data/lib/chewy/{type → index}/import/bulk_request.rb +6 -7
  35. data/lib/chewy/{type → index}/import/journal_builder.rb +11 -12
  36. data/lib/chewy/{type → index}/import/routine.rb +18 -17
  37. data/lib/chewy/{type → index}/import.rb +76 -32
  38. data/lib/chewy/{type → index}/mapping.rb +29 -34
  39. data/lib/chewy/index/observe/active_record_methods.rb +87 -0
  40. data/lib/chewy/index/observe/callback.rb +34 -0
  41. data/lib/chewy/index/observe.rb +17 -0
  42. data/lib/chewy/index/specification.rb +1 -0
  43. data/lib/chewy/{type → index}/syncer.rb +59 -59
  44. data/lib/chewy/{type → index}/witchcraft.rb +11 -7
  45. data/lib/chewy/{type → index}/wrapper.rb +2 -2
  46. data/lib/chewy/index.rb +67 -94
  47. data/lib/chewy/journal.rb +25 -14
  48. data/lib/chewy/log_subscriber.rb +5 -1
  49. data/lib/chewy/minitest/helpers.rb +86 -13
  50. data/lib/chewy/minitest/search_index_receiver.rb +24 -26
  51. data/lib/chewy/railtie.rb +6 -20
  52. data/lib/chewy/rake_helper.rb +169 -113
  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 +55 -44
  56. data/lib/chewy/rspec.rb +2 -0
  57. data/lib/chewy/runtime/version.rb +1 -1
  58. data/lib/chewy/runtime.rb +1 -1
  59. data/lib/chewy/search/loader.rb +19 -41
  60. data/lib/chewy/search/parameters/collapse.rb +16 -0
  61. data/lib/chewy/search/parameters/concerns/query_storage.rb +2 -2
  62. data/lib/chewy/search/parameters/ignore_unavailable.rb +27 -0
  63. data/lib/chewy/search/parameters/indices.rb +13 -58
  64. data/lib/chewy/search/parameters/knn.rb +16 -0
  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/storage.rb +1 -1
  68. data/lib/chewy/search/parameters/track_total_hits.rb +16 -0
  69. data/lib/chewy/search/parameters.rb +6 -4
  70. data/lib/chewy/search/query_proxy.rb +9 -2
  71. data/lib/chewy/search/request.rb +169 -134
  72. data/lib/chewy/search/response.rb +5 -5
  73. data/lib/chewy/search/scoping.rb +7 -8
  74. data/lib/chewy/search/scrolling.rb +13 -13
  75. data/lib/chewy/search.rb +9 -19
  76. data/lib/chewy/stash.rb +19 -30
  77. data/lib/chewy/strategy/active_job.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 +151 -0
  81. data/lib/chewy/strategy/delayed_sidekiq/worker.rb +52 -0
  82. data/lib/chewy/strategy/delayed_sidekiq.rb +30 -0
  83. data/lib/chewy/strategy/lazy_sidekiq.rb +64 -0
  84. data/lib/chewy/strategy/sidekiq.rb +2 -1
  85. data/lib/chewy/strategy.rb +6 -19
  86. data/lib/chewy/version.rb +1 -1
  87. data/lib/chewy.rb +39 -86
  88. data/lib/generators/chewy/install_generator.rb +1 -1
  89. data/lib/tasks/chewy.rake +36 -32
  90. data/migration_guide.md +46 -8
  91. data/spec/chewy/config_spec.rb +14 -39
  92. data/spec/chewy/elastic_client_spec.rb +26 -0
  93. data/spec/chewy/fields/base_spec.rb +432 -147
  94. data/spec/chewy/fields/root_spec.rb +20 -28
  95. data/spec/chewy/fields/time_fields_spec.rb +5 -5
  96. data/spec/chewy/index/actions_spec.rb +368 -59
  97. data/spec/chewy/{type → index}/adapter/active_record_spec.rb +156 -40
  98. data/spec/chewy/{type → index}/adapter/object_spec.rb +21 -6
  99. data/spec/chewy/index/aliases_spec.rb +3 -3
  100. data/spec/chewy/index/import/bulk_builder_spec.rb +494 -0
  101. data/spec/chewy/{type → index}/import/bulk_request_spec.rb +5 -12
  102. data/spec/chewy/{type → index}/import/journal_builder_spec.rb +9 -19
  103. data/spec/chewy/{type → index}/import/routine_spec.rb +19 -19
  104. data/spec/chewy/{type → index}/import_spec.rb +164 -98
  105. data/spec/chewy/index/mapping_spec.rb +135 -0
  106. data/spec/chewy/index/observe/active_record_methods_spec.rb +68 -0
  107. data/spec/chewy/index/observe/callback_spec.rb +139 -0
  108. data/spec/chewy/index/observe_spec.rb +143 -0
  109. data/spec/chewy/index/settings_spec.rb +3 -1
  110. data/spec/chewy/index/specification_spec.rb +20 -30
  111. data/spec/chewy/{type → index}/syncer_spec.rb +14 -19
  112. data/spec/chewy/{type → index}/witchcraft_spec.rb +20 -22
  113. data/spec/chewy/index/wrapper_spec.rb +100 -0
  114. data/spec/chewy/index_spec.rb +60 -105
  115. data/spec/chewy/journal_spec.rb +25 -74
  116. data/spec/chewy/minitest/helpers_spec.rb +123 -15
  117. data/spec/chewy/minitest/search_index_receiver_spec.rb +28 -30
  118. data/spec/chewy/multi_search_spec.rb +4 -5
  119. data/spec/chewy/rake_helper_spec.rb +315 -55
  120. data/spec/chewy/rspec/build_query_spec.rb +34 -0
  121. data/spec/chewy/rspec/helpers_spec.rb +61 -0
  122. data/spec/chewy/rspec/update_index_spec.rb +74 -71
  123. data/spec/chewy/runtime_spec.rb +2 -2
  124. data/spec/chewy/search/loader_spec.rb +19 -53
  125. data/spec/chewy/search/pagination/kaminari_examples.rb +4 -6
  126. data/spec/chewy/search/pagination/kaminari_spec.rb +2 -2
  127. data/spec/chewy/search/parameters/collapse_spec.rb +5 -0
  128. data/spec/chewy/search/parameters/ignore_unavailable_spec.rb +67 -0
  129. data/spec/chewy/search/parameters/indices_spec.rb +26 -117
  130. data/spec/chewy/search/parameters/knn_spec.rb +5 -0
  131. data/spec/chewy/search/parameters/order_spec.rb +18 -11
  132. data/spec/chewy/search/parameters/query_storage_examples.rb +67 -21
  133. data/spec/chewy/search/parameters/search_after_spec.rb +4 -1
  134. data/spec/chewy/search/parameters/source_spec.rb +8 -2
  135. data/spec/chewy/search/parameters/track_total_hits_spec.rb +5 -0
  136. data/spec/chewy/search/parameters_spec.rb +18 -4
  137. data/spec/chewy/search/query_proxy_spec.rb +68 -17
  138. data/spec/chewy/search/request_spec.rb +292 -110
  139. data/spec/chewy/search/response_spec.rb +12 -12
  140. data/spec/chewy/search/scrolling_spec.rb +10 -17
  141. data/spec/chewy/search_spec.rb +40 -34
  142. data/spec/chewy/stash_spec.rb +9 -21
  143. data/spec/chewy/strategy/active_job_spec.rb +16 -16
  144. data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
  145. data/spec/chewy/strategy/atomic_spec.rb +9 -10
  146. data/spec/chewy/strategy/delayed_sidekiq_spec.rb +202 -0
  147. data/spec/chewy/strategy/lazy_sidekiq_spec.rb +214 -0
  148. data/spec/chewy/strategy/sidekiq_spec.rb +12 -12
  149. data/spec/chewy/strategy_spec.rb +19 -15
  150. data/spec/chewy_spec.rb +24 -107
  151. data/spec/spec_helper.rb +3 -22
  152. data/spec/support/active_record.rb +25 -7
  153. metadata +78 -339
  154. data/.circleci/config.yml +0 -240
  155. data/Appraisals +0 -81
  156. data/gemfiles/rails.5.2.activerecord.gemfile +0 -17
  157. data/gemfiles/rails.5.2.mongoid.6.4.gemfile +0 -17
  158. data/gemfiles/rails.6.0.activerecord.gemfile +0 -17
  159. data/gemfiles/sequel.4.45.gemfile +0 -11
  160. data/lib/chewy/backports/deep_dup.rb +0 -46
  161. data/lib/chewy/backports/duplicable.rb +0 -91
  162. data/lib/chewy/search/pagination/will_paginate.rb +0 -43
  163. data/lib/chewy/search/parameters/types.rb +0 -20
  164. data/lib/chewy/strategy/resque.rb +0 -27
  165. data/lib/chewy/strategy/shoryuken.rb +0 -40
  166. data/lib/chewy/type/actions.rb +0 -43
  167. data/lib/chewy/type/adapter/mongoid.rb +0 -67
  168. data/lib/chewy/type/adapter/sequel.rb +0 -93
  169. data/lib/chewy/type/crutch.rb +0 -32
  170. data/lib/chewy/type/import/bulk_builder.rb +0 -122
  171. data/lib/chewy/type/observe.rb +0 -82
  172. data/lib/chewy/type.rb +0 -120
  173. data/lib/sequel/plugins/chewy_observe.rb +0 -63
  174. data/spec/chewy/search/pagination/will_paginate_examples.rb +0 -63
  175. data/spec/chewy/search/pagination/will_paginate_spec.rb +0 -23
  176. data/spec/chewy/search/parameters/types_spec.rb +0 -5
  177. data/spec/chewy/strategy/resque_spec.rb +0 -46
  178. data/spec/chewy/strategy/shoryuken_spec.rb +0 -70
  179. data/spec/chewy/type/actions_spec.rb +0 -50
  180. data/spec/chewy/type/adapter/mongoid_spec.rb +0 -372
  181. data/spec/chewy/type/adapter/sequel_spec.rb +0 -472
  182. data/spec/chewy/type/import/bulk_builder_spec.rb +0 -194
  183. data/spec/chewy/type/mapping_spec.rb +0 -175
  184. data/spec/chewy/type/observe_spec.rb +0 -137
  185. data/spec/chewy/type/wrapper_spec.rb +0 -100
  186. data/spec/chewy/type_spec.rb +0 -55
  187. data/spec/support/mongoid.rb +0 -93
  188. data/spec/support/sequel.rb +0 -80
@@ -3,14 +3,12 @@ module Chewy
3
3
  IMPORT_CALLBACK = lambda do |output, _name, start, finish, _id, payload|
4
4
  duration = (finish - start).ceil
5
5
  stats = payload.fetch(:import, {}).map { |key, count| "#{key} #{count}" }.join(', ')
6
- output.puts " Imported #{payload[:type]} in #{human_duration(duration)}, stats: #{stats}"
7
- if payload[:errors]
8
- payload[:errors].each do |action, errors|
9
- output.puts " #{action.to_s.humanize} errors:"
10
- errors.each do |error, documents|
11
- output.puts " `#{error}`"
12
- output.puts " on #{documents.count} documents: #{documents}"
13
- end
6
+ output.puts " Imported #{payload[:index]} in #{human_duration(duration)}, stats: #{stats}"
7
+ payload[:errors]&.each do |action, errors|
8
+ output.puts " #{action.to_s.humanize} errors:"
9
+ errors.each do |error, documents|
10
+ output.puts " `#{error}`"
11
+ output.puts " on #{documents.count} documents: #{documents}"
14
12
  end
15
13
  end
16
14
  end
@@ -21,6 +19,9 @@ module Chewy
21
19
  output.puts " Applying journal to #{targets}, #{count} entries, stage #{payload[:stage]}"
22
20
  end
23
21
 
22
+ DELETE_BY_QUERY_OPTIONS = %w[WAIT_FOR_COMPLETION REQUESTS_PER_SECOND SCROLL_SIZE].freeze
23
+ FALSE_VALUES = %w[0 f false off].freeze
24
+
24
25
  class << self
25
26
  # Performs zero-downtime reindexing of all documents for the specified indexes
26
27
  #
@@ -36,7 +37,9 @@ module Chewy
36
37
  # @param parallel [true, Integer, Hash] any acceptable parallel options for import
37
38
  # @param output [IO] output io for logging
38
39
  # @return [Array<Chewy::Index>] indexes that were reset
39
- def reset(only: nil, except: nil, parallel: nil, output: STDOUT)
40
+ def reset(only: nil, except: nil, parallel: nil, output: $stdout)
41
+ warn_missing_index(output)
42
+
40
43
  subscribed_task_stats(output) do
41
44
  indexes_from(only: only, except: except).each do |index|
42
45
  reset_one(index, output, parallel: parallel)
@@ -59,7 +62,9 @@ module Chewy
59
62
  # @param parallel [true, Integer, Hash] any acceptable parallel options for import
60
63
  # @param output [IO] output io for logging
61
64
  # @return [Array<Chewy::Index>] indexes that were actually reset
62
- def upgrade(only: nil, except: nil, parallel: nil, output: STDOUT)
65
+ def upgrade(only: nil, except: nil, parallel: nil, output: $stdout)
66
+ warn_missing_index(output)
67
+
63
68
  subscribed_task_stats(output) do
64
69
  indexes = indexes_from(only: only, except: except)
65
70
 
@@ -87,23 +92,21 @@ module Chewy
87
92
  #
88
93
  # @example
89
94
  # Chewy::RakeHelper.update # updates everything
90
- # Chewy::RakeHelper.update(only: 'places') # updates only PlacesIndex::City and PlacesIndex::Country
91
- # Chewy::RakeHelper.update(only: 'places#city') # updates PlacesIndex::City only
92
- # Chewy::RakeHelper.update(except: PlacesIndex::Country) # updates everything, but PlacesIndex::Country
93
- # Chewy::RakeHelper.update(only: 'places', except: 'places#country') # updates PlacesIndex::City only
95
+ # Chewy::RakeHelper.update(only: 'places') # updates only PlacesIndex
96
+ # Chewy::RakeHelper.update(except: PlacesIndex) # updates everything, but PlacesIndex
94
97
  #
95
- # @param only [Array<Chewy::Index, Chewy::Type, String>, Chewy::Index, Chewy::Type, String] indexes or types to update; if nothing is passed - uses all the types defined in the app
96
- # @param except [Array<Chewy::Index, Chewy::Type, String>, Chewy::Index, Chewy::Type, String] indexes or types to exclude from processing
98
+ # @param only [Array<Chewy::Index, String>, Chewy::Index, String] indexes to update; if nothing is passed - uses all the indexes defined in the app
99
+ # @param except [Array<Chewy::Index, String>, Chewy::Index, String] indexes to exclude from processing
97
100
  # @param parallel [true, Integer, Hash] any acceptable parallel options for import
98
101
  # @param output [IO] output io for logging
99
- # @return [Array<Chewy::Type>] types that were actually updated
100
- def update(only: nil, except: nil, parallel: nil, output: STDOUT)
102
+ # @return [Array<Chewy::Index>] indexes that were actually updated
103
+ def update(only: nil, except: nil, parallel: nil, output: $stdout)
101
104
  subscribed_task_stats(output) do
102
- types_from(only: only, except: except).group_by(&:index).each_with_object([]) do |(index, types), update_types|
105
+ indexes_from(only: only, except: except).each_with_object([]) do |index, updated_indexes|
103
106
  if index.exists?
104
107
  output.puts "Updating #{index}"
105
- types.each { |type| type.import(parallel: parallel) }
106
- update_types.concat(types)
108
+ index.import(parallel: parallel)
109
+ updated_indexes.push(index)
107
110
  else
108
111
  output.puts "Skipping #{index}, it does not exists (use rake chewy:reset[#{index.derivable_name}] to create and update it)"
109
112
  end
@@ -115,31 +118,29 @@ module Chewy
115
118
  #
116
119
  # @example
117
120
  # Chewy::RakeHelper.sync # synchronizes everything
118
- # Chewy::RakeHelper.sync(only: 'places') # synchronizes only PlacesIndex::City and PlacesIndex::Country
119
- # Chewy::RakeHelper.sync(only: 'places#city') # synchronizes PlacesIndex::City only
120
- # Chewy::RakeHelper.sync(except: PlacesIndex::Country) # synchronizes everything, but PlacesIndex::Country
121
- # Chewy::RakeHelper.sync(only: 'places', except: 'places#country') # synchronizes PlacesIndex::City only
121
+ # Chewy::RakeHelper.sync(only: 'places') # synchronizes only PlacesIndex
122
+ # Chewy::RakeHelper.sync(except: PlacesIndex) # synchronizes everything, but PlacesIndex
122
123
  #
123
- # @param only [Array<Chewy::Index, Chewy::Type, String>, Chewy::Index, Chewy::Type, String] indexes or types to synchronize; if nothing is passed - uses all the types defined in the app
124
- # @param except [Array<Chewy::Index, Chewy::Type, String>, Chewy::Index, Chewy::Type, String] indexes or types to exclude from processing
124
+ # @param only [Array<Chewy::Index, String>, Chewy::Index, String] indexes to synchronize; if nothing is passed - uses all the indexes defined in the app
125
+ # @param except [Array<Chewy::Index, String>, Chewy::Index, String] indexes to exclude from processing
125
126
  # @param parallel [true, Integer, Hash] any acceptable parallel options for sync
126
127
  # @param output [IO] output io for logging
127
- # @return [Array<Chewy::Type>] types that were actually updated
128
- def sync(only: nil, except: nil, parallel: nil, output: STDOUT)
128
+ # @return [Array<Chewy::Index>] indexes that were actually updated
129
+ def sync(only: nil, except: nil, parallel: nil, output: $stdout)
129
130
  subscribed_task_stats(output) do
130
- types_from(only: only, except: except).each_with_object([]) do |type, synced_types|
131
- output.puts "Synchronizing #{type}"
132
- output.puts " #{type} doesn't support outdated synchronization" unless type.supports_outdated_sync?
131
+ indexes_from(only: only, except: except).each_with_object([]) do |index, synced_indexes|
132
+ output.puts "Synchronizing #{index}"
133
+ output.puts " #{index} doesn't support outdated synchronization" unless index.supports_outdated_sync?
133
134
  time = Time.now
134
- sync_result = type.sync(parallel: parallel)
135
+ sync_result = index.sync(parallel: parallel)
135
136
  if !sync_result
136
- output.puts " Something went wrong with the #{type} synchronization"
137
- elsif sync_result[:count] > 0
137
+ output.puts " Something went wrong with the #{index} synchronization"
138
+ elsif (sync_result[:count]).positive?
138
139
  output.puts " Missing documents: #{sync_result[:missing]}" if sync_result[:missing].present?
139
140
  output.puts " Outdated documents: #{sync_result[:outdated]}" if sync_result[:outdated].present?
140
- synced_types.push(type)
141
+ synced_indexes.push(index)
141
142
  else
142
- output.puts " Skipping #{type}, up to date"
143
+ output.puts " Skipping #{index}, up to date"
143
144
  end
144
145
  output.puts " Took #{human_duration(Time.now - time)}"
145
146
  end
@@ -147,51 +148,65 @@ module Chewy
147
148
  end
148
149
 
149
150
  # Applies changes that were done after the specified time for the
150
- # specified indexes/types or all of them.
151
+ # specified indexes or all of them.
151
152
  #
152
153
  # @example
153
154
  # Chewy::RakeHelper.journal_apply(time: 1.minute.ago) # applies entries created for the last minute
154
- # Chewy::RakeHelper.journal_apply(time: 1.minute.ago, only: 'places') # applies only PlacesIndex::City and PlacesIndex::Country entries reated for the last minute
155
- # Chewy::RakeHelper.journal_apply(time: 1.minute.ago, only: 'places#city') # applies PlacesIndex::City entries reated for the last minute only
156
- # Chewy::RakeHelper.journal_apply(time: 1.minute.ago, except: PlacesIndex::Country) # applies everything, but PlacesIndex::Country entries reated for the last minute
157
- # Chewy::RakeHelper.journal_apply(time: 1.minute.ago, only: 'places', except: 'places#country') # applies PlacesIndex::City entries reated for the last minute only
155
+ # Chewy::RakeHelper.journal_apply(time: 1.minute.ago, only: 'places') # applies only PlacesIndex entries created for the last minute
156
+ # Chewy::RakeHelper.journal_apply(time: 1.minute.ago, except: PlacesIndex) # applies everything, but PlacesIndex, entries created for the last minute
158
157
  #
159
158
  # @param time [Time, DateTime] use only journal entries created after this time
160
- # @param only [Array<Chewy::Index, Chewy::Type, String>, Chewy::Index, Chewy::Type, String] indexes or types to synchronize; if nothing is passed - uses all the types defined in the app
161
- # @param except [Array<Chewy::Index, Chewy::Type, String>, Chewy::Index, Chewy::Type, String] indexes or types to exclude from processing
159
+ # @param only [Array<Chewy::Index, String>, Chewy::Index, String] indexes to synchronize; if nothing is passed - uses all the indexes defined in the app
160
+ # @param except [Array<Chewy::Index, String>, Chewy::Index, String] indexes to exclude from processing
162
161
  # @param output [IO] output io for logging
163
- # @return [Array<Chewy::Type>] types that were actually updated
164
- def journal_apply(time: nil, only: nil, except: nil, output: STDOUT)
162
+ # @return [Array<Chewy::Index>] indexes that were actually updated
163
+ def journal_apply(time: nil, only: nil, except: nil, output: $stdout)
165
164
  raise ArgumentError, 'Please specify the time to start with' unless time
165
+
166
166
  subscribed_task_stats(output) do
167
167
  output.puts "Applying journal entries created after #{time}"
168
- count = Chewy::Journal.new(types_from(only: only, except: except)).apply(time)
168
+ count = Chewy::Journal.new(journal_indexes_from(only: only, except: except)).apply(time)
169
169
  output.puts 'No journal entries were created after the specified time' if count.zero?
170
170
  end
171
171
  end
172
172
 
173
173
  # Removes journal records created before the specified timestamp for
174
- # the specified indexes/types or all of them.
174
+ # the specified indexes or all of them.
175
175
  #
176
176
  # @example
177
177
  # Chewy::RakeHelper.journal_clean # cleans everything
178
178
  # Chewy::RakeHelper.journal_clean(time: 1.minute.ago) # leaves only entries created for the last minute
179
- # Chewy::RakeHelper.journal_clean(only: 'places') # cleans only PlacesIndex::City and PlacesIndex::Country entries
180
- # Chewy::RakeHelper.journal_clean(only: 'places#city') # cleans PlacesIndex::City entries only
181
- # Chewy::RakeHelper.journal_clean(except: PlacesIndex::Country) # cleans everything, but PlacesIndex::Country entries
182
- # Chewy::RakeHelper.journal_clean(only: 'places', except: 'places#country') # cleans PlacesIndex::City entries only
179
+ # Chewy::RakeHelper.journal_clean(only: 'places') # cleans only PlacesIndex entries
180
+ # Chewy::RakeHelper.journal_clean(except: PlacesIndex) # cleans everything, but PlacesIndex entries
183
181
  #
184
182
  # @param time [Time, DateTime] clean all the journal entries created before this time
185
- # @param only [Array<Chewy::Index, Chewy::Type, String>, Chewy::Index, Chewy::Type, String] indexes or types to synchronize; if nothing is passed - uses all the types defined in the app
186
- # @param except [Array<Chewy::Index, Chewy::Type, String>, Chewy::Index, Chewy::Type, String] indexes or types to exclude from processing
183
+ # @param only [Array<Chewy::Index, String>, Chewy::Index, String] indexes to synchronize; if nothing is passed - uses all the indexes defined in the app
184
+ # @param except [Array<Chewy::Index, String>, Chewy::Index, String] indexes to exclude from processing
187
185
  # @param output [IO] output io for logging
188
- # @return [Array<Chewy::Type>] types that were actually updated
189
- def journal_clean(time: nil, only: nil, except: nil, output: STDOUT)
186
+ # @return [Array<Chewy::Index>] indexes that were actually updated
187
+ def journal_clean(time: nil, only: nil, except: nil, delete_by_query_options: {}, output: $stdout)
190
188
  subscribed_task_stats(output) do
191
189
  output.puts "Cleaning journal entries created before #{time}" if time
192
- response = Chewy::Journal.new(types_from(only: only, except: except)).clean(time)
193
- count = response['deleted'] || response['_indices']['_all']['deleted']
194
- output.puts "Cleaned up #{count} journal entries"
190
+ response = Chewy::Journal.new(journal_indexes_from(only: only, except: except)).clean(time, delete_by_query_options: delete_by_query_options)
191
+ if response.key?('task')
192
+ output.puts "Task to cleanup the journal has been created, #{response['task']}"
193
+ else
194
+ count = response['deleted'] || response['_indices']['_all']['deleted']
195
+ output.puts "Cleaned up #{count} journal entries"
196
+ end
197
+ end
198
+ end
199
+
200
+ # Creates journal index.
201
+ #
202
+ # @example
203
+ # Chewy::RakeHelper.journal_create # creates journal
204
+ #
205
+ # @param output [IO] output io for logging
206
+ # @return Chewy::Index Returns instance of chewy index
207
+ def journal_create(output: $stdout)
208
+ subscribed_task_stats(output) do
209
+ Chewy::Stash::Journal.create!
195
210
  end
196
211
  end
197
212
 
@@ -204,47 +219,100 @@ module Chewy
204
219
  Chewy::Index.descendants - [Chewy::Stash::Journal, Chewy::Stash::Specification]
205
220
  end
206
221
 
222
+ # Reindex data from source index to destination index
223
+ #
224
+ # @example
225
+ # Chewy::RakeHelper.reindex(source: 'users_index', dest: 'cities_index') reindex data from 'users_index' index to 'cities_index'
226
+ #
227
+ # @param source [String], dest [String] indexes to reindex
228
+ def reindex(source:, dest:, output: $stdout)
229
+ subscribed_task_stats(output) do
230
+ output.puts "Source index is #{source}\nDestination index is #{dest}"
231
+ Chewy::Index.reindex(source: source, dest: dest)
232
+ output.puts "#{source} index successfully reindexed with #{dest} index data"
233
+ end
234
+ end
235
+
236
+ # Adds new fields to an existing data stream or index.
237
+ # Change the search settings of existing fields.
238
+ #
239
+ # @example
240
+ # Chewy::RakeHelper.update_mapping('cities', {properties: {new_field: {type: :text}}}) update 'cities' index with new_field of text type
241
+ #
242
+ # @param name [String], body_hash [Hash] index name and body hash to update
243
+ def update_mapping(name:, output: $stdout)
244
+ subscribed_task_stats(output) do
245
+ output.puts "Index name is #{name}"
246
+ normalize_index(name).update_mapping
247
+ output.puts "#{name} index successfully updated"
248
+ end
249
+ end
250
+
251
+ # Reads options that are required to run journal cleanup asynchronously from ENV hash
252
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
253
+ #
254
+ # @example
255
+ # Chewy::RakeHelper.delete_by_query_options_from_env({'WAIT_FOR_COMPLETION' => 'false','REQUESTS_PER_SECOND' => '10','SCROLL_SIZE' => '5000'})
256
+ # # => { wait_for_completion: false, requests_per_second: 10.0, scroll_size: 5000 }
257
+ #
258
+ def delete_by_query_options_from_env(env)
259
+ env
260
+ .slice(*DELETE_BY_QUERY_OPTIONS)
261
+ .transform_keys { |k| k.downcase.to_sym }
262
+ .to_h do |key, value|
263
+ case key
264
+ when :wait_for_completion then [key, !FALSE_VALUES.include?(value.downcase)]
265
+ when :requests_per_second then [key, value.to_f]
266
+ when :scroll_size then [key, value.to_i]
267
+ end
268
+ end
269
+ end
270
+
271
+ def create_missing_indexes!(output: $stdout, env: ENV)
272
+ subscribed_task_stats(output) do
273
+ Chewy.eager_load!
274
+ all_indexes = Chewy::Index.descendants
275
+ all_indexes -= [Chewy::Stash::Journal] unless Chewy.configuration[:journal]
276
+ all_indexes.each do |index|
277
+ if index.exists?
278
+ output.puts "#{index.name} already exists, skipping" if env['VERBOSE']
279
+ next
280
+ end
281
+
282
+ index.create!
283
+
284
+ output.puts "#{index.name} index successfully created"
285
+ end
286
+ end
287
+ end
288
+
207
289
  def normalize_indexes(*identifiers)
208
290
  identifiers.flatten(1).map { |identifier| normalize_index(identifier) }
209
291
  end
210
292
 
211
293
  def normalize_index(identifier)
212
294
  return identifier if identifier.is_a?(Class) && identifier < Chewy::Index
213
- "#{identifier.to_s.gsub(/identifier\z/i, '').camelize}Index".constantize
295
+
296
+ "#{identifier.to_s.camelize}Index".constantize
214
297
  end
215
298
 
216
- def subscribed_task_stats(output = STDOUT)
299
+ def subscribed_task_stats(output = $stdout, &block)
217
300
  start = Time.now
218
301
  ActiveSupport::Notifications.subscribed(JOURNAL_CALLBACK.curry[output], 'apply_journal.chewy') do
219
- ActiveSupport::Notifications.subscribed(IMPORT_CALLBACK.curry[output], 'import_objects.chewy') do
220
- yield
221
- end
302
+ ActiveSupport::Notifications.subscribed(IMPORT_CALLBACK.curry[output], 'import_objects.chewy', &block)
222
303
  end
304
+ ensure
223
305
  output.puts "Total: #{human_duration(Time.now - start)}"
224
306
  end
225
307
 
226
- def reset_index(*indexes)
227
- ActiveSupport::Deprecation.warn '`Chewy::RakeHelper.reset_index` is deprecated and will be removed soon, use `Chewy::RakeHelper.reset` instead'
228
- reset(only: indexes)
229
- end
230
-
231
- def reset_all(*except)
232
- ActiveSupport::Deprecation.warn '`Chewy::RakeHelper.reset_all` is deprecated and will be removed soon, use `Chewy::RakeHelper.reset` instead'
233
- reset(except: except)
234
- end
308
+ private
235
309
 
236
- def update_index(*indexes)
237
- ActiveSupport::Deprecation.warn '`Chewy::RakeHelper.update_index` is deprecated and will be removed soon, use `Chewy::RakeHelper.update` instead'
238
- update(only: indexes)
239
- end
310
+ def journal_indexes_from(only: nil, except: nil)
311
+ return if Array.wrap(only).empty? && Array.wrap(except).empty?
240
312
 
241
- def update_all(*except)
242
- ActiveSupport::Deprecation.warn '`Chewy::RakeHelper.update_all` is deprecated and will be removed soon, use `Chewy::RakeHelper.update` instead'
243
- update(except: except)
313
+ indexes_from(only: only, except: except)
244
314
  end
245
315
 
246
- private
247
-
248
316
  def indexes_from(only: nil, except: nil)
249
317
  indexes = if only.present?
250
318
  normalize_indexes(Array.wrap(only))
@@ -252,42 +320,14 @@ module Chewy
252
320
  all_indexes
253
321
  end
254
322
 
255
- indexes = if except.present?
256
- indexes - normalize_indexes(Array.wrap(except))
257
- else
258
- indexes
259
- end
323
+ indexes -= normalize_indexes(Array.wrap(except)) if except.present?
260
324
 
261
325
  indexes.sort_by(&:derivable_name)
262
326
  end
263
327
 
264
- def types_from(only: nil, except: nil)
265
- types = if only.present?
266
- normalize_types(Array.wrap(only))
267
- else
268
- all_indexes.flat_map(&:types)
269
- end
270
-
271
- types = if except.present?
272
- types - normalize_types(Array.wrap(except))
273
- else
274
- types
275
- end
276
-
277
- types.sort_by(&:derivable_name)
278
- end
279
-
280
- def normalize_types(*identifiers)
281
- identifiers.flatten(1).flat_map { |identifier| normalize_type(identifier) }
282
- end
283
-
284
- def normalize_type(identifier)
285
- Chewy.derive_types(identifier)
286
- end
287
-
288
328
  def human_duration(seconds)
289
329
  [[60, :s], [60, :m], [24, :h]].map do |amount, unit|
290
- if seconds > 0
330
+ if seconds.positive?
291
331
  seconds, n = seconds.divmod(amount)
292
332
  "#{n.to_i}#{unit}"
293
333
  end
@@ -296,7 +336,23 @@ module Chewy
296
336
 
297
337
  def reset_one(index, output, parallel: false)
298
338
  output.puts "Resetting #{index}"
299
- index.reset!((Time.now.to_f * 1000).round, parallel: parallel)
339
+ index.reset!((Time.now.to_f * 1000).round, parallel: parallel, apply_journal: journal_exists?)
340
+ end
341
+
342
+ def warn_missing_index(output)
343
+ return if journal_exists?
344
+
345
+ output.puts "############################################################\n" \
346
+ "WARN: You are risking to lose some changes during the reset.\n " \
347
+ "Please consider enabling journaling.\n " \
348
+ "See https://github.com/toptal/chewy#journaling\n" \
349
+ '############################################################'
350
+ end
351
+
352
+ def journal_exists?
353
+ @journal_exists = Chewy::Stash::Journal.exists? if @journal_exists.nil?
354
+
355
+ @journal_exists
300
356
  end
301
357
  end
302
358
  end
@@ -0,0 +1,12 @@
1
+ # Rspec helper to compare request and expected query
2
+ # To use it - add `require 'chewy/rspec/build_query'` to the `spec_helper.rb`
3
+ # Simple usage - just pass expected response as argument
4
+ # and then call needed query.
5
+ #
6
+ # expect { method1.method2...methodN }.to build_query(expected_query)
7
+ #
8
+ RSpec::Matchers.define :build_query do |expected_query = {}|
9
+ match do |request|
10
+ request.render == expected_query
11
+ end
12
+ end
@@ -0,0 +1,55 @@
1
+ module Chewy
2
+ module Rspec
3
+ module Helpers
4
+ extend ActiveSupport::Concern
5
+ # Rspec helper to mock elasticsearch response
6
+ # To use it - add `require 'chewy/rspec'` to the `spec_helper.rb`
7
+ #
8
+ # mock_elasticsearch_response(CitiesIndex, raw_response)
9
+ # expect(CitiesIndex.query({}).hits).to eq(hits)
10
+ #
11
+ def mock_elasticsearch_response(index, raw_response)
12
+ mocked_request = Chewy::Search::Request.new(index)
13
+ allow(Chewy::Search::Request).to receive(:new).and_return(mocked_request)
14
+ allow(mocked_request).to receive(:perform).and_return(raw_response)
15
+ end
16
+
17
+ # Rspec helper to mock Elasticsearch response source
18
+ # To use it - add `require 'chewy/rspec'` to the `spec_helper.rb`
19
+ #
20
+ # mock_elasticsearch_response_sources(CitiesIndex, sources)
21
+ # expect(CitiesIndex.query({}).hits).to eq(hits)
22
+ #
23
+ def mock_elasticsearch_response_sources(index, hits)
24
+ raw_response = {
25
+ 'took' => 4,
26
+ 'timed_out' => false,
27
+ '_shards' => {
28
+ 'total' => 1,
29
+ 'successful' => 1,
30
+ 'skipped' => 0,
31
+ 'failed' => 0
32
+ },
33
+ 'hits' => {
34
+ 'total' => {
35
+ 'value' => hits.count,
36
+ 'relation' => 'eq'
37
+ },
38
+ 'max_score' => 1.0,
39
+ 'hits' => hits.each_with_index.map do |hit, i|
40
+ {
41
+ '_index' => index.index_name,
42
+ '_type' => '_doc',
43
+ '_id' => (i + 1).to_s,
44
+ '_score' => 3.14,
45
+ '_source' => hit
46
+ }
47
+ end
48
+ }
49
+ }
50
+
51
+ mock_elasticsearch_response(index, raw_response)
52
+ end
53
+ end
54
+ end
55
+ end