esse-async_indexing 0.0.2

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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +35 -0
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile +12 -0
  5. data/Gemfile.lock +168 -0
  6. data/LICENSE +21 -0
  7. data/README.md +262 -0
  8. data/Rakefile +4 -0
  9. data/docker-compose.yml +11 -0
  10. data/lib/esse/async_indexing/actions/batch_delete.rb +15 -0
  11. data/lib/esse/async_indexing/actions/batch_import.rb +16 -0
  12. data/lib/esse/async_indexing/actions/batch_import_all.rb +11 -0
  13. data/lib/esse/async_indexing/actions/batch_update.rb +32 -0
  14. data/lib/esse/async_indexing/actions/bulk_update_lazy_document_attribute.rb +20 -0
  15. data/lib/esse/async_indexing/actions/coerce_index_repository.rb +28 -0
  16. data/lib/esse/async_indexing/actions/delete_document.rb +16 -0
  17. data/lib/esse/async_indexing/actions/import_batch_id.rb +21 -0
  18. data/lib/esse/async_indexing/actions/index_document.rb +20 -0
  19. data/lib/esse/async_indexing/actions/update_document.rb +20 -0
  20. data/lib/esse/async_indexing/actions/update_lazy_document_attribute.rb +14 -0
  21. data/lib/esse/async_indexing/actions/upsert_document.rb +25 -0
  22. data/lib/esse/async_indexing/actions.rb +21 -0
  23. data/lib/esse/async_indexing/active_record.rb +102 -0
  24. data/lib/esse/async_indexing/active_record_callbacks/callback.rb +27 -0
  25. data/lib/esse/async_indexing/active_record_callbacks/lazy_update_attribute.rb +26 -0
  26. data/lib/esse/async_indexing/active_record_callbacks/on_create.rb +15 -0
  27. data/lib/esse/async_indexing/active_record_callbacks/on_destroy.rb +15 -0
  28. data/lib/esse/async_indexing/active_record_callbacks/on_update.rb +27 -0
  29. data/lib/esse/async_indexing/adapters/adapter.rb +29 -0
  30. data/lib/esse/async_indexing/adapters/faktory.rb +114 -0
  31. data/lib/esse/async_indexing/adapters/sidekiq.rb +94 -0
  32. data/lib/esse/async_indexing/adapters.rb +12 -0
  33. data/lib/esse/async_indexing/cli/async_import.rb +58 -0
  34. data/lib/esse/async_indexing/cli.rb +32 -0
  35. data/lib/esse/async_indexing/config.rb +27 -0
  36. data/lib/esse/async_indexing/configuration/base.rb +65 -0
  37. data/lib/esse/async_indexing/configuration/faktory.rb +6 -0
  38. data/lib/esse/async_indexing/configuration/sidekiq.rb +12 -0
  39. data/lib/esse/async_indexing/configuration.rb +45 -0
  40. data/lib/esse/async_indexing/errors.rb +12 -0
  41. data/lib/esse/async_indexing/jobs/bulk_update_lazy_document_attribute_job.rb +7 -0
  42. data/lib/esse/async_indexing/jobs/document_delete_by_id_job.rb +7 -0
  43. data/lib/esse/async_indexing/jobs/document_index_by_id_job.rb +7 -0
  44. data/lib/esse/async_indexing/jobs/document_update_by_id_job.rb +7 -0
  45. data/lib/esse/async_indexing/jobs/document_upsert_by_id_job.rb +7 -0
  46. data/lib/esse/async_indexing/jobs/import_all_job.rb +7 -0
  47. data/lib/esse/async_indexing/jobs/import_batch_id_job.rb +34 -0
  48. data/lib/esse/async_indexing/jobs/update_lazy_document_attribute_job.rb +7 -0
  49. data/lib/esse/async_indexing/testing.rb +79 -0
  50. data/lib/esse/async_indexing/version.rb +7 -0
  51. data/lib/esse/async_indexing/worker.rb +85 -0
  52. data/lib/esse/async_indexing/workers/faktory.rb +28 -0
  53. data/lib/esse/async_indexing/workers/shared_class_methods.rb +26 -0
  54. data/lib/esse/async_indexing/workers/sidekiq.rb +28 -0
  55. data/lib/esse/async_indexing/workers.rb +48 -0
  56. data/lib/esse/async_indexing.rb +72 -0
  57. data/lib/esse/plugins/async_indexing.rb +106 -0
  58. data/lib/esse-async-indexing.rb +3 -0
  59. data/lib/esse-async_indexing.rb +3 -0
  60. metadata +244 -0
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "esse/cli"
4
+
5
+ module Esse::AsyncIndexing
6
+ module CLI
7
+ end
8
+ end
9
+
10
+ Esse::CLI::Index.class_eval do
11
+ desc "async_import *INDEX_CLASSES", "Async Import documents from the given classes into the index"
12
+ option :repo, type: :string, default: nil, alias: "-r", desc: "Repository to use for import"
13
+ option :suffix, type: :string, default: nil, aliases: "-s", desc: "Suffix to append to index name"
14
+ option :context, type: :hash, default: {}, required: true, desc: "List of options to pass to the index class"
15
+ option :service, type: :string, default: nil, alias: "-s", desc: "Service to use for async import: sidekiq, faktory"
16
+ option :eager_include_document_attributes, type: :string, default: nil, desc: "Comma separated list of lazy document attributes to include to the bulk index request. Or pass `true` to include all lazy attributes"
17
+ option :lazy_update_document_attributes, type: :string, default: nil, desc: "Comma separated list of lazy document attributes to bulk update after the bulk index request Or pass `true` to include all lazy attributes"
18
+ def async_import(*index_classes)
19
+ opts = Esse::HashUtils.deep_transform_keys(options.to_h, &:to_sym)
20
+ opts[:service] ||= Esse.config.async_indexing.services.first
21
+ opts.delete(:lazy_update_document_attributes) if opts[:lazy_update_document_attributes] == "false"
22
+ opts.delete(:eager_include_document_attributes) if opts[:eager_include_document_attributes] == "false"
23
+ if (val = opts[:eager_include_document_attributes])
24
+ opts[:eager_include_document_attributes] = (val == "true") ? true : val.split(",")
25
+ end
26
+ if (val = opts[:lazy_update_document_attributes])
27
+ opts[:lazy_update_document_attributes] = (val == "true") ? true : val.split(",")
28
+ end
29
+ require "esse/async_indexing/cli/async_import"
30
+ Esse::AsyncIndexing::CLI::AsyncImport.new(indices: index_classes, **opts).run
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "configuration"
4
+ require_relative "configuration/base"
5
+ require_relative "configuration/faktory"
6
+ require_relative "configuration/sidekiq"
7
+
8
+ module Esse
9
+ module AsyncIndexing
10
+ module Config
11
+ def self.included(base)
12
+ base.__send__(:include, InstanceMethods)
13
+ end
14
+
15
+ module InstanceMethods
16
+ def async_indexing
17
+ @async_indexing ||= AsyncIndexing::Configuration.new
18
+ if block_given?
19
+ yield @async_indexing
20
+ else
21
+ @async_indexing
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Esse::AsyncIndexing::Configuration::Base
4
+ class << self
5
+ private
6
+
7
+ def attribute_accessor(field, validator: nil, normalizer: nil, default: nil)
8
+ normalizer ||= :"normalize_#{field}"
9
+ validator ||= :"validate_#{field}"
10
+
11
+ define_method(field) do
12
+ unless instance_variable_defined?(:"@#{field}")
13
+ return if default.nil?
14
+
15
+ send(:"#{field}=", default.respond_to?(:call) ? default.call : default)
16
+ end
17
+ instance_variable_get(:"@#{field}")
18
+ end
19
+
20
+ define_method(:"#{field}=") do |value|
21
+ value = send(normalizer, field, value) if respond_to?(normalizer, true)
22
+ send(validator, field, value) if respond_to?(validator, true)
23
+
24
+ instance_variable_set(:"@#{field}", value)
25
+ end
26
+ end
27
+ end
28
+
29
+ # A Hash with all workers definitions. The worker class name must be the main hash key
30
+ # Example:
31
+ # "FaktoryIndexWorker":
32
+ # retry: false
33
+ # queue: "indexing"
34
+ # adapter: "faktory"
35
+ # "FaktoryBatchIndexWorker":
36
+ # retry: 5
37
+ # queue: "batch_index"
38
+ # adapter: "faktory"
39
+ attribute_accessor :workers, default: {}
40
+
41
+ def worker_options(class_name)
42
+ class_name = class_name.to_s
43
+ if strict? && !workers.key?(class_name)
44
+ raise Esse::AsyncIndexing::NotDefinedWorkerError.new(class_name)
45
+ end
46
+
47
+ workers.fetch(class_name, {})
48
+ end
49
+
50
+ def strict?
51
+ true
52
+ end
53
+
54
+ protected
55
+
56
+ def normalize_workers(_, value)
57
+ return unless value.is_a?(Hash)
58
+
59
+ hash = Esse::AsyncIndexing::Workers::DEFAULT.values.map { |v| [v, {}] }.to_h
60
+ value.each do |class_name, opts|
61
+ hash[class_name.to_s] = Esse::HashUtils.deep_transform_keys(opts.to_h, &:to_sym)
62
+ end
63
+ hash
64
+ end
65
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse::AsyncIndexing
4
+ class Configuration::Faktory < Configuration::Base
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse::AsyncIndexing
4
+ class Configuration::Sidekiq < Configuration::Base
5
+ attribute_accessor :redis
6
+ attribute_accessor :namespace, default: "sidekiq"
7
+
8
+ def redis_pool
9
+ @redis_pool ||= Esse::RedisStorage::Pool.new(redis)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ module AsyncIndexing
5
+ class ConfigService < Set
6
+ def sidekiq?
7
+ include?(:sidekiq)
8
+ end
9
+
10
+ def faktory?
11
+ include?(:faktory)
12
+ end
13
+ end
14
+
15
+ class Configuration
16
+ def services
17
+ @services ||= ConfigService.new
18
+ end
19
+
20
+ def faktory
21
+ @faktory ||= begin
22
+ services.add(:faktory)
23
+ Configuration::Faktory.new
24
+ end
25
+ if block_given?
26
+ yield @faktory
27
+ else
28
+ @faktory
29
+ end
30
+ end
31
+
32
+ def sidekiq
33
+ @sidekiq ||= begin
34
+ services.add(:sidekiq)
35
+ Configuration::Sidekiq.new
36
+ end
37
+ if block_given?
38
+ yield @sidekiq
39
+ else
40
+ @sidekiq
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse::AsyncIndexing
4
+ class Error < StandardError
5
+ end
6
+
7
+ class NotDefinedWorkerError < Error
8
+ def initialize(worker_name)
9
+ super("Worker `#{worker_name}` is not defined.")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Esse::AsyncIndexing::Jobs::BulkUpdateLazyDocumentAttributeJob
4
+ def perform(index_class_name, repo_name, attribute_name, batch_id, options = {})
5
+ Esse::AsyncIndexing::Actions::BulkUpdateLazyDocumentAttribute.call(index_class_name, repo_name, attribute_name, batch_id, options)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Esse::AsyncIndexing::Jobs::DocumentDeleteByIdJob
4
+ def perform(index_class_name, repo_name, document_id, options = {})
5
+ Esse::AsyncIndexing::Actions::DeleteDocument.call(index_class_name, repo_name, document_id, options)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Esse::AsyncIndexing::Jobs::DocumentIndexByIdJob
4
+ def perform(index_class_name, repo_name, document_id, options = {})
5
+ Esse::AsyncIndexing::Actions::IndexDocument.call(index_class_name, repo_name, document_id, options)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Esse::AsyncIndexing::Jobs::DocumentUpdateByIdJob
4
+ def perform(index_class_name, repo_name, document_id, options = {})
5
+ Esse::AsyncIndexing::Actions::UpdateDocument.call(index_class_name, repo_name, document_id, options)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Esse::AsyncIndexing::Jobs::DocumentUpsertByIdJob
4
+ def perform(index_class_name, repo_name, document_id, operation = "index", options = {})
5
+ Esse::AsyncIndexing::Actions::UpsertDocument.call(index_class_name, repo_name, document_id, operation, options)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Esse::AsyncIndexing::Jobs::ImportAllJob
4
+ def perform(index_class_name, repo_name, options = {})
5
+ Esse::AsyncIndexing::Actions::BatchImportAll.call(index_class_name, repo_name, options)
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Esse::AsyncIndexing::Jobs::ImportBatchIdJob
4
+ LAZY_ATTR_WORKER = "Esse::AsyncIndexing::Jobs::BulkUpdateLazyDocumentAttributeJob"
5
+
6
+ def perform(index_class_name, repo_name, batch_id, options = {})
7
+ total, ids = Esse::AsyncIndexing::Actions::ImportBatchId.call(index_class_name, repo_name, batch_id, options)
8
+
9
+ options = Esse::HashUtils.deep_transform_keys(options, &:to_s)
10
+ return total if total.zero?
11
+ return total if lazy_already_imported?(options)
12
+ return total unless self.class.respond_to?(:bg_worker_options)
13
+
14
+ _index_class, repo_class = Esse::AsyncIndexing::Actions::CoerceIndexRepository.call(index_class_name, repo_name)
15
+
16
+ repo_class.lazy_document_attributes.each_key do |attr_name|
17
+ queue = Esse::RedisStorage::Queue.for(repo: repo_class, attribute_name: attr_name)
18
+ queue.enqueue(id: batch_id, values: ids)
19
+ Esse::AsyncIndexing.worker(LAZY_ATTR_WORKER, service: self.class.bg_worker_options[:service])
20
+ .with_args(index_class_name, repo_name, attr_name.to_s, batch_id, options)
21
+ .push
22
+ end
23
+ total
24
+ end
25
+
26
+ protected
27
+
28
+ # The `import` action already eager or lazy load the document attributes when some of these options are set.
29
+ def lazy_already_imported?(options)
30
+ eager = options.delete("eager_include_document_attributes") || false
31
+ lazy = options.delete("lazy_update_document_attributes") || false
32
+ eager || lazy
33
+ end
34
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Esse::AsyncIndexing::Jobs::UpdateLazyDocumentAttributeJob
4
+ def perform(index_class_name, repo_name, attribute_name, ids, options = {})
5
+ Esse::AsyncIndexing::Actions::UpdateLazyDocumentAttribute.call(index_class_name, repo_name, attribute_name, ids, options)
6
+ end
7
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ module AsyncIndexing
5
+ class Testing
6
+ class << self
7
+ def enable!
8
+ Thread.current[:esse_async_indexing_testing] = true
9
+ end
10
+
11
+ def disable!
12
+ Thread.current[:esse_async_indexing_testing] = false
13
+ end
14
+
15
+ def enabled?
16
+ Thread.current[:esse_async_indexing_testing] == true
17
+ end
18
+
19
+ def disabled?
20
+ !enabled?
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ Esse::AsyncIndexing::Testing.disable!
28
+
29
+ module Esse::AsyncIndexing::Jobs
30
+ class << self
31
+ def jobs
32
+ @jobs ||= []
33
+ end
34
+
35
+ def push(job)
36
+ jobs.push(job)
37
+ end
38
+
39
+ def clear
40
+ jobs.clear
41
+ end
42
+
43
+ def size
44
+ jobs.size
45
+ end
46
+
47
+ def jobs_for(service: nil, class_name: nil)
48
+ filtered = jobs
49
+ if service
50
+ filtered = filtered.select { |job| job["service"] == service.to_s }
51
+ end
52
+ if class_name
53
+ filtered = filtered.select { |job| job["__class_name__"] == class_name.to_s }
54
+ end
55
+ filtered
56
+ end
57
+ end
58
+ end
59
+
60
+ module Esse::AsyncIndexing::JobsInterceptorAdapter
61
+ def push
62
+ return super unless Esse::AsyncIndexing::Testing.enabled?
63
+
64
+ normalize_before_push
65
+ test_payload = @payload.dup
66
+ if @payload["jobtype"]
67
+ test_payload["service"] = "faktory"
68
+ test_payload["__class_name__"] = @payload["jobtype"]
69
+ else
70
+ test_payload["service"] = "sidekiq"
71
+ test_payload["__class_name__"] = @payload["class"]
72
+ end
73
+ Esse::AsyncIndexing::Jobs.push(test_payload)
74
+ end
75
+ end
76
+
77
+ Esse::AsyncIndexing::SERVICES.each_value do |adapter|
78
+ adapter.prepend(Esse::AsyncIndexing::JobsInterceptorAdapter)
79
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ module AsyncIndexing
5
+ VERSION = "0.0.2"
6
+ end
7
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse::AsyncIndexing
4
+ class Worker
5
+ attr_reader :options, :payload, :worker_class, :service
6
+
7
+ attr_reader :arguments
8
+
9
+ def initialize(worker_class, service: nil, **options)
10
+ @worker_class = worker_class
11
+ @service = service
12
+ @options = options
13
+ @payload = {}
14
+ end
15
+
16
+ def self.coerce(service:, payload:, **opts)
17
+ Esse::AsyncIndexing::SERVICES.fetch(service).coerce_to_worker(payload, **opts)
18
+ end
19
+
20
+ %i[created_at enqueued_at].each do |method_name|
21
+ define_method method_name do |value|
22
+ @payload[method_name.to_s] =
23
+ case value
24
+ when Numeric then value.to_f
25
+ when String then Time.parse(value).to_f
26
+ when Time, DateTime then value.to_f
27
+ else
28
+ raise ArgumentError, format("The %<v>p is not a valid value for %<m>s.", v: value, m: method_name)
29
+ end
30
+
31
+ self
32
+ end
33
+ end
34
+
35
+ # Adds arguments to the job
36
+ # @return self
37
+ def with_args(*args)
38
+ @payload["args"] = args
39
+
40
+ self
41
+ end
42
+
43
+ # Schedule the time when a job will be executed. Jobs which are scheduled in the past are enqueued for immediate execution.
44
+ # @param timestamp [Numeric] timestamp, numeric or something that acts numeric.
45
+ # @return self
46
+ def in(timestamp)
47
+ now = Time.now.to_f
48
+ timestamp = Time.parse(timestamp) if timestamp.is_a?(String)
49
+ int = timestamp.respond_to?(:strftime) ? timestamp.to_f : now + timestamp.to_f
50
+ return self if int <= now
51
+
52
+ @payload["at"] = int
53
+ @payload["created_at"] = now
54
+
55
+ self
56
+ end
57
+ alias_method :at, :in
58
+
59
+ def with_job_jid(jid = nil)
60
+ @payload["jid"] ||= jid || Esse::AsyncIndexing.jid
61
+
62
+ self
63
+ end
64
+
65
+ # @return Response of service
66
+ # @see Esse::AsyncIndexing::Adapters::** for more details
67
+ def push
68
+ unless Esse::AsyncIndexing::SERVICES.key?(service)
69
+ raise Esse::AsyncIndexing::Error, format("Service %<service>p is not implemented. Please use one of #{Esse::AsyncIndexing::SERVICES.keys.map(&:inspect).join(" or ")}.", service: service)
70
+ end
71
+ @payload["created_at"] ||= Time.now.to_f
72
+ worker_to_push = with_job_jid
73
+ Esse::AsyncIndexing::SERVICES[service].push(worker_to_push)
74
+ end
75
+
76
+ def eql?(other)
77
+ return false unless other.is_a?(self.class)
78
+
79
+ worker_class == other.worker_class &&
80
+ payload == other.payload &&
81
+ options == other.options
82
+ end
83
+ alias_method :==, :eql?
84
+ end
85
+ end
@@ -0,0 +1,28 @@
1
+ # frizen_string_literal: true
2
+
3
+ require_relative "shared_class_methods"
4
+
5
+ module Esse::AsyncIndexing
6
+ module Workers
7
+ module Faktory
8
+ def self.extended(base)
9
+ base.include(::Faktory::Job) if defined?(::Faktory)
10
+ base.extend SharedClassMethods
11
+ base.extend ClassMethods
12
+ end
13
+
14
+ module ClassMethods
15
+ def service_worker_options
16
+ default_queue = Esse.config.async_indexing.faktory.workers.dig(name, :queue)
17
+ default_retry = Esse.config.async_indexing.faktory.workers.dig(name, :retry)
18
+ default_queue ||= ::Faktory.default_job_options["queue"] if defined?(::Faktory)
19
+ default_retry ||= ::Faktory.default_job_options["retry"] if defined?(::Faktory)
20
+ {
21
+ queue: default_queue || "default",
22
+ retry: default_retry || 25
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ # frizen_string_literal: true
2
+
3
+ module Esse::AsyncIndexing
4
+ module Workers
5
+ module SharedClassMethods
6
+ def perform_async(*args)
7
+ build_worker.with_args(*args).push
8
+ end
9
+
10
+ def perform_in(interval, *args)
11
+ build_worker.with_args(*args).at(interval).push
12
+ end
13
+ alias_method :perform_at, :perform_in
14
+
15
+ protected
16
+
17
+ def service_worker_options
18
+ {}
19
+ end
20
+
21
+ def build_worker
22
+ Esse::AsyncIndexing.worker(name, **service_worker_options.merge(bg_worker_options))
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ # frizen_string_literal: true
2
+
3
+ require_relative "shared_class_methods"
4
+
5
+ module Esse::AsyncIndexing
6
+ module Workers
7
+ module Sidekiq
8
+ def self.extended(base)
9
+ base.include(::Sidekiq::Worker) if defined?(::Sidekiq)
10
+ base.extend SharedClassMethods
11
+ base.extend ClassMethods
12
+ end
13
+
14
+ module ClassMethods
15
+ def service_worker_options
16
+ default_queue = Esse.config.async_indexing.sidekiq.workers.dig(name, :queue)
17
+ default_retry = Esse.config.async_indexing.sidekiq.workers.dig(name, :retry)
18
+ default_queue ||= ::Sidekiq.default_worker_options["queue"] if defined?(::Sidekiq)
19
+ default_retry ||= ::Sidekiq.default_worker_options["retry"] if defined?(::Sidekiq)
20
+ {
21
+ queue: default_queue || "default",
22
+ retry: default_retry || 15
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "worker"
4
+
5
+ module Esse
6
+ module AsyncIndexing
7
+ module Workers
8
+ DEFAULT = {
9
+ "esse/async_indexing/jobs/bulk_update_lazy_document_attribute_job" => "Esse::AsyncIndexing::Jobs::BulkUpdateLazyDocumentAttributeJob",
10
+ "esse/async_indexing/jobs/document_delete_by_id_job" => "Esse::AsyncIndexing::Jobs::DocumentDeleteByIdJob",
11
+ "esse/async_indexing/jobs/document_index_by_id_job" => "Esse::AsyncIndexing::Jobs::DocumentIndexByIdJob",
12
+ "esse/async_indexing/jobs/document_update_by_id_job" => "Esse::AsyncIndexing::Jobs::DocumentUpdateByIdJob",
13
+ "esse/async_indexing/jobs/document_upsert_by_id_job" => "Esse::AsyncIndexing::Jobs::DocumentUpsertByIdJob",
14
+ "esse/async_indexing/jobs/import_all_job" => "Esse::AsyncIndexing::Jobs::ImportAllJob",
15
+ "esse/async_indexing/jobs/import_batch_id_job" => "Esse::AsyncIndexing::Jobs::ImportBatchIdJob",
16
+ "esse/async_indexing/jobs/update_lazy_document_attribute_job" => "Esse::AsyncIndexing::Jobs::UpdateLazyDocumentAttributeJob"
17
+ }
18
+
19
+ # The backend service may live in a different application, so they are not required by default.
20
+ # That's why we have a separate structure to let enqueue jobs even without having the explicit worker class loaded.
21
+ # This method will require all the internal jobs and configure them according to the defined options.
22
+ def self.install!(service, **options)
23
+ return if @installed_services&.include?(service.to_sym)
24
+
25
+ DEFAULT.each do |job, worker_name|
26
+ Kernel.require(job)
27
+ worker = Esse::AsyncIndexing::Jobs.const_get(worker_name.split("::").last)
28
+ worker.extend(self.for(service, **options))
29
+ end
30
+ @installed_services = Array(@installed_services) << service.to_sym
31
+ end
32
+
33
+ def self.for(service, **options)
34
+ require_relative "workers/#{service}"
35
+ service = service.to_sym
36
+ worker_options = options.merge(service: service)
37
+ module_name = service.to_s.split(/_/i).collect! { |w| w.capitalize }.join
38
+ mod = Esse::AsyncIndexing::Workers.const_get(module_name)
39
+ mod.module_eval do
40
+ define_method(:bg_worker_options) do
41
+ worker_options
42
+ end
43
+ end
44
+ mod
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "esse"
4
+ require "esse-redis_storage"
5
+ require "forwardable"
6
+ require "securerandom"
7
+ require "time"
8
+ require "multi_json"
9
+
10
+ module Esse
11
+ module AsyncIndexing
12
+ module Actions
13
+ end
14
+
15
+ module Jobs
16
+ end
17
+
18
+ module Workers
19
+ end
20
+ end
21
+ end
22
+
23
+ require_relative "async_indexing/version"
24
+ require_relative "async_indexing/actions"
25
+ require_relative "async_indexing/adapters"
26
+ require_relative "async_indexing/config"
27
+ require_relative "async_indexing/errors"
28
+ require_relative "async_indexing/workers"
29
+ require_relative "plugins/async_indexing"
30
+
31
+ module Esse::AsyncIndexing
32
+ SERVICES = {
33
+ sidekiq: Adapters::Sidekiq,
34
+ faktory: Adapters::Faktory
35
+ }
36
+
37
+ # @param worker_class [String] The worker class name
38
+ # @param options [Hash] Options that will be passed along to the worker instance
39
+ # @return [Esse::AsyncIndexing::Worker] An instance of worker
40
+ def self.worker(worker_class, service: nil, **options)
41
+ serv_name = service_name(service)
42
+ Worker.new(worker_class, **Esse.config.async_indexing.send(service).worker_options(worker_class).merge(options), service: serv_name)
43
+ end
44
+
45
+ def self.service_name(identifier = nil)
46
+ identifier ||= Esse.config.async_indexing.services.first
47
+ if identifier.nil?
48
+ raise ArgumentError, "There are no async indexing services configured. Please configure at least one service or pass the service name as an argument."
49
+ end
50
+
51
+ if SERVICES[identifier.to_sym].nil?
52
+ raise ArgumentError, "Invalid service: #{identifier.inspect}, valid services are: #{SERVICES.keys.join(", ")}"
53
+ end
54
+
55
+ identifier.to_sym
56
+ end
57
+
58
+ def self.jid
59
+ SecureRandom.hex(12)
60
+ end
61
+
62
+ def self.async_indexing_repo?(repo)
63
+ return false unless repo.is_a?(Class) && repo < Esse::Repository
64
+
65
+ repo.respond_to?(:implement_batch_ids?) && repo.implement_batch_ids?
66
+ end
67
+ end
68
+
69
+ Esse::Config.__send__ :include, Esse::AsyncIndexing::Config
70
+ if defined?(Esse::CLI)
71
+ require_relative "async_indexing/cli"
72
+ end