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
@@ -1,5 +1,5 @@
1
1
  module Chewy
2
- class Type
2
+ class Index
3
3
  # This class is able to find missing and outdated documents in the ES
4
4
  # comparing ids from the data source and the ES index. Also, if `outdated_sync_field`
5
5
  # exists in the index definition, it performs comparison of this field
@@ -9,10 +9,10 @@ module Chewy
9
9
  # should be reindexed.
10
10
  #
11
11
  # To fetch necessary data from the source it uses adapter method
12
- # {Chewy::Type::Adapter::Base#import_fields}, in case when the Object
12
+ # {Chewy::Index::Adapter::Base#import_fields}, in case when the Object
13
13
  # adapter is used it makes sense to read corresponding documentation.
14
14
  #
15
- # If `parallel` option is passed to the initializer - it will fetch surce and
15
+ # If `parallel` option is passed to the initializer - it will fetch source and
16
16
  # index data in parallel and then perform outdated objects calculation in
17
17
  # parallel processes. Also, further import (if required) will be performed
18
18
  # in parallel as well.
@@ -24,17 +24,17 @@ module Chewy
24
24
  # ATTENTION: synchronization may be slow in case when synchronized tables
25
25
  # are missing compound index on primary key and `outdated_sync_field`.
26
26
  #
27
- # @see Chewy::Type::Actions::ClassMethods#sync
27
+ # @see Chewy::Index::Actions::ClassMethods#sync
28
28
  class Syncer
29
29
  DEFAULT_SYNC_BATCH_SIZE = 20_000
30
30
  ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
31
- OUTDATED_IDS_WORKER = lambda do |outdated_sync_field_type, source_data_hash, type, total, index_data|
32
- ::Process.setproctitle("chewy [#{type}]: sync outdated calculation (#{::Parallel.worker_number + 1}/#{total})") if type
31
+ OUTDATED_IDS_WORKER = lambda do |outdated_sync_field_type, source_data_hash, index, total, index_data|
32
+ ::Process.setproctitle("chewy [#{index}]: sync outdated calculation (#{::Parallel.worker_number + 1}/#{total})") if index
33
33
  index_data.each_with_object([]) do |(id, index_sync_value), result|
34
34
  next unless source_data_hash[id]
35
35
 
36
36
  outdated = if outdated_sync_field_type == 'date'
37
- !Chewy::Type::Syncer.dates_equal(typecast_date(source_data_hash[id]), Time.iso8601(index_sync_value))
37
+ !Chewy::Index::Syncer.dates_equal(typecast_date(source_data_hash[id]), Time.iso8601(index_sync_value))
38
38
  else
39
39
  source_data_hash[id] != index_sync_value
40
40
  end
@@ -42,8 +42,8 @@ module Chewy
42
42
  result.push(id) if outdated
43
43
  end
44
44
  end
45
- SOURCE_OR_INDEX_DATA_WORKER = lambda do |syncer, type, kind|
46
- ::Process.setproctitle("chewy [#{type}]: sync fetching data (#{kind})")
45
+ SOURCE_OR_INDEX_DATA_WORKER = lambda do |syncer, index, kind|
46
+ ::Process.setproctitle("chewy [#{index}]: sync fetching data (#{kind})")
47
47
  result = case kind
48
48
  when :source
49
49
  syncer.send(:fetch_source_data)
@@ -56,7 +56,10 @@ module Chewy
56
56
  def self.typecast_date(string)
57
57
  if string.is_a?(String) && (match = ISO_DATETIME.match(string))
58
58
  microsec = (match[7].to_r * 1_000_000).to_i
59
- date = "#{match[1]}-#{match[2]}-#{match[3]}T#{match[4]}:#{match[5]}:#{match[6]}.#{format('%06d', microsec)}+00:00"
59
+ day = "#{match[1]}-#{match[2]}-#{match[3]}"
60
+ time_with_seconds = "#{match[4]}:#{match[5]}:#{match[6]}"
61
+ microseconds = format('%06d', microsec)
62
+ date = "#{day}T#{time_with_seconds}.#{microseconds}+00:00"
60
63
  Time.iso8601(date)
61
64
  else
62
65
  string
@@ -68,18 +71,10 @@ module Chewy
68
71
  [one.to_i, one.usec / 1000] == [two.to_i, two.usec / 1000]
69
72
  end
70
73
 
71
- # In ActiveSupport ~> 4.0 json dumpled times without any
72
- # milliseconds, so ES stored time with the seconds precision.
73
- if ActiveSupport::VERSION::STRING < '4.1.0'
74
- def self.dates_equal(one, two)
75
- one.to_i == two.to_i
76
- end
77
- end
78
-
79
- # @param type [Chewy::Type] chewy type
74
+ # @param index [Chewy::Index] chewy index
80
75
  # @param parallel [true, Integer, Hash] options for parallel execution or the number of processes
81
- def initialize(type, parallel: nil)
82
- @type = type
76
+ def initialize(index, parallel: nil)
77
+ @index = index
83
78
  @parallel = if !parallel || parallel.is_a?(Hash)
84
79
  parallel
85
80
  elsif parallel.is_a?(Integer)
@@ -95,7 +90,8 @@ module Chewy
95
90
  def perform
96
91
  ids = missing_ids | outdated_ids
97
92
  return 0 if ids.blank?
98
- @type.import(ids, parallel: @parallel) && ids.count
93
+
94
+ @index.import(ids, parallel: @parallel) && ids.count
99
95
  end
100
96
 
101
97
  # Finds ids of all the objects that are not indexed yet or deleted
@@ -113,21 +109,19 @@ module Chewy
113
109
  end
114
110
  end
115
111
 
116
- # If type supports outdated sync, it compares for the values of the
117
- # type `outdated_sync_field` for each object and document in the source
118
- # and index and returns the ids of entities which which are having
119
- # different values there.
112
+ # If index supports outdated sync, it compares the values of the
113
+ # `outdated_sync_field` for each object and document in the source
114
+ # and index and returns the ids of entities which differ.
120
115
  #
121
- # @see Chewy::Type::Mapping::ClassMethods#supports_outdated_sync?
116
+ # @see Chewy::Index::Mapping::ClassMethods#supports_outdated_sync?
122
117
  # @return [Array<String>] an array of outdated ids
123
118
  def outdated_ids
124
- return [] if source_data.blank? || index_data.blank? || !@type.supports_outdated_sync?
125
- @outdated_ids ||= begin
126
- if @parallel
127
- parallel_outdated_ids
128
- else
129
- linear_outdated_ids
130
- end
119
+ return [] if source_data.blank? || index_data.blank? || !@index.supports_outdated_sync?
120
+
121
+ @outdated_ids ||= if @parallel
122
+ parallel_outdated_ids
123
+ else
124
+ linear_outdated_ids
131
125
  end
132
126
  end
133
127
 
@@ -142,44 +136,48 @@ module Chewy
142
136
  end
143
137
 
144
138
  def source_and_index_data
145
- @source_and_index_data ||= begin
146
- if @parallel
147
- ::ActiveRecord::Base.connection.close if defined?(::ActiveRecord::Base)
148
- result = ::Parallel.map(%i[source index], @parallel, &SOURCE_OR_INDEX_DATA_WORKER.curry[self, @type])
149
- ::ActiveRecord::Base.connection.reconnect! if defined?(::ActiveRecord::Base)
150
- if result.first.keys.first == :source
151
- [result.first.values.first, result.second.values.first]
152
- else
153
- [result.second.values.first, result.first.values.first]
154
- end
139
+ @source_and_index_data ||= if @parallel
140
+ ::ActiveRecord::Base.connection.close if defined?(::ActiveRecord::Base)
141
+ result = ::Parallel.map(%i[source index], @parallel, &SOURCE_OR_INDEX_DATA_WORKER.curry[self, @index])
142
+ ::ActiveRecord::Base.connection.reconnect! if defined?(::ActiveRecord::Base)
143
+ if result.first.keys.first == :source
144
+ [result.first.values.first, result.second.values.first]
155
145
  else
156
- [fetch_source_data, fetch_index_data]
146
+ [result.second.values.first, result.first.values.first]
157
147
  end
148
+ else
149
+ [fetch_source_data, fetch_index_data]
158
150
  end
159
151
  end
160
152
 
161
153
  def fetch_source_data
162
- if @type.supports_outdated_sync?
163
- @type.adapter.import_fields(fields: [@type.outdated_sync_field], batch_size: DEFAULT_SYNC_BATCH_SIZE, typecast: false).to_a.flatten(1).each do |data|
154
+ if @index.supports_outdated_sync?
155
+ import_fields_args = {
156
+ fields: [@index.outdated_sync_field],
157
+ batch_size: DEFAULT_SYNC_BATCH_SIZE,
158
+ typecast: false
159
+ }
160
+ @index.adapter.import_fields(import_fields_args).to_a.flatten(1).each do |data|
164
161
  data[0] = data[0].to_s
165
162
  end
166
163
  else
167
- @type.adapter.import_fields(batch_size: DEFAULT_SYNC_BATCH_SIZE, typecast: false).to_a.flatten(1).map(&:to_s)
164
+ @index.adapter.import_fields(batch_size: DEFAULT_SYNC_BATCH_SIZE, typecast: false).to_a.flatten(1).map(&:to_s)
168
165
  end
169
166
  end
170
167
 
171
168
  def fetch_index_data
172
- if @type.supports_outdated_sync?
173
- @type.pluck(:_id, @type.outdated_sync_field).each do |data|
169
+ if @index.supports_outdated_sync?
170
+ @index.pluck(:_id, @index.outdated_sync_field).each do |data|
174
171
  data[0] = data[0].to_s
175
172
  end
176
173
  else
177
- @type.pluck(:_id).map(&:to_s)
174
+ @index.pluck(:_id).map(&:to_s)
178
175
  end
179
176
  end
180
177
 
181
178
  def data_ids(data)
182
- return data unless @type.supports_outdated_sync?
179
+ return data unless @index.supports_outdated_sync?
180
+
183
181
  data.map(&:first)
184
182
  end
185
183
 
@@ -192,7 +190,12 @@ module Chewy
192
190
  batches = index_data.each_slice(size)
193
191
 
194
192
  ::ActiveRecord::Base.connection.close if defined?(::ActiveRecord::Base)
195
- result = ::Parallel.map(batches, @parallel, &OUTDATED_IDS_WORKER.curry[outdated_sync_field_type, source_data.to_h, @type, batches.size]).flatten(1)
193
+ curried_outdated_ids_worker = OUTDATED_IDS_WORKER.curry[outdated_sync_field_type, source_data.to_h, @index, batches.size]
194
+ result = ::Parallel.map(
195
+ batches,
196
+ @parallel,
197
+ &curried_outdated_ids_worker
198
+ ).flatten(1)
196
199
  ::ActiveRecord::Base.connection.reconnect! if defined?(::ActiveRecord::Base)
197
200
  result
198
201
  end
@@ -203,16 +206,13 @@ module Chewy
203
206
 
204
207
  def outdated_sync_field_type
205
208
  return @outdated_sync_field_type if instance_variable_defined?(:@outdated_sync_field_type)
206
- return unless @type.outdated_sync_field
209
+ return unless @index.outdated_sync_field
207
210
 
208
- args = {index: @type.index_name, type: @type.type_name}
209
- args[:include_type_name] = true if Runtime.version >= '6.7.0'
210
- mappings = @type.client.indices.get_mapping(**args).values.first.fetch('mappings', {})
211
+ mappings = @index.client.indices.get_mapping(index: @index.index_name).values.first.fetch('mappings', {})
211
212
 
212
213
  @outdated_sync_field_type = mappings
213
- .fetch(@type.type_name, {})
214
214
  .fetch('properties', {})
215
- .fetch(@type.outdated_sync_field.to_s, {})['type']
215
+ .fetch(@index.outdated_sync_field.to_s, {})['type']
216
216
  rescue Elasticsearch::Transport::Transport::Errors::NotFound
217
217
  nil
218
218
  end
@@ -7,7 +7,7 @@ rescue LoadError
7
7
  end
8
8
 
9
9
  module Chewy
10
- class Type
10
+ class Index
11
11
  module Witchcraft
12
12
  extend ActiveSupport::Concern
13
13
 
@@ -43,10 +43,10 @@ module Chewy
43
43
  class Cauldron
44
44
  attr_reader :locals
45
45
 
46
- # @param type [Chewy::Type] type for composition
46
+ # @param index [Chewy::Index] index for composition
47
47
  # @param fields [Array<Symbol>] restricts the fields for composition
48
- def initialize(type, fields: [])
49
- @type = type
48
+ def initialize(index, fields: [])
49
+ @index = index
50
50
  @locals = []
51
51
  @fields = fields
52
52
  end
@@ -60,7 +60,7 @@ module Chewy
60
60
  def alicorn
61
61
  @alicorn ||= singleton_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
62
62
  -> (locals, object0, crutches) do
63
- #{composed_values(@type.root, 0)}
63
+ #{composed_values(@index.root, 0)}
64
64
  end
65
65
  RUBY
66
66
  end
@@ -141,6 +141,7 @@ module Chewy
141
141
 
142
142
  def non_proc_fields_for(parent, nesting)
143
143
  return [] unless parent
144
+
144
145
  fields = (parent.children || []).reject { |field| field.value.is_a?(Proc) }
145
146
 
146
147
  if nesting.zero? && @fields.present?
@@ -152,6 +153,7 @@ module Chewy
152
153
 
153
154
  def proc_fields_for(parent, nesting)
154
155
  return [] unless parent
156
+
155
157
  fields = (parent.children || []).select { |field| field.value.is_a?(Proc) }
156
158
 
157
159
  if nesting.zero? && @fields.present?
@@ -173,7 +175,7 @@ module Chewy
173
175
  if proc.arity.zero?
174
176
  source = replace_self(source, :"object#{nesting}")
175
177
  source = replace_send(source, :"object#{nesting}")
176
- elsif proc.arity < 0
178
+ elsif proc.arity.negative?
177
179
  raise "Splat arguments are unsupported by witchcraft:\n`#{proc.source}"
178
180
  else
179
181
  (nesting + 1).times do |n|
@@ -192,6 +194,7 @@ module Chewy
192
194
 
193
195
  def exctract_lambdas(node)
194
196
  return unless node.is_a?(Parser::AST::Node)
197
+
195
198
  if node.type == :block && node.children[0].type == :send && node.children[0].to_a == [nil, :lambda]
196
199
  [node.children[2]]
197
200
  else
@@ -214,7 +217,7 @@ module Chewy
214
217
  def replace_send(node, variable)
215
218
  if node.is_a?(Parser::AST::Node)
216
219
  if node.type == :send && node.children[0].nil?
217
- node.updated(nil, [Parser::AST::Node.new(:lvar, [variable]), *node.children[1..-1]])
220
+ node.updated(nil, [Parser::AST::Node.new(:lvar, [variable]), *node.children[1..]])
218
221
  else
219
222
  node.updated(nil, node.children.map { |child| replace_send(child, variable) })
220
223
  end
@@ -253,6 +256,7 @@ module Chewy
253
256
 
254
257
  def binding_variable_list(node)
255
258
  return unless node.is_a?(Parser::AST::Node)
259
+
256
260
  if node.type == :send && node.children[0].nil?
257
261
  node.children[1]
258
262
  else
@@ -1,5 +1,5 @@
1
1
  module Chewy
2
- class Type
2
+ class Index
3
3
  module Wrapper
4
4
  extend ActiveSupport::Concern
5
5
 
@@ -28,7 +28,7 @@ module Chewy
28
28
  def ==(other)
29
29
  return true if super
30
30
 
31
- if other.is_a?(Chewy::Type)
31
+ if other.is_a?(Chewy::Index)
32
32
  self.class == other.class && (respond_to?(:id) ? id == other.id : attributes == other.attributes)
33
33
  elsif other.respond_to?(:id)
34
34
  self.class.adapter.target.is_a?(Class) &&
data/lib/chewy/index.rb CHANGED
@@ -1,23 +1,52 @@
1
1
  require 'chewy/search'
2
2
  require 'chewy/index/actions'
3
+ require 'chewy/index/adapter/active_record'
4
+ require 'chewy/index/adapter/object'
3
5
  require 'chewy/index/aliases'
6
+ require 'chewy/index/crutch'
7
+ require 'chewy/index/import'
8
+ require 'chewy/index/mapping'
9
+ require 'chewy/index/observe'
4
10
  require 'chewy/index/settings'
5
11
  require 'chewy/index/specification'
12
+ require 'chewy/index/syncer'
13
+ require 'chewy/index/witchcraft'
14
+ require 'chewy/index/wrapper'
6
15
 
7
16
  module Chewy
8
17
  class Index
18
+ IMPORT_OPTIONS_KEYS = %i[
19
+ batch_size bulk_size consistency direct_import journal
20
+ pipeline raw_import refresh replication
21
+ ].freeze
22
+
23
+ STRATEGY_OPTIONS = {
24
+ delayed_sidekiq: %i[latency margin ttl reindex_wrapper]
25
+ }.freeze
26
+
9
27
  include Search
10
28
  include Actions
11
29
  include Aliases
30
+ include Import
31
+ include Mapping
32
+ include Observe
33
+ include Crutch
34
+ include Witchcraft
35
+ include Wrapper
12
36
 
13
37
  singleton_class.delegate :client, to: 'Chewy'
14
38
 
15
- class_attribute :type_hash
16
- self.type_hash = {}
39
+ class_attribute :adapter
40
+ self.adapter = Chewy::Index::Adapter::Object.new(:default)
41
+
42
+ class_attribute :index_scope_defined
17
43
 
18
44
  class_attribute :_settings
19
45
  self._settings = Chewy::Index::Settings.new
20
46
 
47
+ class_attribute :_default_import_options
48
+ self._default_import_options = {}
49
+
21
50
  class << self
22
51
  # @overload index_name(suggest)
23
52
  # If suggested name is passed, it is set up as the new base name for
@@ -47,7 +76,8 @@ module Chewy
47
76
  # UsersIndex.index_name(prefix: '', suffix: '2017') # => 'users_2017'
48
77
  #
49
78
  # @param prefix [String] index name prefix, uses {.prefix} method by default
50
- # @param suffix [String] index name suffix, used for creating several indexes for the same alias during the zero-downtime reset
79
+ # @param suffix [String] index name suffix, used for creating several indexes for the same
80
+ # alias during the zero-downtime reset
51
81
  # @raise [UndefinedIndex] if the base name is blank
52
82
  # @return [String] result index name
53
83
  def index_name(suggest = nil, prefix: nil, suffix: nil)
@@ -55,7 +85,7 @@ module Chewy
55
85
  @base_name = suggest.to_s.presence
56
86
  else
57
87
  [
58
- prefix || prefix_with_deprecation,
88
+ prefix || self.prefix,
59
89
  base_name,
60
90
  suffix
61
91
  ].reject(&:blank?).join('_')
@@ -77,6 +107,7 @@ module Chewy
77
107
  def base_name
78
108
  @base_name ||= name.sub(/Index\z/, '').demodulize.underscore if name
79
109
  raise UndefinedIndex if @base_name.blank?
110
+
80
111
  @base_name
81
112
  end
82
113
 
@@ -111,80 +142,30 @@ module Chewy
111
142
  Chewy.configuration[:prefix]
112
143
  end
113
144
 
114
- # Defines type for the index. Arguments depends on adapter used. For
145
+ # Defines scope and options for the index. Arguments depends on adapter used. For
115
146
  # ActiveRecord you can pass model or scope and options
116
147
  #
117
148
  # class CarsIndex < Chewy::Index
118
- # define_type Car do
119
- # ...
120
- # end # defines VehiclesIndex::Car type
121
- # end
122
- #
123
- # Type name might be passed in complicated cases:
124
- #
125
- # class VehiclesIndex < Chewy::Index
126
- # define_type Vehicle.cars.includes(:manufacturer), name: 'cars' do
127
- # ...
128
- # end # defines VehiclesIndex::Cars type
129
- #
130
- # define_type Vehicle.motocycles.includes(:manufacturer), name: 'motocycles' do
131
- # ...
132
- # end # defines VehiclesIndex::Motocycles type
149
+ # index_scope Car
150
+ # ...
133
151
  # end
134
152
  #
135
- # For plain objects:
153
+ # For plain objects you can completely omit this directive, unless you need to specify some options:
136
154
  #
137
155
  # class PlanesIndex < Chewy::Index
138
- # define_type :plane do
139
- # ...
140
- # end # defines PlanesIndex::Plane type
156
+ # ...
141
157
  # end
142
158
  #
143
159
  # The main difference between using plain objects or ActiveRecord models for indexing
144
- # is import. If you will call `CarsIndex::Car.import` - it will import all the cars
145
- # automatically, while `PlanesIndex::Plane.import(my_planes)` requires import data to be
160
+ # is import. If you will call `CarsIndex.import` - it will import all the cars
161
+ # automatically, while `PlanesIndex.import(my_planes)` requires import data to be
146
162
  # passed.
147
163
  #
148
- def define_type(target, options = {}, &block)
149
- raise 'Multiple types are deprecated' if type_hash.present?
150
-
151
- type_class = Chewy.create_type(self, target, options, &block)
152
- self.type_hash = type_hash.merge(type_class.type_name => type_class)
153
- end
154
-
155
- # Types method has double usage.
156
- # If no arguments are passed - it returns array of defined types:
157
- #
158
- # UsersIndex.types # => [UsersIndex::Admin, UsersIndex::Manager, UsersIndex::User]
159
- #
160
- # If arguments are passed it treats like a part of chainable query DSL and
161
- # adds types array for index to select.
162
- #
163
- # UsersIndex.filters { name =~ 'ro' }.types(:admin, :manager)
164
- # UsersIndex.types(:admin, :manager).filters { name =~ 'ro' } # the same as the first example
165
- #
166
- def types(*args)
167
- if args.present?
168
- all.types(*args)
169
- else
170
- type_hash.values
171
- end
172
- end
173
-
174
- # Returns defined types names:
175
- #
176
- # UsersIndex.type_names # => ['admin', 'manager', 'user']
177
- #
178
- def type_names
179
- type_hash.keys
180
- end
164
+ def index_scope(target, options = {})
165
+ raise 'Index scope is already defined' if index_scope_defined?
181
166
 
182
- # Returns named type:
183
- #
184
- # UserIndex.type('admin') # => UsersIndex::Admin
185
- #
186
- def type(type_name)
187
- type_hash.fetch(type_name) { raise UndefinedType, "Unknown type in #{name}: #{type_name}" }
167
+ self.adapter = Chewy.adapters.find { |klass| klass.accepts?(target) }.new(target, **options)
168
+ self.index_scope_defined = true
188
169
  end
189
170
 
190
171
  # Used as a part of index definition DSL. Defines settings:
@@ -221,7 +202,7 @@ module Chewy
221
202
  end
222
203
 
223
204
  def mappings_hash
224
- mappings = types.map(&:mappings_hash).inject(:merge)
205
+ mappings = root.mappings_hash
225
206
  mappings.present? ? {mappings: mappings} : {}
226
207
  end
227
208
 
@@ -234,44 +215,36 @@ module Chewy
234
215
  [settings_hash, mappings_hash].inject(:merge)
235
216
  end
236
217
 
237
- def index_params
238
- ActiveSupport::Deprecation.warn '`Chewy::Index.index_params` is deprecated and will be removed soon, use `Chewy::Index.specification_hash`'
239
- specification_hash
240
- end
241
-
242
218
  # @see Chewy::Index::Specification
243
219
  # @return [Chewy::Index::Specification] a specification object instance for this particular index
244
220
  def specification
245
221
  @specification ||= Specification.new(self)
246
222
  end
247
223
 
248
- def derivable_index_name
249
- ActiveSupport::Deprecation.warn '`Chewy::Index.derivable_index_name` is deprecated and will be removed soon, use `Chewy::Index.derivable_name` instead'
250
- derivable_name
224
+ def default_import_options(params)
225
+ params.assert_valid_keys(IMPORT_OPTIONS_KEYS)
226
+ self._default_import_options = _default_import_options.merge(params)
251
227
  end
252
228
 
253
- # Handling old default_prefix if it is not defined.
254
- def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissing
255
- if name == :default_prefix
256
- ActiveSupport::Deprecation.warn '`Chewy::Index.default_prefix` is deprecated and will be removed soon, use `Chewy::Index.prefix` instead'
257
- prefix
258
- else
259
- super
260
- end
261
- end
229
+ def strategy_config(params = {})
230
+ @strategy_config ||= begin
231
+ config_struct = Struct.new(*STRATEGY_OPTIONS.keys).new
262
232
 
263
- def prefix_with_deprecation
264
- if respond_to?(:default_prefix)
265
- ActiveSupport::Deprecation.warn '`Chewy::Index.default_prefix` is deprecated and will be removed soon, define `Chewy::Index.prefix` method instead'
266
- default_prefix
267
- else
268
- prefix
269
- end
270
- end
233
+ STRATEGY_OPTIONS.each_with_object(config_struct) do |(strategy, options), res|
234
+ res[strategy] = case strategy
235
+ when :delayed_sidekiq
236
+ Struct.new(*STRATEGY_OPTIONS[strategy]).new.tap do |config|
237
+ options.each do |option|
238
+ config[option] = params.dig(strategy, option) || Chewy.configuration.dig(:strategy_config, strategy, option)
239
+ end
271
240
 
272
- def build_index_name(*args)
273
- ActiveSupport::Deprecation.warn '`Chewy::Index.build_index_name` is deprecated and will be removed soon, use `Chewy::Index.index_name` instead'
274
- index_name(args.extract_options!)
241
+ config[:reindex_wrapper] ||= ->(&reindex) { reindex.call } # default wrapper
242
+ end
243
+ else
244
+ raise NotImplementedError, "Unsupported strategy: '#{strategy}'"
245
+ end
246
+ end
247
+ end
275
248
  end
276
249
  end
277
250
  end
data/lib/chewy/journal.rb CHANGED
@@ -2,33 +2,36 @@ module Chewy
2
2
  # A class to perform journal-related actions for the specified indexes/types.
3
3
  #
4
4
  # @example
5
- # journal = Chewy::Journal.new('places#city', UsersIndex)
5
+ # journal = Chewy::Journal.new('places', UsersIndex)
6
6
  # journal.apply(20.minutes.ago)
7
7
  # journal.clean
8
8
  #
9
9
  class Journal
10
- # @param only [Array<String, Chewy::Index, Chewy::Type>] indexes/types or even string references to perform actions on
10
+ # @param only [Array<String, Chewy::Index>] indexes or string references to perform actions on
11
11
  def initialize(*only)
12
12
  @only = only
13
13
  end
14
14
 
15
15
  # Applies all changes that were done since the specified time to the
16
- # specified indexes/types.
16
+ # specified indexes.
17
17
  #
18
18
  # @param since_time [Time, DateTime] timestamp from which changes will be applied
19
- # @param retries [Integer] maximum number of attempts to make journal empty, 10 by default
19
+ # @param fetch_limit [Int] amount of entries to be fetched on each cycle
20
20
  # @return [Integer] the amount of journal entries found
21
- def apply(since_time, retries: 10, **import_options)
21
+ def apply(since_time, fetch_limit: 10, **import_options)
22
22
  stage = 1
23
23
  since_time -= 1
24
24
  count = 0
25
- while stage <= retries
26
- entries = Chewy::Stash::Journal.entries(since_time, only: @only).to_a.presence or break
25
+
26
+ total_count = entries(since_time, fetch_limit).total_count
27
+
28
+ while count < total_count
29
+ entries = entries(since_time, fetch_limit).to_a.presence or break
27
30
  count += entries.size
28
31
  groups = reference_groups(entries)
29
32
  ActiveSupport::Notifications.instrument 'apply_journal.chewy', stage: stage, groups: groups
30
- groups.each do |type, references|
31
- type.import(references, import_options.merge(journal: false))
33
+ groups.each do |index, references|
34
+ index.import(references, import_options.merge(journal: false))
32
35
  end
33
36
  stage += 1
34
37
  since_time = entries.map(&:created_at).max
@@ -40,16 +43,24 @@ module Chewy
40
43
  #
41
44
  # @param until_time [Time, DateTime] time to clean up until it
42
45
  # @return [Hash] delete_by_query ES API call result
43
- def clean(until_time = nil)
44
- Chewy::Stash::Journal.clean(until_time, only: @only)
46
+ def clean(until_time = nil, delete_by_query_options: {})
47
+ Chewy::Stash::Journal.clean(
48
+ until_time,
49
+ only: @only,
50
+ delete_by_query_options: delete_by_query_options.merge(refresh: false)
51
+ )
45
52
  end
46
53
 
47
54
  private
48
55
 
56
+ def entries(since_time, fetch_limit)
57
+ Chewy::Stash::Journal.entries(since_time, only: @only).order(:created_at).limit(fetch_limit)
58
+ end
59
+
49
60
  def reference_groups(entries)
50
- entries.group_by(&:type).map do |type, grouped_entries|
51
- [type, grouped_entries.map(&:references).inject(:|)]
52
- end.to_h
61
+ entries.group_by(&:index_name)
62
+ .transform_keys { |index_name| Chewy.derive_name(index_name) }
63
+ .transform_values { |grouped_entries| grouped_entries.map(&:references).inject(:|) }
53
64
  end
54
65
  end
55
66
  end
@@ -24,7 +24,11 @@ module Chewy
24
24
 
25
25
  subject = payload[:type].presence || payload[:index]
26
26
  action = "#{subject} #{action} (#{event.duration.round(1)}ms)"
27
- action = color(action, GREEN, true)
27
+ action = if ActiveSupport.version >= Gem::Version.new('7.1')
28
+ color(action, GREEN, bold: true)
29
+ else
30
+ color(action, GREEN, true)
31
+ end
28
32
 
29
33
  debug(" #{action} #{description}")
30
34
  end