dbcron 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f2da3d3795e6cb2e592892f57d617d13d0ea21eb
4
+ data.tar.gz: bbbd001bd17ca9fa13cfcbd95f32513459790fe1
5
+ SHA512:
6
+ metadata.gz: 51c56179fc1bbfae680131b964b8de23ada953cfb4e33c99e7eb687785f624759682e002303dde294436e7160de413e85ae44f04c1e70b50fa256d9249cf6c9b
7
+ data.tar.gz: 0176be8d9a99cda870180f543b283a14e4e33696536526049d8a2ef7b260491b68caffe769d8839d5719e88e03174ccec188d36b6400e126a18ed39c717a543a
@@ -0,0 +1,18 @@
1
+ .DS_Store
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
@@ -0,0 +1,216 @@
1
+ AllCops:
2
+ Exclude:
3
+ - .git/**/*
4
+ - tmp/**/*
5
+ - suo.gemspec
6
+
7
+ Lint/DuplicateMethods:
8
+ Enabled: true
9
+
10
+ Lint/DeprecatedClassMethods:
11
+ Enabled: true
12
+
13
+ Style/TrailingWhitespace:
14
+ Enabled: true
15
+
16
+ Style/Tab:
17
+ Enabled: true
18
+
19
+ Style/TrailingBlankLines:
20
+ Enabled: true
21
+
22
+ Style/NilComparison:
23
+ Enabled: true
24
+
25
+ Style/NonNilCheck:
26
+ Enabled: true
27
+
28
+ Style/Not:
29
+ Enabled: true
30
+
31
+ Style/RedundantReturn:
32
+ Enabled: true
33
+
34
+ Style/ClassCheck:
35
+ Enabled: true
36
+
37
+ Style/EmptyLines:
38
+ Enabled: true
39
+
40
+ Style/EmptyLiteral:
41
+ Enabled: true
42
+
43
+ Style/Alias:
44
+ Enabled: true
45
+
46
+ Style/MethodCallParentheses:
47
+ Enabled: true
48
+
49
+ Style/MethodDefParentheses:
50
+ Enabled: true
51
+
52
+ Style/SpaceBeforeBlockBraces:
53
+ Enabled: true
54
+
55
+ Style/SpaceInsideBlockBraces:
56
+ Enabled: true
57
+
58
+ Style/SpaceInsideParens:
59
+ Enabled: true
60
+
61
+ Style/DeprecatedHashMethods:
62
+ Enabled: true
63
+
64
+ Style/HashSyntax:
65
+ Enabled: true
66
+
67
+ Style/SpaceInsideHashLiteralBraces:
68
+ Enabled: true
69
+ EnforcedStyle: no_space
70
+
71
+ Style/SpaceInsideBrackets:
72
+ Enabled: true
73
+
74
+ Style/AndOr:
75
+ Enabled: false
76
+
77
+ Style/TrailingComma:
78
+ Enabled: true
79
+
80
+ Style/SpaceBeforeComma:
81
+ Enabled: true
82
+
83
+ Style/SpaceBeforeComment:
84
+ Enabled: true
85
+
86
+ Style/SpaceBeforeSemicolon:
87
+ Enabled: true
88
+
89
+ Style/SpaceAroundBlockParameters:
90
+ Enabled: true
91
+
92
+ Style/SpaceAroundOperators:
93
+ Enabled: true
94
+
95
+ Style/SpaceAfterColon:
96
+ Enabled: true
97
+
98
+ Style/SpaceAfterComma:
99
+ Enabled: true
100
+
101
+ Style/SpaceAfterControlKeyword:
102
+ Enabled: true
103
+
104
+ Style/SpaceAfterNot:
105
+ Enabled: true
106
+
107
+ Style/SpaceAfterSemicolon:
108
+ Enabled: true
109
+
110
+ Lint/UselessComparison:
111
+ Enabled: true
112
+
113
+ Lint/InvalidCharacterLiteral:
114
+ Enabled: true
115
+
116
+ Lint/LiteralInInterpolation:
117
+ Enabled: true
118
+
119
+ Lint/LiteralInCondition:
120
+ Enabled: true
121
+
122
+ Lint/UnusedBlockArgument:
123
+ Enabled: true
124
+
125
+ Style/VariableInterpolation:
126
+ Enabled: true
127
+
128
+ Style/RedundantSelf:
129
+ Enabled: true
130
+
131
+ Style/ParenthesesAroundCondition:
132
+ Enabled: true
133
+
134
+ Style/WhileUntilDo:
135
+ Enabled: true
136
+
137
+ Style/EmptyLineBetweenDefs:
138
+ Enabled: true
139
+
140
+ Style/EmptyLinesAroundAccessModifier:
141
+ Enabled: true
142
+
143
+ Style/EmptyLinesAroundMethodBody:
144
+ Enabled: true
145
+
146
+ Style/ColonMethodCall:
147
+ Enabled: true
148
+
149
+ Lint/SpaceBeforeFirstArg:
150
+ Enabled: true
151
+
152
+ Lint/UnreachableCode:
153
+ Enabled: true
154
+
155
+ Style/UnlessElse:
156
+ Enabled: true
157
+
158
+ Style/ClassVars:
159
+ Enabled: true
160
+
161
+ Style/StringLiterals:
162
+ Enabled: true
163
+ EnforcedStyle: double_quotes
164
+
165
+ Metrics/CyclomaticComplexity:
166
+ Max: 8
167
+
168
+ Metrics/LineLength:
169
+ Max: 136
170
+
171
+ Metrics/MethodLength:
172
+ Max: 32
173
+
174
+ Metrics/PerceivedComplexity:
175
+ Max: 8
176
+
177
+ # Disabled
178
+
179
+ Style/EvenOdd:
180
+ Enabled: false
181
+
182
+ Style/AsciiComments:
183
+ Enabled: false
184
+
185
+ Style/NumericLiterals:
186
+ Enabled: false
187
+
188
+ Style/UnneededPercentQ:
189
+ Enabled: false
190
+
191
+ Style/SpecialGlobalVars:
192
+ Enabled: false
193
+
194
+ Style/TrivialAccessors:
195
+ Enabled: false
196
+
197
+ Style/PerlBackrefs:
198
+ Enabled: false
199
+
200
+ Metrics/AbcSize:
201
+ Enabled: false
202
+
203
+ Metrics/BlockNesting:
204
+ Enabled: false
205
+
206
+ Metrics/ClassLength:
207
+ Enabled: false
208
+
209
+ Metrics/MethodLength:
210
+ Enabled: false
211
+
212
+ Metrics/ParameterLists:
213
+ Enabled: false
214
+
215
+ Metrics/PerceivedComplexity:
216
+ Enabled: false
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Nick Elser
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,37 @@
1
+ # dbcron
2
+
3
+ Distributed cron powered by your database.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'dbcron'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install dbcron
20
+
21
+ ## Usage
22
+
23
+ TODO: Still a WIP.
24
+
25
+ ## Development
26
+
27
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
28
+
29
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it ( https://github.com/nickelser/dbcron/fork )
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create a new Pull Request
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ #Bundler::GemHelper.install_tasks(name: "dbcron")
7
+
8
+ task default: :test
9
+ Rake::TestTask.new do |t|
10
+ t.libs << "test"
11
+ t.pattern = "test/**/*_test.rb"
12
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dbcron"
5
+
6
+ require "irb"
7
+ IRB.start
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ STDERR.sync = STDOUT.sync = true
4
+
5
+ require "bundler/setup"
6
+ require "dbcron"
7
+
8
+ usage = "dbcron <dbcron.rb>"
9
+ file = ARGV.shift || abort(usage)
10
+
11
+ file = "./#{file}" unless file.match(%r{^[\/.]})
12
+
13
+ require file
14
+
15
+ DBcron.start
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "dbcron/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dbcron"
8
+ spec.version = DBcron::VERSION
9
+ spec.authors = ["Nick Elser"]
10
+ spec.email = ["nick.elser@gmail.com"]
11
+
12
+ spec.summary = "Distributed cron powered by your database"
13
+ spec.description = "Distributed cron powered by your database"
14
+ spec.homepage = "https://github.com/nickelser/dbcron"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.bindir = "bin"
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.required_ruby_version = "~> 2.0"
24
+
25
+ spec.add_dependency "parse-cron"
26
+ spec.add_dependency "activerecord", "~> 4.2"
27
+ spec.add_dependency "activesupport", "~> 4.2"
28
+ spec.add_dependency "celluloid", "~> 0.17"
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.5"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rubocop", "~> 0.30.0"
33
+ spec.add_development_dependency "minitest", "~> 5.5.0"
34
+ #spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4.7"
35
+ end
@@ -0,0 +1,232 @@
1
+ require "dbcron/version"
2
+ require "parse-cron"
3
+ require "active_record"
4
+ require "active_support/time"
5
+ require "celluloid/current"
6
+
7
+ # nodoc
8
+ module DBcron
9
+ GRACE_TIME = 5.minutes
10
+ SLEEPY_TIME = 3.seconds
11
+ HOST_ALIVE_TIME = 15.seconds
12
+ UTC = ActiveSupport::TimeZone["UTC"]
13
+
14
+ # nodoc
15
+ module Actor
16
+ def self.included(klass)
17
+ klass.__send__(:include, Celluloid)
18
+ klass.__send__(:include, Celluloid::Internals::Logger)
19
+ end
20
+ end
21
+
22
+ class << self
23
+ def schedule(**opts)
24
+ Celluloid.logger = opts[:logger] if opts[:logger]
25
+
26
+ Class.new(Celluloid::Supervision::Container) do
27
+ supervise type: Clock, as: :clock
28
+ pool TaskRunner, as: :task_runners
29
+ end.run!
30
+
31
+ Celluloid::Actor[:clock].configure(opts)
32
+
33
+ yield(Celluloid::Actor[:clock])
34
+ end
35
+
36
+ def start
37
+ fail "You must run `dbcron.schedule` first" unless Celluloid::Actor[:clock]
38
+ Celluloid::Actor[:clock].start
39
+ end
40
+ end
41
+
42
+ # nodoc
43
+ class Host < ActiveRecord::Base
44
+ self.table_name = "dbcron_hosts"
45
+ end
46
+
47
+ # nodoc
48
+ class Entry < ActiveRecord::Base
49
+ self.table_name = "dbcron_entries"
50
+ end
51
+
52
+ # nodoc
53
+ class CrontabEntry
54
+ attr_accessor :name, :last, :task
55
+
56
+ def initialize(name, cron:, task:)
57
+ @name = name
58
+ @cron = CronParser.new(cron, Celluloid::Actor[:clock].tz)
59
+ @task = task
60
+ @last = nil
61
+ end
62
+
63
+ def ready?(time)
64
+ return false if @cron.next(time) > time + GRACE_TIME # is it time for the task?
65
+ return false if @last && (time - @last) < interval # running too often?
66
+ return false if !@last && time.sec > 2 * SLEEPY_TIME # for new tasks, start it as close as possible to the top of the minute
67
+
68
+ true
69
+ end
70
+
71
+ def run!(time)
72
+ @last = time
73
+
74
+ Celluloid::Actor[:task_runners].async.dispatch(@task)
75
+ end
76
+
77
+ private
78
+
79
+ def interval
80
+ @interval ||= ((@cron.next - @cron.last) / 2).seconds
81
+ end
82
+ end
83
+
84
+ # nodoc
85
+ class TaskRunner
86
+ include Actor
87
+
88
+ def dispatch(task)
89
+ task.call
90
+ rescue => boom
91
+ error "failed: #{boom}"
92
+ end
93
+ end
94
+
95
+ # nodoc
96
+ class Clock
97
+ include Actor
98
+
99
+ attr_accessor :stop
100
+ attr_reader :tz
101
+
102
+ finalizer :finalize
103
+
104
+ def initialize
105
+ @tasks = {}
106
+ @last_refresh = nil
107
+ @tz = UTC
108
+ @uuid = SecureRandom.uuid
109
+ end
110
+
111
+ def configure(opts = {})
112
+ @tz = opts[:tz] if opts[:tz]
113
+ end
114
+
115
+ def add_entry(name, cron, task)
116
+ @tasks[name] = CrontabEntry.new(name, cron: cron, task: task)
117
+ end
118
+
119
+ def ready_tasks(time)
120
+ task_array.select { |e| e.ready?(time) }
121
+ end
122
+
123
+ def task_array
124
+ @task_array ||= @tasks.values
125
+ end
126
+
127
+ def start
128
+ started_at = now
129
+
130
+ host = Host.where(uuid: @uuid).first_or_create!(
131
+ hostname: Socket.gethostname,
132
+ pid: Process.pid,
133
+ started: started_at,
134
+ last_seen: started_at
135
+ )
136
+
137
+ create(task_array)
138
+ refresh(task_array)
139
+
140
+ info "dbcron starting with #{worker_pool_size} workers"
141
+
142
+ loop do
143
+ start = now
144
+
145
+ if start >= host.last_seen + HOST_ALIVE_TIME
146
+ host.update_column(:last_seen, start)
147
+ end
148
+
149
+ tick(start)
150
+
151
+ finish = now
152
+
153
+ sleep_until_next_tick(finish)
154
+
155
+ break if @stop
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ def finalize
162
+ @stop = true
163
+ end
164
+
165
+ def create(tasks)
166
+ tasks.each do |task|
167
+ begin
168
+ Entry.transaction do
169
+ Entry.where(task: task.name).first_or_create!
170
+ end
171
+ rescue ActiveRecord::RecordNotUnique => _ # rubocop:disable Lint/HandleExceptions
172
+ # this is fine - the record already exists
173
+ end
174
+ end
175
+ end
176
+
177
+ def refresh(tasks)
178
+ Entry.where(task: tasks.map(&:name)).each do |t|
179
+ @tasks[t.task].last = t.last
180
+ end
181
+ end
182
+
183
+ def tick(time)
184
+ ready_tasks(time).shuffle.each_slice(worker_pool_size) do |tasks|
185
+ task_names = tasks.map(&:name)
186
+
187
+ Entry.transaction do
188
+ Entry.where(task: task_names).lock(true).pluck(:id)
189
+
190
+ info "running #{task_names.join(', ')}"
191
+
192
+ refresh(tasks)
193
+
194
+ ran_tasks = run_tasks(tasks, time)
195
+
196
+ if ran_tasks.size != task_names.size
197
+ info "did not run #{(task_names - ran_tasks.map(&:name)).join(', ')}"
198
+ end
199
+
200
+ unless ran_tasks.empty?
201
+ Entry.where(task: ran_tasks.map(&:name)).update_all("last = CURRENT_TIMESTAMP")
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ def run_tasks(tasks, time)
208
+ tasks.select do |task|
209
+ if task.ready?(time)
210
+ task.run!(time)
211
+ true
212
+ else
213
+ false
214
+ end
215
+ end
216
+ end
217
+
218
+ def sleep_until_next_tick(time)
219
+ sleep_time = SLEEPY_TIME - time.subsec - (time.sec % SLEEPY_TIME)
220
+ sleep(sleep_time)
221
+ end
222
+
223
+ def now
224
+ timestamp = ActiveRecord::Base.connection.execute("SELECT CURRENT_TIMESTAMP")
225
+ UTC.parse(timestamp.first.values.first).in_time_zone(@tz)
226
+ end
227
+
228
+ def worker_pool_size
229
+ @worker_pool_size ||= Celluloid::Actor[:task_runners].size
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,3 @@
1
+ module DBcron
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,29 @@
1
+ # taken from https://github.com/collectiveidea/audited/blob/master/lib/generators/audited/install_generator.rb
2
+ require "rails/generators"
3
+ require "rails/generators/migration"
4
+ require "active_record"
5
+ require "rails/generators/active_record"
6
+
7
+ module Dbcron # note the spelling - formats the generator as dbcron instead of d_bcron
8
+ module Generators
9
+ class InstallGenerator < Rails::Generators::Base
10
+ include Rails::Generators::Migration
11
+
12
+ source_root File.expand_path("../templates", __FILE__)
13
+
14
+ # Implement the required interface for Rails::Generators::Migration.
15
+ def self.next_migration_number(dirname) #:nodoc:
16
+ next_migration_number = current_migration_number(dirname) + 1
17
+ if ::ActiveRecord::Base.timestamped_migrations
18
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
19
+ else
20
+ "%.3d" % next_migration_number
21
+ end
22
+ end
23
+
24
+ def copy_migration
25
+ migration_template "install.rb", "db/migrate/install_dbcron.rb"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :dbcron_hosts, force: true do |t|
4
+ t.column :uuid, :string
5
+ t.column :hostname, :string
6
+ t.column :pid, :integer
7
+ t.column :started, :datetime
8
+ t.column :last_seen, :datetime
9
+ end
10
+
11
+ create_table :dbcron_entries, force: true do |t|
12
+ t.column :task, :string
13
+ t.column :last, :datetime
14
+ end
15
+
16
+ add_index :dbcron_hosts, :uuid, unique: true, name: "dbcron_hosts_uuid_index"
17
+ add_index :dbcron_entries, :task, unique: true, name: "dbcron_entry_task_index"
18
+ end
19
+
20
+ def self.down
21
+ drop_table :dbcron_hosts
22
+ drop_table :dbcron_entries
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ require "test_helper"
2
+
3
+ class TestDBcron < Minitest::Test
4
+ def test_that_it_has_a_version_number
5
+ refute_nil ::DBcron::VERSION
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+
3
+ if ENV["CODECLIMATE_REPO_TOKEN"]
4
+ require "codeclimate-test-reporter"
5
+ CodeClimate::TestReporter.start
6
+ end
7
+
8
+ require "dbcron"
9
+ require "minitest/autorun"
10
+ require "minitest/benchmark"
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dbcron
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nick Elser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parse-cron
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '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'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: celluloid
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.17'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.17'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.30.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.30.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 5.5.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 5.5.0
125
+ description: Distributed cron powered by your database
126
+ email:
127
+ - nick.elser@gmail.com
128
+ executables:
129
+ - console
130
+ - dbcron
131
+ - setup
132
+ extensions: []
133
+ extra_rdoc_files: []
134
+ files:
135
+ - ".gitignore"
136
+ - ".rubocop.yml"
137
+ - ".travis.yml"
138
+ - Gemfile
139
+ - LICENSE.txt
140
+ - README.md
141
+ - Rakefile
142
+ - bin/console
143
+ - bin/dbcron
144
+ - bin/setup
145
+ - dbcron.gemspec
146
+ - lib/dbcron.rb
147
+ - lib/dbcron/version.rb
148
+ - lib/generators/dbcron/install_generator.rb
149
+ - lib/generators/dbcron/templates/install.rb
150
+ - test/dbcron_test.rb
151
+ - test/test_helper.rb
152
+ homepage: https://github.com/nickelser/dbcron
153
+ licenses:
154
+ - MIT
155
+ metadata: {}
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - "~>"
163
+ - !ruby/object:Gem::Version
164
+ version: '2.0'
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubyforge_project:
172
+ rubygems_version: 2.4.5
173
+ signing_key:
174
+ specification_version: 4
175
+ summary: Distributed cron powered by your database
176
+ test_files:
177
+ - test/dbcron_test.rb
178
+ - test/test_helper.rb