cron-table 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ae408479bf5161e8a60f21fd10758b7bc9790f2e47eb13517127f78ec09d9174
4
+ data.tar.gz: 70787c17ee8e536bb73c8226162765e5c7bc6a3b888cc6c3686df83f676004cc
5
+ SHA512:
6
+ metadata.gz: 82b82aa4118fefa72c3896bb3850e281b8987205ecee857a5266c6674c81613ada788f3090b696e15a15761641cc2f471e7c96bafe4d9547fe5d722d8d58e497
7
+ data.tar.gz: fe33d54efd7eb2036125202dada55394624a81c17d461d512cda2693cedd4c9f943228dbae0b783ac959b474fec9bfc0298d449df9d7e2d66d99581ac0c86e86
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 TODO: Write your name
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # CronTable
2
+ Basic cron-like system to schedule jobs
3
+
4
+ ## Setup
5
+ 1. Add `cron-table` gem
6
+ 2. Run `rails app:cron_table:install:migrations`
7
+ 3. Run `rails db:migrate` to apply migrations
8
+
9
+ ## Usage
10
+ 1. Add `include CronTable::Schedule` to the job
11
+ 2. Define cron schedule using `crontable(every: <interval>)`
12
+ 3. Use block if cron requires params, eg `crontable(every: 1.day) { perform_later(Time.now) }`
13
+
14
+ ## License
15
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1,6 @@
1
+ module Cron
2
+ module Table
3
+ class ApplicationController < ActionController::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module CronTable
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module CronTable
2
+ class Item < ApplicationRecord
3
+ self.table_name = "cron_table"
4
+ end
5
+ end
@@ -0,0 +1,67 @@
1
+ class CronTable::Server
2
+ SLEEP = 1.second..1.day
3
+ SLEEP_IDLE = 1.hour
4
+
5
+ def initialize
6
+ end
7
+
8
+ def sync!
9
+ crons = CronTable.all.clone
10
+ deleted = []
11
+ CronTable::Item.transaction do
12
+ CronTable::Item.lock.all.each do |cron|
13
+ if definition = crons.delete(cron.key)
14
+ cron.update(next_run_at: definition.next_run_at(Time.now)) if cron.next_run_at.nil?
15
+ elsif cron.next_run_at.present?
16
+ deleted << cron.key
17
+ end
18
+ end
19
+ CronTable::Item.where(key: deleted).update(next_run_at: nil)
20
+ crons.each do |key, definition|
21
+ CronTable::Item.create(key: key, next_run_at: definition.next_run_at(Time.now))
22
+ end
23
+ end
24
+ end
25
+
26
+ def run!
27
+ @exit = false
28
+ next_run_at = CronTable::Item.minimum(:next_run_at) || SLEEP_IDLE.from_now
29
+ interruptible_sleep(next_run_at.to_f - Time.now.to_f)
30
+
31
+ CronTable::Item.lock.where(next_run_at: ..Time.now).each do |cron|
32
+ process(cron)
33
+ end
34
+ end
35
+
36
+ def exit!
37
+ @exit = true
38
+ interrupt!
39
+ end
40
+
41
+ def exit?
42
+ @exit.present?
43
+ end
44
+
45
+ private
46
+
47
+ def process(cron)
48
+ Rails.application.reloader.wrap do
49
+ definition = CronTable.all.fetch(cron.key)
50
+ definition.call
51
+
52
+ cron.update(last_run_at: Time.now, next_run_at: definition.next_run_at(Time.now))
53
+ end
54
+ rescue => e
55
+ cron.update(next_run_at: nil)
56
+ Rails.error.report(e, handled: true, severity: :error, context: { cron: cron.id })
57
+ end
58
+
59
+ def interruptible_sleep(seconds)
60
+ @sleep, @interrupt = IO.pipe
61
+ IO.select([@sleep], nil, nil, seconds.to_i.clamp(SLEEP))
62
+ end
63
+
64
+ def interrupt!
65
+ @interrupt&.close
66
+ end
67
+ end
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Cron table</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+ </head>
8
+ <body>
9
+
10
+ <%= yield %>
11
+
12
+ </body>
13
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ CronTable::Engine.routes.draw do
2
+ end
@@ -0,0 +1,11 @@
1
+ class CreateCronTable < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :cron_table do |t|
4
+ t.string :key, null: false, unique: true
5
+
6
+ t.datetime :next_run_at, index: true
7
+ t.datetime :last_run_at
8
+ t.datetime :created_at, null: false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module CronTable
2
+ class Definition < Struct.new(:key, :every, :block, keyword_init: true)
3
+ delegate :call, to: :block
4
+
5
+ def next_run_at(now)
6
+ now + every
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module CronTable
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace CronTable
4
+
5
+ server do
6
+ Thread.new do
7
+ cron = CronTable::Server.new
8
+ cron.sync!
9
+ cron.run until true
10
+ end if CronTable.attach_to_server
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ require "active_support/all"
2
+
3
+ module CronTable
4
+ module Schedule
5
+ class DuplicateKeyError < ArgumentError
6
+ def initialize(key)
7
+ @key = key
8
+ end
9
+
10
+ def message = "There can be only one crontable entry with key `#{@key}`. Use `crontable(key: '<other unique name>')` to select a different key"
11
+ end
12
+
13
+ class MissingBlockError < ArgumentError
14
+ def message = "Provide a block to `crontable(..) { }` with code to execute"
15
+ end
16
+
17
+ extend ActiveSupport::Concern
18
+
19
+ included do |_base|
20
+ singleton_class.prepend(PrependedClassMethods)
21
+ end
22
+
23
+ module PrependedClassMethods
24
+ def crontable(every:, key: self.name, &block)
25
+ raise DuplicateKeyError.new(key) if CronTable.all.key? key
26
+
27
+ block ||= -> { self.perform_later } if self.respond_to?(:perform_later)
28
+ raise MissingBlockError if block.nil?
29
+
30
+ CronTable.all[key] = Definition.new(key:, every:, block:)
31
+
32
+ self
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module CronTable
2
+ VERSION = "0.2"
3
+ end
data/lib/cron-table.rb ADDED
@@ -0,0 +1,23 @@
1
+ require "cron-table/version"
2
+ require "cron-table/engine"
3
+ require "cron-table/definition"
4
+ require "cron-table/schedule"
5
+
6
+ module CronTable
7
+ mattr_accessor :attach_to_server, default: Rails.env.production?
8
+
9
+ mattr_accessor :preload_dirs, default: ["app/jobs"]
10
+
11
+ @@all = nil
12
+ def self.all
13
+ if @@all.nil?
14
+ @@all = {}
15
+
16
+ CronTable.preload_dirs.each do |dir|
17
+ Rails.autoloaders.main.eager_load_dir(Rails.root.join(dir))
18
+ end if CronTable.preload_dirs
19
+ end
20
+
21
+ @@all
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ namespace :cron_table do
2
+ desc "Run cron_table scheduler"
3
+ task run: :environment do
4
+ cron = CronTable::Server.new
5
+ cron.sync!
6
+
7
+ Signal.trap("INT") { cron.exit! }
8
+ Signal.trap("TERM") { cron.exit! }
9
+
10
+ cron.run! until cron.exit?
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cron-table
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.2'
5
+ platform: ruby
6
+ authors:
7
+ - twratajczak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-07-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mocha
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rufo
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.16'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.16'
55
+ description: Simple system to schedule recurring jobs for Rails
56
+ email:
57
+ - twratajczak@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - app/controllers/cron/table/application_controller.rb
66
+ - app/models/cron_table/application_record.rb
67
+ - app/models/cron_table/item.rb
68
+ - app/services/cron_table/server.rb
69
+ - app/views/layouts/cron-table/application.html.erb
70
+ - config/routes.rb
71
+ - db/migrate/20230723000000_create_cron_table.rb
72
+ - lib/cron-table.rb
73
+ - lib/cron-table/definition.rb
74
+ - lib/cron-table/engine.rb
75
+ - lib/cron-table/schedule.rb
76
+ - lib/cron-table/version.rb
77
+ - lib/tasks/cron_table_tasks.rake
78
+ homepage: https://github.com/cron-table/cron-table
79
+ licenses:
80
+ - MIT
81
+ metadata:
82
+ homepage_uri: https://github.com/cron-table/cron-table
83
+ source_code_uri: https://github.com/cron-table/cron-table
84
+ changelog_uri: https://github.com/cron-table/cron-table/blob/main/CHANGELOG.md
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubygems_version: 3.3.26
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Basic cron-like scheduling system for Rails
104
+ test_files: []