kaal 0.3.0 → 0.5.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +43 -11
  3. data/lib/kaal/active_record_support.rb +82 -0
  4. data/lib/kaal/backend/adapter.rb +4 -0
  5. data/lib/kaal/backend/memory_adapter.rb +5 -0
  6. data/lib/kaal/backend/mysql.rb +63 -0
  7. data/lib/kaal/backend/postgres.rb +45 -0
  8. data/lib/kaal/backend/redis_adapter.rb +5 -0
  9. data/lib/kaal/backend/sqlite.rb +45 -0
  10. data/lib/kaal/cli.rb +1 -0
  11. data/lib/kaal/config/configuration.rb +33 -2
  12. data/lib/kaal/config/delayed_job_security_policy.rb +60 -0
  13. data/lib/kaal/config.rb +1 -0
  14. data/lib/kaal/core/coordinator.rb +68 -19
  15. data/lib/kaal/definition/database_engine.rb +88 -0
  16. data/lib/kaal/delayed_job/database_engine.rb +116 -0
  17. data/lib/kaal/delayed_job/dispatch_failure_logger.rb +31 -0
  18. data/lib/kaal/delayed_job/memory_engine.rb +79 -0
  19. data/lib/kaal/delayed_job/mysql_version_support.rb +43 -0
  20. data/lib/kaal/delayed_job/redis_engine.rb +119 -0
  21. data/lib/kaal/delayed_job/registry.rb +39 -0
  22. data/lib/kaal/dispatch/database_engine.rb +120 -0
  23. data/lib/kaal/internal/active_record/base_record.rb +16 -0
  24. data/lib/kaal/internal/active_record/connection_support.rb +96 -0
  25. data/lib/kaal/internal/active_record/database_backend.rb +78 -0
  26. data/lib/kaal/internal/active_record/definition_record.rb +16 -0
  27. data/lib/kaal/internal/active_record/definition_registry.rb +81 -0
  28. data/lib/kaal/internal/active_record/delayed_job_record.rb +16 -0
  29. data/lib/kaal/internal/active_record/delayed_job_registry.rb +119 -0
  30. data/lib/kaal/internal/active_record/dispatch_record.rb +16 -0
  31. data/lib/kaal/internal/active_record/dispatch_registry.rb +100 -0
  32. data/lib/kaal/internal/active_record/lock_record.rb +16 -0
  33. data/lib/kaal/internal/active_record/migration_templates.rb +138 -0
  34. data/lib/kaal/internal/active_record/mysql_backend.rb +89 -0
  35. data/lib/kaal/internal/active_record/postgres_backend.rb +73 -0
  36. data/lib/kaal/internal/active_record.rb +19 -0
  37. data/lib/kaal/internal/sequel/database_backend.rb +79 -0
  38. data/lib/kaal/internal/sequel/mysql_backend.rb +83 -0
  39. data/lib/kaal/internal/sequel/postgres_backend.rb +71 -0
  40. data/lib/kaal/internal/sequel.rb +13 -0
  41. data/lib/kaal/job_dispatcher.rb +108 -0
  42. data/lib/kaal/persistence/database.rb +39 -0
  43. data/lib/kaal/persistence/migration_templates.rb +129 -0
  44. data/lib/kaal/registry.rb +0 -2
  45. data/lib/kaal/runtime/scheduler_boot_loader.rb +2 -0
  46. data/lib/kaal/scheduler_file/job_applier.rb +28 -53
  47. data/lib/kaal/sequel_support.rb +82 -0
  48. data/lib/kaal/version.rb +1 -1
  49. data/lib/kaal.rb +117 -0
  50. metadata +36 -1
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+ module Kaal
8
+ module Internal
9
+ module ActiveRecord
10
+ # Establishes and reuses the Active Record connection for adapter models.
11
+ module ConnectionSupport
12
+ CONFIGURE_MUTEX = Mutex.new
13
+
14
+ module_function
15
+
16
+ def configure!(connection = nil)
17
+ return BaseRecord unless connection
18
+
19
+ CONFIGURE_MUTEX.synchronize do
20
+ current_config = current_connection_config
21
+ target_config = normalize_connection_config(connection)
22
+ return BaseRecord if configs_match?(current_config, target_config) && connection_active?
23
+
24
+ BaseRecord.establish_connection(connection)
25
+ end
26
+ BaseRecord
27
+ end
28
+
29
+ def normalize_connection_config(connection)
30
+ config = extract_connection_config(connection)
31
+ return connection unless config
32
+
33
+ config.each_with_object({}) do |(key, value), normalized|
34
+ normalized_key = key.to_sym
35
+ normalized[normalized_key] = normalize_connection_value(normalized_key, value)
36
+ end
37
+ end
38
+
39
+ def current_connection_config
40
+ db_config = BaseRecord.connection_db_config
41
+ normalize_connection_config(extract_connection_config(db_config))
42
+ rescue ::ActiveRecord::ConnectionNotEstablished
43
+ nil
44
+ end
45
+
46
+ def extract_connection_config(connection)
47
+ case connection
48
+ when Hash
49
+ connection
50
+ when String
51
+ { url: connection }
52
+ else
53
+ config = connection.configuration_hash
54
+ url = begin
55
+ connection.url
56
+ rescue NoMethodError
57
+ nil
58
+ end
59
+ url ? config.merge(url: url) : config
60
+ end
61
+ rescue NoMethodError
62
+ nil
63
+ end
64
+
65
+ def normalize_connection_value(key, value)
66
+ case key
67
+ when :adapter
68
+ value.to_s.downcase
69
+ when :port
70
+ integer_like?(value) ? value.to_i : value
71
+ else
72
+ value
73
+ end
74
+ end
75
+
76
+ def integer_like?(value)
77
+ value.is_a?(Integer) || value.to_s.match?(/\A\d+\z/)
78
+ end
79
+
80
+ def configs_match?(current_config, target_config)
81
+ return true if current_config == target_config
82
+
83
+ current_url = current_config.is_a?(Hash) ? current_config[:url] : nil
84
+ target_url = target_config.is_a?(Hash) ? target_config[:url] : nil
85
+ !!(current_url && target_url && current_url == target_url)
86
+ end
87
+
88
+ def connection_active?
89
+ BaseRecord.connection.active?
90
+ rescue ::ActiveRecord::ConnectionNotEstablished, StandardError
91
+ false
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+ require 'kaal/backend/adapter'
8
+ require 'kaal/backend/dispatch_logging'
9
+ require 'kaal/internal/active_record/delayed_job_registry'
10
+
11
+ module Kaal
12
+ module Internal
13
+ module ActiveRecord
14
+ # Table-backed lock engine used for SQLite-style Active Record storage.
15
+ class DatabaseBackend < Kaal::Backend::Adapter
16
+ include Kaal::Backend::DispatchLogging
17
+
18
+ def initialize(connection = nil, lock_model: LockRecord, dispatch_registry: nil, definition_registry: nil, namespace: nil)
19
+ super()
20
+ ConnectionSupport.configure!(connection)
21
+ @lock_model = lock_model
22
+ @dispatch_registry = dispatch_registry
23
+ @definition_registry = definition_registry
24
+ @namespace = namespace
25
+ end
26
+
27
+ def dispatch_registry
28
+ @dispatch_registry ||= DispatchRegistry.new(namespace: resolved_namespace)
29
+ end
30
+
31
+ def definition_registry
32
+ @definition_registry ||= DefinitionRegistry.new
33
+ end
34
+
35
+ def delayed_store
36
+ @delayed_store ||= DelayedJobRegistry.new
37
+ end
38
+
39
+ def acquire(key, ttl)
40
+ now = Time.now.utc
41
+ expires_at = now + ttl
42
+
43
+ 2.times do |attempt|
44
+ cleanup_expired_locks if attempt.positive?
45
+
46
+ begin
47
+ @lock_model.create!(key: key, acquired_at: now, expires_at: expires_at)
48
+ log_dispatch_attempt(key)
49
+ return true
50
+ rescue ::ActiveRecord::RecordNotUnique
51
+ next
52
+ end
53
+ end
54
+
55
+ false
56
+ rescue StandardError => e
57
+ raise Kaal::Backend::LockAdapterError, "Database acquire failed for #{key}: #{e.message}"
58
+ end
59
+
60
+ def release(key)
61
+ @lock_model.where(key: key).delete_all.positive?
62
+ rescue StandardError => e
63
+ raise Kaal::Backend::LockAdapterError, "Database release failed for #{key}: #{e.message}"
64
+ end
65
+
66
+ def cleanup_expired_locks
67
+ @lock_model.where(expires_at: ...Time.now.utc).delete_all
68
+ end
69
+
70
+ private
71
+
72
+ def resolved_namespace
73
+ @namespace || Kaal.configuration.namespace
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+ module Kaal
8
+ module Internal
9
+ module ActiveRecord
10
+ # Active Record model for persisted scheduler definitions.
11
+ class DefinitionRecord < BaseRecord
12
+ self.table_name = 'kaal_definitions'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+ require 'json'
8
+ require 'kaal/definition/registry'
9
+ require 'kaal/definition/persistence_helpers'
10
+
11
+ module Kaal
12
+ module Internal
13
+ module ActiveRecord
14
+ # Active Record-backed registry for scheduler definitions.
15
+ class DefinitionRegistry < Kaal::Definition::Registry
16
+ def initialize(connection: nil, model: DefinitionRecord)
17
+ super()
18
+ ConnectionSupport.configure!(connection)
19
+ @model = model
20
+ end
21
+
22
+ def upsert_definition(key:, cron:, enabled: true, source: 'code', metadata: {})
23
+ record = @model.find_or_initialize_by(key: key)
24
+ existing = record.persisted? ? { enabled: record.enabled, disabled_at: record.disabled_at } : nil
25
+ now = Time.now.utc
26
+ record.cron = cron
27
+ record.enabled = enabled
28
+ record.source = source
29
+ record.metadata = JSON.generate(metadata || {})
30
+ record.created_at ||= now
31
+ record.updated_at = now
32
+ record.disabled_at = Kaal::Definition::PersistenceHelpers.disabled_at_for(existing, enabled, now)
33
+ record.save!
34
+ normalize(record)
35
+ end
36
+
37
+ def remove_definition(key)
38
+ record = @model.find_by(key: key)
39
+ return nil unless record
40
+
41
+ normalized = normalize(record)
42
+ record.destroy!
43
+ normalized
44
+ end
45
+
46
+ def find_definition(key)
47
+ normalize(@model.find_by(key: key))
48
+ end
49
+
50
+ def all_definitions
51
+ @model.order(:key).map { |record| normalize(record) }
52
+ end
53
+
54
+ def enabled_definitions
55
+ @model.where(enabled: true).order(:key).map { |record| normalize(record) }
56
+ end
57
+
58
+ private
59
+
60
+ def normalize(record)
61
+ return nil unless record
62
+
63
+ normalize_definition_record(record)
64
+ end
65
+
66
+ def normalize_definition_record(record)
67
+ {
68
+ key: record.key,
69
+ cron: record.cron,
70
+ enabled: record.enabled ? true : false,
71
+ source: record.source,
72
+ metadata: Kaal::Definition::PersistenceHelpers.parse_metadata(record.metadata),
73
+ created_at: record.created_at,
74
+ updated_at: record.updated_at,
75
+ disabled_at: record.disabled_at
76
+ }
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+ module Kaal
8
+ module Internal
9
+ module ActiveRecord
10
+ # Active Record model for persisted delayed jobs.
11
+ class DelayedJobRecord < BaseRecord
12
+ self.table_name = 'kaal_delayed_jobs'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+ require 'json'
8
+ require 'kaal/delayed_job/registry'
9
+
10
+ module Kaal
11
+ module Internal
12
+ module ActiveRecord
13
+ # Active Record-backed store for delayed jobs.
14
+ class DelayedJobRegistry < Kaal::DelayedJob::Registry
15
+ def initialize(connection: nil, model: DelayedJobRecord, use_skip_locked: false)
16
+ super()
17
+ ConnectionSupport.configure!(connection)
18
+ @model = model
19
+ @use_skip_locked = use_skip_locked
20
+ end
21
+
22
+ def enqueue(job_id:, run_at:, job_class:, args:, queue: nil, connection: nil)
23
+ now = Time.now.utc
24
+ attributes = {
25
+ job_id: job_id,
26
+ run_at: run_at,
27
+ job_class: job_class,
28
+ args: JSON.generate(args),
29
+ queue: queue,
30
+ created_at: now
31
+ }
32
+
33
+ if connection
34
+ insert_with_connection(connection, attributes)
35
+ else
36
+ @model.create!(attributes)
37
+ end
38
+
39
+ self.class.normalize(@model.new(attributes))
40
+ rescue ::ActiveRecord::RecordNotUnique
41
+ raise Kaal::DelayedJob::DuplicateJobError, "Delayed job #{job_id.inspect} already exists"
42
+ end
43
+
44
+ def pop_due(now:, limit:)
45
+ return pop_due_with_skip_locked(now:, limit:) if @use_skip_locked
46
+
47
+ pop_due_with_delete_confirmation(now:, limit:)
48
+ end
49
+
50
+ private
51
+
52
+ def pop_due_with_skip_locked(now:, limit:)
53
+ @model.transaction do
54
+ due_records = @model.where('run_at <= ?', now).order(:run_at, :job_id).lock('FOR UPDATE SKIP LOCKED').limit(limit).to_a
55
+ job_ids = due_records.map(&:job_id)
56
+ @model.where(job_id: job_ids).delete_all unless job_ids.empty?
57
+ due_records.filter_map { |record| self.class.normalize(record) }
58
+ end
59
+ end
60
+
61
+ def pop_due_with_delete_confirmation(now:, limit:)
62
+ @model.transaction do
63
+ @model.where('run_at <= ?', now).order(:run_at, :job_id).limit(limit).each_with_object([]) do |record, jobs|
64
+ normalized_job = self.class.normalize(record)
65
+ jobs << normalized_job if @model.where(job_id: record.job_id).delete_all.positive? && normalized_job
66
+ end
67
+ end
68
+ end
69
+
70
+ public
71
+
72
+ def find_job(job_id)
73
+ self.class.normalize(@model.find_by(job_id: job_id))
74
+ end
75
+
76
+ def all_jobs
77
+ @model.order(:run_at, :job_id).filter_map { |record| self.class.normalize(record) }
78
+ end
79
+
80
+ def claim_strategy
81
+ @use_skip_locked ? :skip_locked : :delete_confirmation
82
+ end
83
+
84
+ def self.normalize(record)
85
+ return nil unless record
86
+
87
+ {
88
+ job_id: record.job_id,
89
+ run_at: record.run_at,
90
+ job_class: record.job_class,
91
+ args: parse_args(record.args),
92
+ queue: record.queue,
93
+ created_at: record.created_at
94
+ }
95
+ rescue JSON::ParserError
96
+ nil
97
+ end
98
+
99
+ private
100
+
101
+ def insert_with_connection(connection, attributes)
102
+ table_name = @model.table_name
103
+ columns = attributes.keys
104
+ quoted_pairs = columns.map do |column|
105
+ [connection.quote_column_name(column), connection.quote(attributes.fetch(column))]
106
+ end
107
+ quoted_columns = quoted_pairs.map(&:first).join(', ')
108
+ quoted_values = quoted_pairs.map(&:last).join(', ')
109
+ connection.execute("INSERT INTO #{connection.quote_table_name(table_name)} (#{quoted_columns}) VALUES (#{quoted_values})")
110
+ end
111
+
112
+ def self.parse_args(args_payload)
113
+ JSON.parse(args_payload || '[]')
114
+ end
115
+ private_class_method :parse_args
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+ module Kaal
8
+ module Internal
9
+ module ActiveRecord
10
+ # Active Record model for persisted dispatch audit entries.
11
+ class DispatchRecord < BaseRecord
12
+ self.table_name = 'kaal_dispatches'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+ require 'kaal/dispatch/registry'
8
+
9
+ module Kaal
10
+ module Internal
11
+ module ActiveRecord
12
+ # Active Record-backed registry for dispatch audit records.
13
+ class DispatchRegistry < Kaal::Dispatch::Registry
14
+ def initialize(connection: nil, model: DispatchRecord, namespace: nil)
15
+ super()
16
+ ConnectionSupport.configure!(connection)
17
+ @model = model
18
+ @namespace = namespace
19
+ end
20
+
21
+ def log_dispatch(key, fire_time, node_id, status = 'dispatched')
22
+ record = @model.find_or_initialize_by(key: namespaced_key(key), fire_time: fire_time)
23
+ record.dispatched_at = Time.now.utc
24
+ record.node_id = node_id
25
+ record.status = status
26
+ record.save!
27
+ normalize(record)
28
+ end
29
+
30
+ def find_dispatch(key, fire_time)
31
+ normalize(@model.find_by(key: namespaced_key(key), fire_time: fire_time))
32
+ end
33
+
34
+ def find_by_key(key)
35
+ query(key: namespaced_key(key))
36
+ end
37
+
38
+ def find_by_node(node_id)
39
+ query(node_id: node_id)
40
+ end
41
+
42
+ def find_by_status(status)
43
+ query(status: status)
44
+ end
45
+
46
+ def cleanup(recovery_window: 86_400)
47
+ cutoff_time = Time.now.utc - recovery_window
48
+ cleanup_scope.where(fire_time: ...cutoff_time).delete_all
49
+ end
50
+
51
+ private
52
+
53
+ def query(filters)
54
+ query_scope(filters).order(fire_time: :desc).map { |record| normalize(record) }
55
+ end
56
+
57
+ def namespaced_key(key)
58
+ "#{namespace_prefix}#{key}"
59
+ end
60
+
61
+ def normalize(record)
62
+ return nil unless record
63
+
64
+ {
65
+ key: strip_namespace(record.key),
66
+ fire_time: record.fire_time,
67
+ dispatched_at: record.dispatched_at,
68
+ node_id: record.node_id,
69
+ status: record.status
70
+ }
71
+ end
72
+
73
+ def strip_namespace(key)
74
+ key.delete_prefix(namespace_prefix)
75
+ end
76
+
77
+ def query_scope(filters)
78
+ relation = @model.where(filters)
79
+ return relation if filters.key?(:key)
80
+
81
+ namespace_scope(relation)
82
+ end
83
+
84
+ def cleanup_scope
85
+ namespace_scope(@model)
86
+ end
87
+
88
+ def namespace_scope(relation)
89
+ return relation if namespace_prefix.empty?
90
+
91
+ relation.where('key LIKE ?', "#{namespace_prefix}%")
92
+ end
93
+
94
+ def namespace_prefix
95
+ @namespace.to_s.empty? ? '' : "#{@namespace}:"
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+ module Kaal
8
+ module Internal
9
+ module ActiveRecord
10
+ # Active Record model for table-backed scheduler locks.
11
+ class LockRecord < BaseRecord
12
+ self.table_name = 'kaal_locks'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+ module Kaal
8
+ module Internal
9
+ module ActiveRecord
10
+ # Rails migration templates for Active Record-backed Kaal tables.
11
+ module MigrationTemplates
12
+ module_function
13
+
14
+ def for_backend(backend)
15
+ case backend.to_s
16
+ when 'sqlite'
17
+ {
18
+ '001_create_kaal_dispatches.rb' => dispatches_template,
19
+ '002_create_kaal_locks.rb' => locks_template,
20
+ '003_create_kaal_definitions.rb' => definitions_template('sqlite'),
21
+ '004_create_kaal_delayed_jobs.rb' => delayed_jobs_template('sqlite')
22
+ }
23
+ when 'postgres'
24
+ {
25
+ '001_create_kaal_dispatches.rb' => dispatches_template,
26
+ '002_create_kaal_definitions.rb' => definitions_template('postgres'),
27
+ '003_create_kaal_delayed_jobs.rb' => delayed_jobs_template('postgres')
28
+ }
29
+ when 'mysql'
30
+ {
31
+ '001_create_kaal_dispatches.rb' => dispatches_template,
32
+ '002_create_kaal_definitions.rb' => definitions_template('mysql'),
33
+ '003_create_kaal_delayed_jobs.rb' => delayed_jobs_template('mysql')
34
+ }
35
+ else
36
+ {}
37
+ end
38
+ end
39
+
40
+ def dispatches_template
41
+ <<~RUBY
42
+ class CreateKaalDispatches < ActiveRecord::Migration[7.1]
43
+ def change
44
+ create_table :kaal_dispatches do |t|
45
+ t.string :key, null: false
46
+ t.datetime :fire_time, null: false
47
+ t.datetime :dispatched_at, null: false
48
+ t.string :node_id, null: false
49
+ t.string :status, null: false, default: 'dispatched', limit: 50
50
+ end
51
+
52
+ add_index :kaal_dispatches, [:key, :fire_time], unique: true
53
+ add_index :kaal_dispatches, :key
54
+ add_index :kaal_dispatches, :node_id
55
+ add_index :kaal_dispatches, :status
56
+ add_index :kaal_dispatches, :fire_time
57
+ end
58
+ end
59
+ RUBY
60
+ end
61
+
62
+ def locks_template
63
+ <<~RUBY
64
+ class CreateKaalLocks < ActiveRecord::Migration[7.1]
65
+ def change
66
+ create_table :kaal_locks do |t|
67
+ t.string :key, null: false
68
+ t.datetime :acquired_at, null: false
69
+ t.datetime :expires_at, null: false
70
+ end
71
+
72
+ add_index :kaal_locks, :key, unique: true
73
+ add_index :kaal_locks, :expires_at
74
+ end
75
+ end
76
+ RUBY
77
+ end
78
+
79
+ def definitions_template(backend)
80
+ metadata_definition =
81
+ if backend == 'mysql'
82
+ 't.text :metadata, null: false'
83
+ else
84
+ "t.text :metadata, null: false, default: '{}'"
85
+ end
86
+
87
+ <<~RUBY
88
+ class CreateKaalDefinitions < ActiveRecord::Migration[7.1]
89
+ def change
90
+ create_table :kaal_definitions do |t|
91
+ t.string :key, null: false
92
+ t.string :cron, null: false
93
+ t.boolean :enabled, null: false, default: true
94
+ t.string :source, null: false
95
+ #{metadata_definition}
96
+ t.datetime :disabled_at
97
+ t.datetime :created_at, null: false
98
+ t.datetime :updated_at, null: false
99
+ end
100
+
101
+ add_index :kaal_definitions, :key, unique: true
102
+ add_index :kaal_definitions, :enabled
103
+ add_index :kaal_definitions, :source
104
+ end
105
+ end
106
+ RUBY
107
+ end
108
+
109
+ def delayed_jobs_template(backend)
110
+ args_definition =
111
+ if backend == 'mysql'
112
+ 't.text :args, null: false'
113
+ else
114
+ "t.text :args, null: false, default: '[]'"
115
+ end
116
+
117
+ <<~RUBY
118
+ class CreateKaalDelayedJobs < ActiveRecord::Migration[7.1]
119
+ def change
120
+ create_table :kaal_delayed_jobs do |t|
121
+ t.string :job_id, null: false
122
+ t.datetime :run_at, null: false
123
+ t.string :job_class, null: false
124
+ #{args_definition}
125
+ t.string :queue
126
+ t.datetime :created_at, null: false
127
+ end
128
+
129
+ add_index :kaal_delayed_jobs, :job_id, unique: true
130
+ add_index :kaal_delayed_jobs, :run_at
131
+ end
132
+ end
133
+ RUBY
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end