solid_queue_mongoid 0.3.0

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 (80) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/settings.local.json +38 -0
  3. data/.idea/copilot.data.migration.ask2agent.xml +6 -0
  4. data/.idea/inspectionProfiles/Project_Default.xml +5 -0
  5. data/.idea/jsLibraryMappings.xml +6 -0
  6. data/.idea/misc.xml +17 -0
  7. data/.idea/modules/bigdecimal-4.0.iml +18 -0
  8. data/.idea/modules/builder-3.3.iml +18 -0
  9. data/.idea/modules/concurrent-ruby-1.3.iml +21 -0
  10. data/.idea/modules/connection_pool-3.0.iml +18 -0
  11. data/.idea/modules/crass-1.0.iml +19 -0
  12. data/.idea/modules/docile-1.4.iml +20 -0
  13. data/.idea/modules/drb-2.2.iml +18 -0
  14. data/.idea/modules/erb-6.0.iml +23 -0
  15. data/.idea/modules/et-orbi-1.4.iml +20 -0
  16. data/.idea/modules/fugit-1.12.iml +18 -0
  17. data/.idea/modules/irb-1.17.iml +26 -0
  18. data/.idea/modules/json-2.18.iml +18 -0
  19. data/.idea/modules/lint_roller-1.1.iml +18 -0
  20. data/.idea/modules/mongo-2.23.iml +19 -0
  21. data/.idea/modules/nokogiri-1.19.iml +19 -0
  22. data/.idea/modules/parser-3.3.10.iml +19 -0
  23. data/.idea/modules/pp-0.6.iml +18 -0
  24. data/.idea/modules/prettyprint-0.2.iml +22 -0
  25. data/.idea/modules/prism-1.9.iml +20 -0
  26. data/.idea/modules/raabro-1.4.iml +18 -0
  27. data/.idea/modules/rake-13.3.iml +22 -0
  28. data/.idea/modules/rdoc-7.2.iml +22 -0
  29. data/.idea/modules/regexp_parser-2.11.iml +20 -0
  30. data/.idea/modules/specifications.iml +18 -0
  31. data/.idea/modules/thor-1.5.iml +20 -0
  32. data/.idea/modules/timeout-0.6.iml +22 -0
  33. data/.idea/modules/tsort-0.2.iml +22 -0
  34. data/.idea/modules/unicode-emoji-4.2.iml +19 -0
  35. data/.idea/modules.xml +36 -0
  36. data/.idea/solid_queue_mongoid.iml +3297 -0
  37. data/.idea/vcs.xml +6 -0
  38. data/.idea/workspace.xml +353 -0
  39. data/.rspec +3 -0
  40. data/.rubocop.yml +47 -0
  41. data/ARCHITECTURE.md +91 -0
  42. data/CHANGELOG.md +27 -0
  43. data/CODE_OF_CONDUCT.md +132 -0
  44. data/LICENSE.txt +21 -0
  45. data/README.md +249 -0
  46. data/Rakefile +12 -0
  47. data/lib/solid_queue_mongoid/models/blocked_execution.rb +125 -0
  48. data/lib/solid_queue_mongoid/models/claimed_execution.rb +134 -0
  49. data/lib/solid_queue_mongoid/models/classes.rb +32 -0
  50. data/lib/solid_queue_mongoid/models/execution/dispatching.rb +23 -0
  51. data/lib/solid_queue_mongoid/models/execution/job_attributes.rb +54 -0
  52. data/lib/solid_queue_mongoid/models/execution.rb +65 -0
  53. data/lib/solid_queue_mongoid/models/failed_execution.rb +74 -0
  54. data/lib/solid_queue_mongoid/models/job/clearable.rb +28 -0
  55. data/lib/solid_queue_mongoid/models/job/concurrency_controls.rb +93 -0
  56. data/lib/solid_queue_mongoid/models/job/executable.rb +142 -0
  57. data/lib/solid_queue_mongoid/models/job/recurrable.rb +14 -0
  58. data/lib/solid_queue_mongoid/models/job/retryable.rb +51 -0
  59. data/lib/solid_queue_mongoid/models/job/schedulable.rb +55 -0
  60. data/lib/solid_queue_mongoid/models/job.rb +103 -0
  61. data/lib/solid_queue_mongoid/models/pause.rb +25 -0
  62. data/lib/solid_queue_mongoid/models/process/executor.rb +30 -0
  63. data/lib/solid_queue_mongoid/models/process/prunable.rb +49 -0
  64. data/lib/solid_queue_mongoid/models/process.rb +73 -0
  65. data/lib/solid_queue_mongoid/models/queue.rb +65 -0
  66. data/lib/solid_queue_mongoid/models/queue_selector.rb +101 -0
  67. data/lib/solid_queue_mongoid/models/ready_execution.rb +70 -0
  68. data/lib/solid_queue_mongoid/models/record.rb +147 -0
  69. data/lib/solid_queue_mongoid/models/recurring_execution.rb +62 -0
  70. data/lib/solid_queue_mongoid/models/recurring_task/arguments.rb +29 -0
  71. data/lib/solid_queue_mongoid/models/recurring_task.rb +194 -0
  72. data/lib/solid_queue_mongoid/models/scheduled_execution.rb +43 -0
  73. data/lib/solid_queue_mongoid/models/semaphore.rb +179 -0
  74. data/lib/solid_queue_mongoid/railtie.rb +29 -0
  75. data/lib/solid_queue_mongoid/version.rb +5 -0
  76. data/lib/solid_queue_mongoid.rb +136 -0
  77. data/lib/tasks/solid_queue_mongoid.rake +51 -0
  78. data/release.sh +13 -0
  79. data/sig/solid_queue_mongoid.rbs +4 -0
  80. metadata +173 -0
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fugit"
4
+
5
+ module SolidQueue
6
+ class RecurringTask < Record
7
+ field :key, type: String
8
+ field :schedule, type: String
9
+ field :command, type: String
10
+ field :class_name, type: String
11
+ field :arguments, type: Array, default: []
12
+ field :queue_name, type: String
13
+ field :priority, type: Integer, default: 0
14
+ field :description, type: String
15
+ field :static, type: Boolean, default: false
16
+
17
+ index({ key: 1 }, { unique: true })
18
+
19
+ scope :static, -> { where(static: true) }
20
+ scope :dynamic, -> { where(static: false) }
21
+
22
+ validates :key, presence: true
23
+
24
+ validate :ensure_schedule_supported
25
+ validate :ensure_command_or_class_present
26
+ validate :ensure_existing_job_class
27
+
28
+ has_many :recurring_executions, foreign_key: :task_key, primary_key: :key,
29
+ class_name: "SolidQueue::RecurringExecution"
30
+
31
+ mattr_accessor :default_job_class
32
+ self.default_job_class = "SolidQueue::RecurringJob".safe_constantize
33
+
34
+ class << self
35
+ def wrap(args)
36
+ args.is_a?(self) ? args : from_configuration(args.first, **args.second)
37
+ end
38
+
39
+ def from_configuration(key, **options)
40
+ new(
41
+ key: key,
42
+ class_name: options[:class],
43
+ command: options[:command],
44
+ arguments: Array(options[:args]),
45
+ schedule: options[:schedule],
46
+ queue_name: options[:queue].presence,
47
+ priority: options[:priority].presence,
48
+ description: options[:description],
49
+ static: options.fetch(:static, true)
50
+ )
51
+ end
52
+
53
+ def create_dynamic_task(key, **options)
54
+ from_configuration(key, **options.merge(static: false)).save!
55
+ end
56
+
57
+ def delete_dynamic_task(key)
58
+ RecurringTask.dynamic.find_by!(key: key).destroy
59
+ end
60
+
61
+ # Upsert all static tasks; used by Scheduler::RecurringSchedule#persist_tasks.
62
+ def create_or_update_all(tasks)
63
+ tasks.each do |task|
64
+ existing = where(key: task.key).first
65
+ if existing
66
+ existing.update!(task.attributes_for_upsert)
67
+ else
68
+ create!(task.attributes_for_upsert.merge(key: task.key))
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def delay_from_now
75
+ [(next_time - Time.current).to_f, 0.1].max
76
+ end
77
+
78
+ def next_time
79
+ parsed_schedule.next_time.utc
80
+ end
81
+
82
+ def previous_time
83
+ parsed_schedule.previous_time.utc
84
+ end
85
+
86
+ def last_enqueued_time
87
+ recurring_executions.maximum(:run_at)
88
+ end
89
+
90
+ def enqueue(at:)
91
+ SolidQueue.instrument(:enqueue_recurring_task, task: key, at: at) do |payload|
92
+ active_job = if using_solid_queue_adapter?
93
+ enqueue_and_record(run_at: at)
94
+ else
95
+ payload[:other_adapter] = true
96
+ perform_later.tap do |job|
97
+ payload[:enqueue_error] = job.enqueue_error&.message unless job.successfully_enqueued?
98
+ end
99
+ end
100
+
101
+ active_job.tap do |enqueued_job|
102
+ payload[:active_job_id] = enqueued_job.job_id if enqueued_job
103
+ end
104
+ rescue RecurringExecution::AlreadyRecorded
105
+ payload[:skipped] = true
106
+ false
107
+ rescue Job::EnqueueError => e
108
+ payload[:enqueue_error] = e.message
109
+ false
110
+ end
111
+ end
112
+
113
+ def to_s
114
+ "#{class_name}.perform_later(#{arguments.map(&:inspect).join(",")}) [ #{parsed_schedule.original} ]"
115
+ end
116
+
117
+ def attributes_for_upsert
118
+ attrs = attributes.except("_id", "id", "created_at", "updated_at")
119
+ attrs.delete("key")
120
+ attrs
121
+ end
122
+
123
+ private
124
+
125
+ def ensure_schedule_supported
126
+ unless parsed_schedule.instance_of?(Fugit::Cron)
127
+ errors.add :schedule, :unsupported, message: "is not a supported recurring schedule"
128
+ end
129
+ rescue ArgumentError => e
130
+ message = if e.message.include?("multiple crons")
131
+ "generates multiple cron schedules. Please use separate recurring tasks for each schedule, " \
132
+ "or use explicit cron syntax (e.g., '40 0,15 * * *' for multiple times with the same minutes)"
133
+ else
134
+ e.message
135
+ end
136
+ errors.add :schedule, :unsupported, message: message
137
+ end
138
+
139
+ def ensure_command_or_class_present
140
+ return if command.present? || class_name.present?
141
+
142
+ errors.add :base, :command_and_class_blank, message: "either command or class must be present"
143
+ end
144
+
145
+ def ensure_existing_job_class
146
+ return unless class_name.present? && job_class.nil?
147
+
148
+ errors.add :class_name, :undefined, message: "doesn't correspond to an existing class"
149
+ end
150
+
151
+ def using_solid_queue_adapter?
152
+ job_class.respond_to?(:queue_adapter_name) &&
153
+ job_class.queue_adapter_name.inquiry.solid_queue?
154
+ end
155
+
156
+ def enqueue_and_record(run_at:)
157
+ RecurringExecution.record(key, run_at) do
158
+ job_class.new(*arguments_with_kwargs).set(enqueue_options).tap do |active_job|
159
+ active_job.run_callbacks(:enqueue) do
160
+ Job.enqueue(active_job)
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ def perform_later
167
+ job_class.new(*arguments_with_kwargs).tap do |active_job|
168
+ active_job.enqueue(enqueue_options)
169
+ end
170
+ end
171
+
172
+ def arguments_with_kwargs
173
+ if class_name.nil?
174
+ command
175
+ elsif arguments.last.is_a?(Hash)
176
+ arguments[0...-1] + [Hash.ruby2_keywords_hash(arguments.last)]
177
+ else
178
+ arguments
179
+ end
180
+ end
181
+
182
+ def parsed_schedule
183
+ @parsed_schedule ||= Fugit.parse(schedule, multi: :fail)
184
+ end
185
+
186
+ def job_class
187
+ @job_class ||= class_name.present? ? class_name.safe_constantize : self.class.default_job_class
188
+ end
189
+
190
+ def enqueue_options
191
+ { queue: queue_name, priority: priority }.compact
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueue
4
+ class ScheduledExecution < Execution
5
+ include Dispatching
6
+
7
+ assumes_attributes_from_job :scheduled_at
8
+
9
+ field :scheduled_at, type: Time
10
+
11
+ scope :due, -> { where(:scheduled_at.lte => Time.current) }
12
+ scope :due_order, -> { order_by(scheduled_at: :asc, priority: :asc, job_id: :asc) }
13
+ scope :next_batch, ->(batch_size) { due.due_order.limit(batch_size) }
14
+
15
+ index({ scheduled_at: 1, priority: 1 })
16
+
17
+ class << self
18
+ def dispatch_next_batch(batch_size)
19
+ Mongoid.transaction do
20
+ SolidQueue.instrument(:dispatch_scheduled, batch_size: batch_size) do |payload|
21
+ job_ids = next_batch(batch_size).pluck(:job_id)
22
+ payload[:size] = if job_ids.empty?
23
+ 0
24
+ else
25
+ dispatch_jobs(job_ids)
26
+ end
27
+ payload[:size]
28
+ end
29
+ end
30
+ end
31
+
32
+ alias dispatch_due_batch dispatch_next_batch
33
+ end
34
+
35
+ # Instance method: dispatch this single execution if the job is due now.
36
+ # Mirrors the AR behaviour used in tests.
37
+ def dispatch
38
+ return unless job.due?
39
+
40
+ self.class.dispatch_jobs([job_id])
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueue
4
+ # Semaphore for concurrency control.
5
+ #
6
+ # Convention (matches spec expectations):
7
+ # value = number of USED (acquired) slots (0 = none in use)
8
+ # limit = maximum concurrent slots
9
+ #
10
+ # wait: acquire a slot — succeeds when value < limit (increments value)
11
+ # signal: release a slot — increments value (marks another slot returned)
12
+ #
13
+ # NOTE: This intentionally differs from the ActiveRecord original which uses
14
+ # value = remaining available slots. The specs and BlockedExecution logic here
15
+ # both expect the "used slots" convention.
16
+ class Semaphore < Record
17
+ field :key, type: String
18
+ field :value, type: Integer, default: 0 # number of currently USED slots
19
+ field :limit, type: Integer, default: 1
20
+ field :expires_at, type: Time
21
+
22
+ index({ key: 1 }, { unique: true })
23
+ index({ expires_at: 1 })
24
+
25
+ validates :key, presence: true, uniqueness: true
26
+
27
+ # available: value < limit (at least one slot still free to acquire)
28
+ scope :available, -> { where("$expr" => { "$lt" => ["$value", "$limit"] }) }
29
+ scope :expired, -> { where(:expires_at.lt => Time.current) }
30
+
31
+ class << self
32
+ def wait(job)
33
+ Proxy.new(job).wait
34
+ end
35
+
36
+ def signal(job)
37
+ Proxy.new(job).signal
38
+ end
39
+
40
+ def signal_all(jobs)
41
+ Proxy.signal_all(jobs)
42
+ end
43
+
44
+ # Requires a unique index on key.
45
+ # Returns true if created/inserted; false on duplicate.
46
+ def create_unique_by(attributes)
47
+ create!(attributes)
48
+ true
49
+ rescue Mongoid::Errors::Validations, Mongo::Error::OperationFailure => e
50
+ raise unless duplicate_key_error?(e)
51
+
52
+ false
53
+ end
54
+
55
+ private
56
+
57
+ def duplicate_key_error?(err)
58
+ err.message.to_s.include?("E11000") || err.message.to_s.include?("duplicate key")
59
+ end
60
+ end
61
+
62
+ # ── Instance methods ──────────────────────────────────────────────────────
63
+
64
+ # Atomically acquire one slot (increment value if value < limit).
65
+ # Returns true on success, false when at limit.
66
+ def acquire
67
+ result = self.class.collection.find_one_and_update(
68
+ { _id: id, "value" => { "$lt" => limit } },
69
+ { "$inc" => { "value" => 1 } },
70
+ return_document: :after
71
+ )
72
+ result.present?
73
+ end
74
+
75
+ # Release one slot (decrement value). No-op if already at 0.
76
+ def release
77
+ self.class.collection.find_one_and_update(
78
+ { _id: id, "value" => { "$gt" => 0 } },
79
+ { "$inc" => { "value" => -1 } },
80
+ return_document: :after
81
+ )
82
+ true
83
+ end
84
+
85
+ # True when there is still room to acquire (value < limit).
86
+ def available?
87
+ reload
88
+ value < limit
89
+ end
90
+
91
+ # ── Proxy inner class ─────────────────────────────────────────────────────
92
+ class Proxy
93
+ # Decrement value for every job's semaphore key (signal = release a used slot).
94
+ def self.signal_all(jobs)
95
+ keys = jobs.map(&:concurrency_key)
96
+ return if keys.empty?
97
+
98
+ Semaphore.in(key: keys).each do |sem|
99
+ Semaphore.collection.find_one_and_update(
100
+ { _id: sem.id, "value" => { "$gt" => 0 } },
101
+ { "$inc" => { "value" => -1 } }
102
+ )
103
+ end
104
+ end
105
+
106
+ def initialize(job)
107
+ @job = job
108
+ end
109
+
110
+ # Acquire a slot: succeeds when value < limit.
111
+ # Creates the semaphore document on first use.
112
+ def wait
113
+ semaphore = Semaphore.where(key: key).first
114
+
115
+ if semaphore
116
+ # Atomically increment if value < limit
117
+ attempt_acquire(semaphore.id, semaphore.limit)
118
+ else
119
+ attempt_creation
120
+ end
121
+ end
122
+
123
+ # Release a slot: decrement value (marks one used slot as freed).
124
+ def signal
125
+ attempt_release_slot
126
+ end
127
+
128
+ private
129
+
130
+ attr_reader :job
131
+
132
+ # Try to create the semaphore with value=1 (one slot in use).
133
+ def attempt_creation
134
+ lim = limit
135
+ if Semaphore.create_unique_by(key: key, value: 1, limit: lim, expires_at: expires_at)
136
+ true
137
+ else
138
+ # Race: someone else created it first — try to acquire from existing
139
+ sem = Semaphore.where(key: key).first
140
+ return false unless sem
141
+
142
+ attempt_acquire(sem.id, sem.limit)
143
+ end
144
+ end
145
+
146
+ # Atomically increment value if currently < limit.
147
+ def attempt_acquire(semaphore_id, lim)
148
+ result = Semaphore.collection.find_one_and_update(
149
+ { _id: semaphore_id, "value" => { "$lt" => lim } },
150
+ { "$inc" => { "value" => 1 }, "$set" => { "expires_at" => expires_at } },
151
+ return_document: :after
152
+ )
153
+ result.present?
154
+ end
155
+
156
+ # Atomically decrement value if currently > 0 (release one used slot).
157
+ def attempt_release_slot
158
+ result = Semaphore.collection.find_one_and_update(
159
+ { "key" => key, "value" => { "$gt" => 0 } },
160
+ { "$inc" => { "value" => -1 }, "$set" => { "expires_at" => expires_at } },
161
+ return_document: :after
162
+ )
163
+ result.present?
164
+ end
165
+
166
+ def key
167
+ job.concurrency_key
168
+ end
169
+
170
+ def expires_at
171
+ job.respond_to?(:concurrency_duration) ? job.concurrency_duration.from_now : 5.minutes.from_now
172
+ end
173
+
174
+ def limit
175
+ (job.respond_to?(:concurrency_limit) ? job.concurrency_limit : nil) || 1
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueMongoid
4
+ class Railtie < Rails::Railtie
5
+ rake_tasks do
6
+ load "tasks/solid_queue_mongoid.rake"
7
+ end
8
+
9
+ # Prevent SolidQueue's AR models from loading via Zeitwerk.
10
+ #
11
+ # Must run BEFORE :set_eager_load_paths (which freezes eager_load_paths)
12
+ # and before SolidQueue's own Railtie initializer adds its app/ path.
13
+ initializer "solid_queue_mongoid.shim",
14
+ before: :set_eager_load_paths do |app|
15
+ next unless defined?(SolidQueue::Engine)
16
+
17
+ sq_app_path = SolidQueue::Engine.root.join("app").to_s
18
+
19
+ # Tell Zeitwerk to ignore SolidQueue's app/ tree so it never autoloads
20
+ # the AR model files.
21
+ Rails.autoloaders.each do |loader|
22
+ loader.ignore(sq_app_path) if loader.respond_to?(:ignore)
23
+ end
24
+
25
+ # Remove from eager_load_paths before Rails freezes it.
26
+ app.config.eager_load_paths.delete_if { |p| p.start_with?(sq_app_path) }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueMongoid
4
+ VERSION = "0.3.0"
5
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "solid_queue_mongoid/version"
4
+ require "mongoid"
5
+ require "active_support/all"
6
+
7
+ # Load Railtie if in Rails — must happen before SolidQueue loads its AR models
8
+ require_relative "solid_queue_mongoid/railtie" if defined?(Rails::Railtie)
9
+
10
+ module SolidQueueMongoid
11
+ class Error < StandardError; end
12
+
13
+ # Configuration
14
+ mattr_accessor :client, :collection_prefix
15
+
16
+ @@client = :default
17
+ @@collection_prefix = "solid_queue_"
18
+
19
+ def self.configure
20
+ yield self
21
+ end
22
+
23
+ def self.create_indexes
24
+ all_models.each(&:create_indexes)
25
+ end
26
+
27
+ def self.remove_indexes
28
+ all_models.each(&:remove_indexes)
29
+ end
30
+
31
+ def self.all_models
32
+ [
33
+ SolidQueue::Job,
34
+ SolidQueue::ReadyExecution,
35
+ SolidQueue::ClaimedExecution,
36
+ SolidQueue::BlockedExecution,
37
+ SolidQueue::ScheduledExecution,
38
+ SolidQueue::FailedExecution,
39
+ SolidQueue::RecurringExecution,
40
+ SolidQueue::Process,
41
+ SolidQueue::Pause,
42
+ SolidQueue::Semaphore,
43
+ SolidQueue::RecurringTask
44
+ ]
45
+ end
46
+ end
47
+
48
+ # ─── Shim: inject SolidQueue namespace helpers before SolidQueue runtime loads ───
49
+ # SolidQueue runtime calls SolidQueue.client, SolidQueue.collection_prefix, etc.
50
+ # We also need clear_finished_jobs_after, process_alive_threshold, preserve_finished_jobs?
51
+ # — if SolidQueue gem is present those will already exist; if not we stub sensible defaults.
52
+ module SolidQueue
53
+ def self.client
54
+ SolidQueueMongoid.client
55
+ end
56
+
57
+ def self.collection_prefix
58
+ SolidQueueMongoid.collection_prefix
59
+ end
60
+
61
+ # These defaults mirror SolidQueue::Configuration defaults and are only
62
+ # active when solid_queue itself is not loaded.
63
+ unless respond_to?(:clear_finished_jobs_after)
64
+ def self.clear_finished_jobs_after
65
+ 1.day
66
+ end
67
+ end
68
+
69
+ unless respond_to?(:process_alive_threshold)
70
+ def self.process_alive_threshold
71
+ 5.minutes
72
+ end
73
+ end
74
+
75
+ unless respond_to?(:preserve_finished_jobs?)
76
+ def self.preserve_finished_jobs?
77
+ true
78
+ end
79
+ end
80
+
81
+ # Stub for SolidQueue.instrument — the real implementation in solid_queue
82
+ # uses ActiveSupport::Notifications. When solid_queue is loaded it already
83
+ # defines this, so we only add it when missing.
84
+ unless respond_to?(:instrument)
85
+ def self.instrument(_event, payload = {}, &block)
86
+ yield(payload) if block_given?
87
+ end
88
+ end
89
+ end
90
+
91
+ # ─── Load order ───────────────────────────────────────────────────────────────
92
+ # 1. Base record class
93
+ require_relative "solid_queue_mongoid/models/record"
94
+
95
+ # 2. Pre-declare all classes (avoids superclass mismatch)
96
+ require_relative "solid_queue_mongoid/models/classes"
97
+
98
+ # 3. Execution concerns
99
+ require_relative "solid_queue_mongoid/models/execution/job_attributes"
100
+ require_relative "solid_queue_mongoid/models/execution/dispatching"
101
+
102
+ # 4. Base execution
103
+ require_relative "solid_queue_mongoid/models/execution"
104
+
105
+ # 5. Job concerns (order matters — ConcurrencyControls/Schedulable/Retryable
106
+ # must exist before Executable includes them)
107
+ require_relative "solid_queue_mongoid/models/job/clearable"
108
+ require_relative "solid_queue_mongoid/models/job/recurrable"
109
+ require_relative "solid_queue_mongoid/models/job/schedulable"
110
+ require_relative "solid_queue_mongoid/models/job/retryable"
111
+ require_relative "solid_queue_mongoid/models/job/concurrency_controls"
112
+ require_relative "solid_queue_mongoid/models/job/executable"
113
+
114
+ # 6. Concrete models
115
+ require_relative "solid_queue_mongoid/models/job"
116
+ require_relative "solid_queue_mongoid/models/semaphore" # needed by BlockedExecution
117
+ require_relative "solid_queue_mongoid/models/ready_execution"
118
+ require_relative "solid_queue_mongoid/models/claimed_execution"
119
+ require_relative "solid_queue_mongoid/models/blocked_execution"
120
+ require_relative "solid_queue_mongoid/models/scheduled_execution"
121
+ require_relative "solid_queue_mongoid/models/failed_execution"
122
+ require_relative "solid_queue_mongoid/models/recurring_task/arguments"
123
+ require_relative "solid_queue_mongoid/models/recurring_task"
124
+ require_relative "solid_queue_mongoid/models/recurring_execution"
125
+ require_relative "solid_queue_mongoid/models/process/executor"
126
+ require_relative "solid_queue_mongoid/models/process/prunable"
127
+ require_relative "solid_queue_mongoid/models/process"
128
+ require_relative "solid_queue_mongoid/models/pause"
129
+ require_relative "solid_queue_mongoid/models/queue"
130
+ require_relative "solid_queue_mongoid/models/queue_selector"
131
+
132
+ # Pull in SolidQueue's runtime (engine, workers, dispatcher, etc.) after our
133
+ # Mongoid models are defined so they claim the SolidQueue::* namespace first.
134
+ # SolidQueue's AR model files live in app/models/ which is never required by
135
+ # solid_queue.rb itself — they're Rails-autoloaded and blocked by our Railtie.
136
+ require "solid_queue"
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :solid_queue_mongoid do
4
+ desc "Create MongoDB indexes for SolidQueue models"
5
+ task create_indexes: :environment do
6
+ require "solid_queue_mongoid"
7
+
8
+ puts "Creating indexes for SolidQueue Mongoid models..."
9
+ SolidQueueMongoid.create_indexes
10
+ end
11
+
12
+ desc "Remove MongoDB indexes for SolidQueue models"
13
+ task remove_indexes: :environment do
14
+ require "solid_queue_mongoid"
15
+
16
+ puts "Removing indexes for SolidQueue Mongoid models..."
17
+ SolidQueueMongoid.remove_indexes
18
+ end
19
+
20
+ desc "Show collection names for SolidQueue models"
21
+ task show_collections: :environment do
22
+ require "solid_queue_mongoid"
23
+
24
+ puts "\nSolidQueue Mongoid Collections:"
25
+ puts "--------------------------------"
26
+ puts "Configuration:"
27
+ puts " Client: #{SolidQueue.client}"
28
+ puts " Prefix: #{SolidQueue.collection_prefix}"
29
+ puts "\nCollections:"
30
+
31
+ models = [
32
+ SolidQueue::Job,
33
+ SolidQueue::ReadyExecution,
34
+ SolidQueue::ClaimedExecution,
35
+ SolidQueue::BlockedExecution,
36
+ SolidQueue::ScheduledExecution,
37
+ SolidQueue::FailedExecution,
38
+ SolidQueue::RecurringExecution,
39
+ SolidQueue::Process,
40
+ SolidQueue::Pause,
41
+ SolidQueue::Semaphore,
42
+ SolidQueue::RecurringTask,
43
+ SolidQueue::Queue
44
+ ]
45
+
46
+ models.each do |model|
47
+ puts " #{model.name.ljust(45)} => #{model.collection.name}"
48
+ end
49
+ puts ""
50
+ end
51
+ end
data/release.sh ADDED
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Extract version from version.rb
5
+ VERSION=$(ruby -r ./lib/solid_queue_mongoid/version.rb -e "puts SolidQueueMongoid::VERSION")
6
+
7
+ echo "Building gem version ${VERSION}..."
8
+ gem build solid_queue_mongoid.gemspec
9
+
10
+ echo "Pushing to RubyGems..."
11
+ gem push solid_queue_mongoid-${VERSION}.gem
12
+
13
+ echo "Done!"
@@ -0,0 +1,4 @@
1
+ module SolidQueueMongoid
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end