kaal-activerecord 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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +76 -0
- data/Rakefile +10 -0
- data/lib/kaal/active_record/base_record.rb +19 -0
- data/lib/kaal/active_record/connection_support.rb +88 -0
- data/lib/kaal/active_record/database_adapter.rb +73 -0
- data/lib/kaal/active_record/definition_record.rb +14 -0
- data/lib/kaal/active_record/definition_registry.rb +79 -0
- data/lib/kaal/active_record/dispatch_record.rb +14 -0
- data/lib/kaal/active_record/dispatch_registry.rb +98 -0
- data/lib/kaal/active_record/lock_record.rb +14 -0
- data/lib/kaal/active_record/migration_templates.rb +106 -0
- data/lib/kaal/active_record/mysql_adapter.rb +69 -0
- data/lib/kaal/active_record/postgres_adapter.rb +67 -0
- data/lib/kaal/active_record/railtie.rb +15 -0
- data/lib/kaal/active_record/sqlite_adapter.rb +7 -0
- data/lib/kaal/active_record/version.rb +11 -0
- data/lib/kaal/active_record.rb +28 -0
- data/lib/tasks/kaal/active_record_tasks.rake +8 -0
- metadata +120 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a7ad9284b605c193a77c6f998fc3f8d38b309075801bc25bad95f8b5d835a027
|
|
4
|
+
data.tar.gz: 671a2dfd4e3ea39ea43889c57258b6b0030170449bb63603d86a51c735e870fc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e5af0962d3d26f4b0e36835744adb1c2ee206c1e7bf95520e429d8ec7952a233cdb560d456b5e9fa9fd2e32ecfe32935498d0457428165c955180441d7b52fc5
|
|
7
|
+
data.tar.gz: 9d94b680eb0217568aa310fa48a070288f69eac29797d4912b8d8e1157de783d805965166b7b59e7d909dd0151709b6279b14975c8a11fa8cc89ec7f0fbbdd60
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present Codevedas Inc. and the Kaal Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Kaal::ActiveRecord
|
|
2
|
+
|
|
3
|
+
Active Record-backed datastore adapter for Kaal.
|
|
4
|
+
|
|
5
|
+
`kaal-activerecord` depends on `kaal` and owns the Active Record persistence layer:
|
|
6
|
+
|
|
7
|
+
- Active Record models for Kaal tables
|
|
8
|
+
- Active Record-backed definition registry
|
|
9
|
+
- Active Record-backed dispatch registry
|
|
10
|
+
- SQLite table-lock adapter
|
|
11
|
+
- PostgreSQL advisory-lock adapter
|
|
12
|
+
- MySQL named-lock adapter
|
|
13
|
+
- Rails-friendly migration templates
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
Plain Ruby:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
gem 'kaal'
|
|
21
|
+
gem 'kaal-activerecord'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Rails applications normally use `kaal-rails`, which already depends on this gem.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
SQLite in plain Ruby:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
require 'kaal'
|
|
32
|
+
require 'kaal/active_record'
|
|
33
|
+
|
|
34
|
+
Kaal::ActiveRecord::ConnectionSupport.configure!(
|
|
35
|
+
adapter: 'sqlite3',
|
|
36
|
+
database: 'db/kaal.sqlite3'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
Kaal.configure do |config|
|
|
40
|
+
config.backend = Kaal::ActiveRecord::DatabaseAdapter.new
|
|
41
|
+
config.scheduler_config_path = 'config/scheduler.yml'
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
PostgreSQL in plain Ruby:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
config.backend = Kaal::ActiveRecord::PostgresAdapter.new
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
MySQL in plain Ruby:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
config.backend = Kaal::ActiveRecord::MySQLAdapter.new
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use this gem directly when you want Active Record-backed SQL outside Rails. For Rails apps, use `kaal-rails`.
|
|
58
|
+
|
|
59
|
+
## Tables
|
|
60
|
+
|
|
61
|
+
The Active Record adapter persists against:
|
|
62
|
+
|
|
63
|
+
- `kaal_definitions`
|
|
64
|
+
- `kaal_dispatches`
|
|
65
|
+
- `kaal_locks`
|
|
66
|
+
|
|
67
|
+
`kaal_locks` is used for SQLite. PostgreSQL and MySQL rely on advisory or named locks and persist definitions and dispatches without a dedicated locks table.
|
|
68
|
+
|
|
69
|
+
## Development
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
bin/rspec-unit
|
|
73
|
+
bin/rspec-e2e sqlite
|
|
74
|
+
bin/rubocop
|
|
75
|
+
bin/reek
|
|
76
|
+
```
|
data/Rakefile
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
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 'bundler/setup'
|
|
8
|
+
APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
|
|
9
|
+
load 'rails/tasks/engine.rake'
|
|
10
|
+
require 'bundler/gem_tasks'
|
|
@@ -0,0 +1,19 @@
|
|
|
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 ActiveRecord
|
|
9
|
+
# Shared abstract ApplicationRecord class for Kaal tables.
|
|
10
|
+
class ApplicationRecord < ::ActiveRecord::Base
|
|
11
|
+
self.abstract_class = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Shared abstract Active Record base class for Kaal tables.
|
|
15
|
+
class BaseRecord < ApplicationRecord
|
|
16
|
+
self.abstract_class = true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
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 ActiveRecord
|
|
9
|
+
# Establishes and reuses the Active Record connection for adapter models.
|
|
10
|
+
module ConnectionSupport
|
|
11
|
+
CONFIGURE_MUTEX = Mutex.new
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def configure!(connection = nil)
|
|
16
|
+
return BaseRecord unless connection
|
|
17
|
+
|
|
18
|
+
CONFIGURE_MUTEX.synchronize do
|
|
19
|
+
current_config = current_connection_config
|
|
20
|
+
target_config = normalize_connection_config(connection)
|
|
21
|
+
return BaseRecord if configs_match?(current_config, target_config)
|
|
22
|
+
|
|
23
|
+
BaseRecord.establish_connection(connection)
|
|
24
|
+
end
|
|
25
|
+
BaseRecord
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def normalize_connection_config(connection)
|
|
29
|
+
config = extract_connection_config(connection)
|
|
30
|
+
return connection unless config
|
|
31
|
+
|
|
32
|
+
config.each_with_object({}) do |(key, value), normalized|
|
|
33
|
+
normalized_key = key.to_sym
|
|
34
|
+
normalized[normalized_key] = normalize_connection_value(normalized_key, value)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def current_connection_config
|
|
39
|
+
db_config = BaseRecord.connection_db_config
|
|
40
|
+
normalize_connection_config(extract_connection_config(db_config))
|
|
41
|
+
rescue ::ActiveRecord::ConnectionNotEstablished
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def extract_connection_config(connection)
|
|
46
|
+
case connection
|
|
47
|
+
when Hash
|
|
48
|
+
connection
|
|
49
|
+
when String
|
|
50
|
+
{ url: connection }
|
|
51
|
+
else
|
|
52
|
+
config = connection.configuration_hash
|
|
53
|
+
url = begin
|
|
54
|
+
connection.url
|
|
55
|
+
rescue NoMethodError
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
url ? config.merge(url: url) : config
|
|
59
|
+
end
|
|
60
|
+
rescue NoMethodError
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def normalize_connection_value(key, value)
|
|
65
|
+
case key
|
|
66
|
+
when :adapter
|
|
67
|
+
value.to_s.downcase
|
|
68
|
+
when :port
|
|
69
|
+
integer_like?(value) ? value.to_i : value
|
|
70
|
+
else
|
|
71
|
+
value
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def integer_like?(value)
|
|
76
|
+
value.is_a?(Integer) || value.to_s.match?(/\A\d+\z/)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def configs_match?(current_config, target_config)
|
|
80
|
+
return true if current_config == target_config
|
|
81
|
+
|
|
82
|
+
current_url = current_config.is_a?(Hash) ? current_config[:url] : nil
|
|
83
|
+
target_url = target_config.is_a?(Hash) ? target_config[:url] : nil
|
|
84
|
+
!!(current_url && target_url && current_url == target_url)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
|
|
10
|
+
module Kaal
|
|
11
|
+
module ActiveRecord
|
|
12
|
+
# Table-backed lock adapter used for SQLite-style Active Record storage.
|
|
13
|
+
class DatabaseAdapter < Kaal::Backend::Adapter
|
|
14
|
+
include Kaal::Backend::DispatchLogging
|
|
15
|
+
|
|
16
|
+
def initialize(connection = nil, lock_model: LockRecord, dispatch_registry: nil, definition_registry: nil, namespace: nil)
|
|
17
|
+
super()
|
|
18
|
+
ConnectionSupport.configure!(connection)
|
|
19
|
+
@lock_model = lock_model
|
|
20
|
+
@dispatch_registry = dispatch_registry
|
|
21
|
+
@definition_registry = definition_registry
|
|
22
|
+
@namespace = namespace
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def dispatch_registry
|
|
26
|
+
@dispatch_registry ||= DispatchRegistry.new(namespace: resolved_namespace)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def definition_registry
|
|
30
|
+
@definition_registry ||= DefinitionRegistry.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def acquire(key, ttl)
|
|
34
|
+
now = Time.now.utc
|
|
35
|
+
expires_at = now + ttl
|
|
36
|
+
|
|
37
|
+
2.times do |attempt|
|
|
38
|
+
cleanup_expired_locks if attempt.positive?
|
|
39
|
+
|
|
40
|
+
begin
|
|
41
|
+
@lock_model.create!(key: key, acquired_at: now, expires_at: expires_at)
|
|
42
|
+
log_dispatch_attempt(key)
|
|
43
|
+
return true
|
|
44
|
+
rescue ::ActiveRecord::RecordNotUnique
|
|
45
|
+
next
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
false
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
raise Kaal::Backend::LockAdapterError, "Database acquire failed for #{key}: #{e.message}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def release(key)
|
|
55
|
+
@lock_model.where(key: key).delete_all.positive?
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
raise Kaal::Backend::LockAdapterError, "Database release failed for #{key}: #{e.message}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def cleanup_expired_locks
|
|
61
|
+
@lock_model.where(expires_at: ...Time.now.utc).delete_all
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def resolved_namespace
|
|
67
|
+
@namespace || Kaal.configuration.namespace
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
SQLiteAdapter = DatabaseAdapter
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
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 ActiveRecord
|
|
9
|
+
# Active Record model for persisted scheduler definitions.
|
|
10
|
+
class DefinitionRecord < BaseRecord
|
|
11
|
+
self.table_name = 'kaal_definitions'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
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 ActiveRecord
|
|
13
|
+
# Active Record-backed registry for scheduler definitions.
|
|
14
|
+
class DefinitionRegistry < Kaal::Definition::Registry
|
|
15
|
+
def initialize(connection: nil, model: DefinitionRecord)
|
|
16
|
+
super()
|
|
17
|
+
ConnectionSupport.configure!(connection)
|
|
18
|
+
@model = model
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def upsert_definition(key:, cron:, enabled: true, source: 'code', metadata: {})
|
|
22
|
+
record = @model.find_or_initialize_by(key: key)
|
|
23
|
+
existing = record.persisted? ? { enabled: record.enabled, disabled_at: record.disabled_at } : nil
|
|
24
|
+
now = Time.now.utc
|
|
25
|
+
record.cron = cron
|
|
26
|
+
record.enabled = enabled
|
|
27
|
+
record.source = source
|
|
28
|
+
record.metadata = JSON.generate(metadata || {})
|
|
29
|
+
record.created_at ||= now
|
|
30
|
+
record.updated_at = now
|
|
31
|
+
record.disabled_at = Kaal::Definition::PersistenceHelpers.disabled_at_for(existing, enabled, now)
|
|
32
|
+
record.save!
|
|
33
|
+
normalize(record)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def remove_definition(key)
|
|
37
|
+
record = @model.find_by(key: key)
|
|
38
|
+
return nil unless record
|
|
39
|
+
|
|
40
|
+
normalized = normalize(record)
|
|
41
|
+
record.destroy!
|
|
42
|
+
normalized
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def find_definition(key)
|
|
46
|
+
normalize(@model.find_by(key: key))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def all_definitions
|
|
50
|
+
@model.order(:key).map { |record| normalize(record) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def enabled_definitions
|
|
54
|
+
@model.where(enabled: true).order(:key).map { |record| normalize(record) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def normalize(record)
|
|
60
|
+
return nil unless record
|
|
61
|
+
|
|
62
|
+
normalize_definition_record(record)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def normalize_definition_record(record)
|
|
66
|
+
{
|
|
67
|
+
key: record.key,
|
|
68
|
+
cron: record.cron,
|
|
69
|
+
enabled: record.enabled ? true : false,
|
|
70
|
+
source: record.source,
|
|
71
|
+
metadata: Kaal::Definition::PersistenceHelpers.parse_metadata(record.metadata),
|
|
72
|
+
created_at: record.created_at,
|
|
73
|
+
updated_at: record.updated_at,
|
|
74
|
+
disabled_at: record.disabled_at
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
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 ActiveRecord
|
|
9
|
+
# Active Record model for persisted dispatch audit entries.
|
|
10
|
+
class DispatchRecord < BaseRecord
|
|
11
|
+
self.table_name = 'kaal_dispatches'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
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 ActiveRecord
|
|
11
|
+
# Active Record-backed registry for dispatch audit records.
|
|
12
|
+
class DispatchRegistry < Kaal::Dispatch::Registry
|
|
13
|
+
def initialize(connection: nil, model: DispatchRecord, namespace: nil)
|
|
14
|
+
super()
|
|
15
|
+
ConnectionSupport.configure!(connection)
|
|
16
|
+
@model = model
|
|
17
|
+
@namespace = namespace
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def log_dispatch(key, fire_time, node_id, status = 'dispatched')
|
|
21
|
+
record = @model.find_or_initialize_by(key: namespaced_key(key), fire_time: fire_time)
|
|
22
|
+
record.dispatched_at = Time.now.utc
|
|
23
|
+
record.node_id = node_id
|
|
24
|
+
record.status = status
|
|
25
|
+
record.save!
|
|
26
|
+
normalize(record)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_dispatch(key, fire_time)
|
|
30
|
+
normalize(@model.find_by(key: namespaced_key(key), fire_time: fire_time))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def find_by_key(key)
|
|
34
|
+
query(key: namespaced_key(key))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def find_by_node(node_id)
|
|
38
|
+
query(node_id: node_id)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def find_by_status(status)
|
|
42
|
+
query(status: status)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def cleanup(recovery_window: 86_400)
|
|
46
|
+
cutoff_time = Time.now.utc - recovery_window
|
|
47
|
+
cleanup_scope.where(fire_time: ...cutoff_time).delete_all
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def query(filters)
|
|
53
|
+
query_scope(filters).order(fire_time: :desc).map { |record| normalize(record) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def namespaced_key(key)
|
|
57
|
+
"#{namespace_prefix}#{key}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def normalize(record)
|
|
61
|
+
return nil unless record
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
key: strip_namespace(record.key),
|
|
65
|
+
fire_time: record.fire_time,
|
|
66
|
+
dispatched_at: record.dispatched_at,
|
|
67
|
+
node_id: record.node_id,
|
|
68
|
+
status: record.status
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def strip_namespace(key)
|
|
73
|
+
key.delete_prefix(namespace_prefix)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def query_scope(filters)
|
|
77
|
+
relation = @model.where(filters)
|
|
78
|
+
return relation if filters.key?(:key)
|
|
79
|
+
|
|
80
|
+
namespace_scope(relation)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def cleanup_scope
|
|
84
|
+
namespace_scope(@model)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def namespace_scope(relation)
|
|
88
|
+
return relation if namespace_prefix.empty?
|
|
89
|
+
|
|
90
|
+
relation.where('key LIKE ?', "#{namespace_prefix}%")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def namespace_prefix
|
|
94
|
+
@namespace.to_s.empty? ? '' : "#{@namespace}:"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
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 ActiveRecord
|
|
9
|
+
# Active Record model for table-backed scheduler locks.
|
|
10
|
+
class LockRecord < BaseRecord
|
|
11
|
+
self.table_name = 'kaal_locks'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
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 ActiveRecord
|
|
9
|
+
# Rails migration templates for Active Record-backed Kaal tables.
|
|
10
|
+
module MigrationTemplates
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def for_backend(backend)
|
|
14
|
+
case backend.to_s
|
|
15
|
+
when 'sqlite'
|
|
16
|
+
{
|
|
17
|
+
'001_create_kaal_dispatches.rb' => dispatches_template,
|
|
18
|
+
'002_create_kaal_locks.rb' => locks_template,
|
|
19
|
+
'003_create_kaal_definitions.rb' => definitions_template('sqlite')
|
|
20
|
+
}
|
|
21
|
+
when 'postgres'
|
|
22
|
+
{
|
|
23
|
+
'001_create_kaal_dispatches.rb' => dispatches_template,
|
|
24
|
+
'002_create_kaal_definitions.rb' => definitions_template('postgres')
|
|
25
|
+
}
|
|
26
|
+
when 'mysql'
|
|
27
|
+
{
|
|
28
|
+
'001_create_kaal_dispatches.rb' => dispatches_template,
|
|
29
|
+
'002_create_kaal_definitions.rb' => definitions_template('mysql')
|
|
30
|
+
}
|
|
31
|
+
else
|
|
32
|
+
{}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def dispatches_template
|
|
37
|
+
<<~RUBY
|
|
38
|
+
class CreateKaalDispatches < ActiveRecord::Migration[7.1]
|
|
39
|
+
def change
|
|
40
|
+
create_table :kaal_dispatches do |t|
|
|
41
|
+
t.string :key, null: false
|
|
42
|
+
t.datetime :fire_time, null: false
|
|
43
|
+
t.datetime :dispatched_at, null: false
|
|
44
|
+
t.string :node_id, null: false
|
|
45
|
+
t.string :status, null: false, default: 'dispatched', limit: 50
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
add_index :kaal_dispatches, [:key, :fire_time], unique: true
|
|
49
|
+
add_index :kaal_dispatches, :key
|
|
50
|
+
add_index :kaal_dispatches, :node_id
|
|
51
|
+
add_index :kaal_dispatches, :status
|
|
52
|
+
add_index :kaal_dispatches, :fire_time
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
RUBY
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def locks_template
|
|
59
|
+
<<~RUBY
|
|
60
|
+
class CreateKaalLocks < ActiveRecord::Migration[7.1]
|
|
61
|
+
def change
|
|
62
|
+
create_table :kaal_locks do |t|
|
|
63
|
+
t.string :key, null: false
|
|
64
|
+
t.datetime :acquired_at, null: false
|
|
65
|
+
t.datetime :expires_at, null: false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
add_index :kaal_locks, :key, unique: true
|
|
69
|
+
add_index :kaal_locks, :expires_at
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
RUBY
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def definitions_template(backend)
|
|
76
|
+
metadata_definition =
|
|
77
|
+
if backend == 'mysql'
|
|
78
|
+
't.text :metadata, null: false'
|
|
79
|
+
else
|
|
80
|
+
"t.text :metadata, null: false, default: '{}'"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
<<~RUBY
|
|
84
|
+
class CreateKaalDefinitions < ActiveRecord::Migration[7.1]
|
|
85
|
+
def change
|
|
86
|
+
create_table :kaal_definitions do |t|
|
|
87
|
+
t.string :key, null: false
|
|
88
|
+
t.string :cron, null: false
|
|
89
|
+
t.boolean :enabled, null: false, default: true
|
|
90
|
+
t.string :source, null: false
|
|
91
|
+
#{metadata_definition}
|
|
92
|
+
t.datetime :disabled_at
|
|
93
|
+
t.datetime :created_at, null: false
|
|
94
|
+
t.datetime :updated_at, null: false
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
add_index :kaal_definitions, :key, unique: true
|
|
98
|
+
add_index :kaal_definitions, :enabled
|
|
99
|
+
add_index :kaal_definitions, :source
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
RUBY
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
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 'digest'
|
|
8
|
+
|
|
9
|
+
module Kaal
|
|
10
|
+
module ActiveRecord
|
|
11
|
+
# MySQL named-lock adapter paired with Active Record registries.
|
|
12
|
+
class MySQLAdapter < Kaal::Backend::Adapter
|
|
13
|
+
include Kaal::Backend::DispatchLogging
|
|
14
|
+
|
|
15
|
+
MAX_LOCK_NAME_LENGTH = 64
|
|
16
|
+
|
|
17
|
+
def initialize(connection = nil, dispatch_registry: nil, definition_registry: nil, namespace: nil)
|
|
18
|
+
super()
|
|
19
|
+
ConnectionSupport.configure!(connection)
|
|
20
|
+
@dispatch_registry = dispatch_registry
|
|
21
|
+
@definition_registry = definition_registry
|
|
22
|
+
@namespace = namespace
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def dispatch_registry
|
|
26
|
+
@dispatch_registry ||= DispatchRegistry.new(namespace: resolved_namespace)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def definition_registry
|
|
30
|
+
@definition_registry ||= DefinitionRegistry.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def acquire(key, _ttl)
|
|
34
|
+
acquired = scalar('SELECT GET_LOCK(?, 0) AS lock_result', self.class.normalize_lock_name(key)) == 1
|
|
35
|
+
log_dispatch_attempt(key) if acquired
|
|
36
|
+
acquired
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
raise Kaal::Backend::LockAdapterError, "MySQL acquire failed for #{key}: #{e.message}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def release(key)
|
|
42
|
+
scalar('SELECT RELEASE_LOCK(?) AS lock_result', self.class.normalize_lock_name(key)) == 1
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
raise Kaal::Backend::LockAdapterError, "MySQL release failed for #{key}: #{e.message}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.normalize_lock_name(key)
|
|
48
|
+
return key if key.length <= MAX_LOCK_NAME_LENGTH
|
|
49
|
+
|
|
50
|
+
digest = Digest::SHA256.hexdigest(key)
|
|
51
|
+
prefix_length = MAX_LOCK_NAME_LENGTH - 17
|
|
52
|
+
"#{key[0...prefix_length]}:#{digest[0...16]}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def scalar(sql, value)
|
|
58
|
+
result = BaseRecord.connection.exec_query(
|
|
59
|
+
BaseRecord.send(:sanitize_sql_array, [sql, value])
|
|
60
|
+
)
|
|
61
|
+
result.first.values.first
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def resolved_namespace
|
|
65
|
+
@namespace || Kaal.configuration.namespace
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
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 'digest'
|
|
8
|
+
|
|
9
|
+
module Kaal
|
|
10
|
+
module ActiveRecord
|
|
11
|
+
# PostgreSQL advisory-lock adapter paired with Active Record registries.
|
|
12
|
+
class PostgresAdapter < Kaal::Backend::Adapter
|
|
13
|
+
include Kaal::Backend::DispatchLogging
|
|
14
|
+
|
|
15
|
+
SIGNED_64_MAX = 9_223_372_036_854_775_807
|
|
16
|
+
UNSIGNED_64_RANGE = 18_446_744_073_709_551_616
|
|
17
|
+
|
|
18
|
+
def initialize(connection = nil, dispatch_registry: nil, definition_registry: nil, namespace: nil)
|
|
19
|
+
super()
|
|
20
|
+
ConnectionSupport.configure!(connection)
|
|
21
|
+
@dispatch_registry = dispatch_registry
|
|
22
|
+
@definition_registry = definition_registry
|
|
23
|
+
@namespace = namespace
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def dispatch_registry
|
|
27
|
+
@dispatch_registry ||= DispatchRegistry.new(namespace: resolved_namespace)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def definition_registry
|
|
31
|
+
@definition_registry ||= DefinitionRegistry.new
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def acquire(key, _ttl)
|
|
35
|
+
acquired = scalar('SELECT pg_try_advisory_lock(?) AS acquired', self.class.calculate_lock_id(key)) == true
|
|
36
|
+
log_dispatch_attempt(key) if acquired
|
|
37
|
+
acquired
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
raise Kaal::Backend::LockAdapterError, "PostgreSQL acquire failed for #{key}: #{e.message}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def release(key)
|
|
43
|
+
scalar('SELECT pg_advisory_unlock(?) AS released', self.class.calculate_lock_id(key)) == true
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
raise Kaal::Backend::LockAdapterError, "PostgreSQL release failed for #{key}: #{e.message}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.calculate_lock_id(key)
|
|
49
|
+
hash = Digest::MD5.digest(key).unpack1('Q>')
|
|
50
|
+
hash > SIGNED_64_MAX ? hash - UNSIGNED_64_RANGE : hash
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def scalar(sql, value)
|
|
56
|
+
result = BaseRecord.connection.exec_query(
|
|
57
|
+
BaseRecord.send(:sanitize_sql_array, [sql, value])
|
|
58
|
+
)
|
|
59
|
+
result.first.values.first
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def resolved_namespace
|
|
63
|
+
@namespace || Kaal.configuration.namespace
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
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 'rails/railtie'
|
|
8
|
+
|
|
9
|
+
module Kaal
|
|
10
|
+
module ActiveRecord
|
|
11
|
+
# Minimal Railtie so the adapter can integrate with Rails loading.
|
|
12
|
+
class Railtie < ::Rails::Railtie
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
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 ActiveRecord
|
|
9
|
+
VERSION = '0.3.0'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
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 'active_record'
|
|
8
|
+
require 'kaal'
|
|
9
|
+
require 'kaal/active_record/version'
|
|
10
|
+
require 'kaal/active_record/railtie'
|
|
11
|
+
require 'kaal/active_record/connection_support'
|
|
12
|
+
require 'kaal/active_record/base_record'
|
|
13
|
+
require 'kaal/active_record/definition_record'
|
|
14
|
+
require 'kaal/active_record/dispatch_record'
|
|
15
|
+
require 'kaal/active_record/lock_record'
|
|
16
|
+
require 'kaal/active_record/definition_registry'
|
|
17
|
+
require 'kaal/active_record/dispatch_registry'
|
|
18
|
+
require 'kaal/active_record/database_adapter'
|
|
19
|
+
require 'kaal/active_record/postgres_adapter'
|
|
20
|
+
require 'kaal/active_record/mysql_adapter'
|
|
21
|
+
require 'kaal/active_record/sqlite_adapter'
|
|
22
|
+
require 'kaal/active_record/migration_templates'
|
|
23
|
+
|
|
24
|
+
module Kaal
|
|
25
|
+
# Active Record-backed datastore adapter namespace for Kaal.
|
|
26
|
+
module ActiveRecord
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
|
|
8
|
+
# This file intentionally does not define any rake tasks for kaal-activerecord.
|
metadata
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kaal-activerecord
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.3.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nitesh Purohit
|
|
8
|
+
- Codevedas Inc.
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: kaal
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - '='
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.3.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - '='
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.3.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rails
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '7.1'
|
|
34
|
+
- - "<"
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '9.0'
|
|
37
|
+
type: :runtime
|
|
38
|
+
prerelease: false
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '7.1'
|
|
44
|
+
- - "<"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '9.0'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: rails-i18n
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '7.0'
|
|
54
|
+
type: :runtime
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '7.0'
|
|
61
|
+
description: " Kaal-ActiveRecord provides seamless integration of Kaal with ActiveRecord,
|
|
62
|
+
allowing you to use ActiveRecord models for scheduling and managing cron jobs in
|
|
63
|
+
a distributed environment.\n"
|
|
64
|
+
email:
|
|
65
|
+
- nitesh.purohit.it@gmail.com
|
|
66
|
+
- team@codevedas.com
|
|
67
|
+
executables: []
|
|
68
|
+
extensions: []
|
|
69
|
+
extra_rdoc_files: []
|
|
70
|
+
files:
|
|
71
|
+
- LICENSE
|
|
72
|
+
- README.md
|
|
73
|
+
- Rakefile
|
|
74
|
+
- lib/kaal/active_record.rb
|
|
75
|
+
- lib/kaal/active_record/base_record.rb
|
|
76
|
+
- lib/kaal/active_record/connection_support.rb
|
|
77
|
+
- lib/kaal/active_record/database_adapter.rb
|
|
78
|
+
- lib/kaal/active_record/definition_record.rb
|
|
79
|
+
- lib/kaal/active_record/definition_registry.rb
|
|
80
|
+
- lib/kaal/active_record/dispatch_record.rb
|
|
81
|
+
- lib/kaal/active_record/dispatch_registry.rb
|
|
82
|
+
- lib/kaal/active_record/lock_record.rb
|
|
83
|
+
- lib/kaal/active_record/migration_templates.rb
|
|
84
|
+
- lib/kaal/active_record/mysql_adapter.rb
|
|
85
|
+
- lib/kaal/active_record/postgres_adapter.rb
|
|
86
|
+
- lib/kaal/active_record/railtie.rb
|
|
87
|
+
- lib/kaal/active_record/sqlite_adapter.rb
|
|
88
|
+
- lib/kaal/active_record/version.rb
|
|
89
|
+
- lib/tasks/kaal/active_record_tasks.rake
|
|
90
|
+
homepage: https://github.com/Code-Vedas/kaal
|
|
91
|
+
licenses:
|
|
92
|
+
- MIT
|
|
93
|
+
metadata:
|
|
94
|
+
bug_tracker_uri: https://github.com/Code-Vedas/kaal/issues
|
|
95
|
+
changelog_uri: https://github.com/Code-Vedas/kaal/blob/main/CHANGELOG.md
|
|
96
|
+
documentation_uri: https://kaal.codevedas.com
|
|
97
|
+
homepage_uri: https://github.com/Code-Vedas/kaal
|
|
98
|
+
source_code_uri: https://github.com/Code-Vedas/kaal.git
|
|
99
|
+
funding_uri: https://github.com/sponsors/Code-Vedas
|
|
100
|
+
support_uri: https://kaal.codevedas.com/support
|
|
101
|
+
rubygems_uri: https://rubygems.org/gems/kaal-activerecord
|
|
102
|
+
rubygems_mfa_required: 'true'
|
|
103
|
+
rdoc_options: []
|
|
104
|
+
require_paths:
|
|
105
|
+
- lib
|
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '3.2'
|
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - ">="
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '0'
|
|
116
|
+
requirements: []
|
|
117
|
+
rubygems_version: 3.6.7
|
|
118
|
+
specification_version: 4
|
|
119
|
+
summary: ActiveRecord integration for Kaal, a distributed cron scheduler for Ruby.
|
|
120
|
+
test_files: []
|