cronjobber 1.0.1

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 (48) hide show
  1. data/CHANGELOG +1 -0
  2. data/Gemfile +11 -0
  3. data/Gemfile.lock +98 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +81 -0
  6. data/Rakefile +45 -0
  7. data/VERSION +1 -0
  8. data/app/models/cronjobber/task.rb +170 -0
  9. data/cronjobber.gemspec +88 -0
  10. data/init.rb +1 -0
  11. data/lib/cronjobber.rb +21 -0
  12. data/lib/cronjobber/tasks_helper.rb +20 -0
  13. data/spec/cronjobber_spec.rb +256 -0
  14. data/spec/dummy/Rakefile +7 -0
  15. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  16. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  17. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  18. data/spec/dummy/config.ru +4 -0
  19. data/spec/dummy/config/application.rb +45 -0
  20. data/spec/dummy/config/boot.rb +10 -0
  21. data/spec/dummy/config/database.yml +22 -0
  22. data/spec/dummy/config/environment.rb +5 -0
  23. data/spec/dummy/config/environments/development.rb +26 -0
  24. data/spec/dummy/config/environments/production.rb +49 -0
  25. data/spec/dummy/config/environments/test.rb +35 -0
  26. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  27. data/spec/dummy/config/initializers/inflections.rb +10 -0
  28. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  29. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  30. data/spec/dummy/config/initializers/session_store.rb +8 -0
  31. data/spec/dummy/config/locales/en.yml +5 -0
  32. data/spec/dummy/config/routes.rb +58 -0
  33. data/spec/dummy/db/migrate/20110112183948_create_cronjobs.rb +18 -0
  34. data/spec/dummy/db/schema.rb +26 -0
  35. data/spec/dummy/public/404.html +26 -0
  36. data/spec/dummy/public/422.html +26 -0
  37. data/spec/dummy/public/500.html +26 -0
  38. data/spec/dummy/public/favicon.ico +0 -0
  39. data/spec/dummy/public/javascripts/application.js +2 -0
  40. data/spec/dummy/public/javascripts/controls.js +965 -0
  41. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  42. data/spec/dummy/public/javascripts/effects.js +1123 -0
  43. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  44. data/spec/dummy/public/javascripts/rails.js +175 -0
  45. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  46. data/spec/dummy/script/rails +6 -0
  47. data/spec/spec_helper.rb +28 -0
  48. metadata +141 -0
@@ -0,0 +1 @@
1
+ 1.0.0 - Initial implementation
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rails", ">= 3.0.0"
5
+ gem "jeweler"
6
+ end
7
+
8
+ group :test do
9
+ gem "sqlite3"
10
+ gem "rspec-rails", ">= 2.0.0"
11
+ end
@@ -0,0 +1,98 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ abstract (1.0.0)
5
+ actionmailer (3.0.11)
6
+ actionpack (= 3.0.11)
7
+ mail (~> 2.2.19)
8
+ actionpack (3.0.11)
9
+ activemodel (= 3.0.11)
10
+ activesupport (= 3.0.11)
11
+ builder (~> 2.1.2)
12
+ erubis (~> 2.6.6)
13
+ i18n (~> 0.5.0)
14
+ rack (~> 1.2.1)
15
+ rack-mount (~> 0.6.14)
16
+ rack-test (~> 0.5.7)
17
+ tzinfo (~> 0.3.23)
18
+ activemodel (3.0.11)
19
+ activesupport (= 3.0.11)
20
+ builder (~> 2.1.2)
21
+ i18n (~> 0.5.0)
22
+ activerecord (3.0.11)
23
+ activemodel (= 3.0.11)
24
+ activesupport (= 3.0.11)
25
+ arel (~> 2.0.10)
26
+ tzinfo (~> 0.3.23)
27
+ activeresource (3.0.11)
28
+ activemodel (= 3.0.11)
29
+ activesupport (= 3.0.11)
30
+ activesupport (3.0.11)
31
+ arel (2.0.10)
32
+ builder (2.1.2)
33
+ diff-lcs (1.1.3)
34
+ erubis (2.6.6)
35
+ abstract (>= 1.0.0)
36
+ git (1.2.5)
37
+ i18n (0.5.0)
38
+ jeweler (1.6.4)
39
+ bundler (~> 1.0)
40
+ git (>= 1.2.5)
41
+ rake
42
+ json (1.6.4)
43
+ mail (2.2.19)
44
+ activesupport (>= 2.3.6)
45
+ i18n (>= 0.4.0)
46
+ mime-types (~> 1.16)
47
+ treetop (~> 1.4.8)
48
+ mime-types (1.17.2)
49
+ polyglot (0.3.3)
50
+ rack (1.2.5)
51
+ rack-mount (0.6.14)
52
+ rack (>= 1.0.0)
53
+ rack-test (0.5.7)
54
+ rack (>= 1.0)
55
+ rails (3.0.11)
56
+ actionmailer (= 3.0.11)
57
+ actionpack (= 3.0.11)
58
+ activerecord (= 3.0.11)
59
+ activeresource (= 3.0.11)
60
+ activesupport (= 3.0.11)
61
+ bundler (~> 1.0)
62
+ railties (= 3.0.11)
63
+ railties (3.0.11)
64
+ actionpack (= 3.0.11)
65
+ activesupport (= 3.0.11)
66
+ rake (>= 0.8.7)
67
+ rdoc (~> 3.4)
68
+ thor (~> 0.14.4)
69
+ rake (0.9.2.2)
70
+ rdoc (3.12)
71
+ json (~> 1.4)
72
+ rspec (2.0.1)
73
+ rspec-core (~> 2.0.1)
74
+ rspec-expectations (~> 2.0.1)
75
+ rspec-mocks (~> 2.0.1)
76
+ rspec-core (2.0.1)
77
+ rspec-expectations (2.0.1)
78
+ diff-lcs (>= 1.1.2)
79
+ rspec-mocks (2.0.1)
80
+ rspec-core (~> 2.0.1)
81
+ rspec-expectations (~> 2.0.1)
82
+ rspec-rails (2.0.1)
83
+ rspec (~> 2.0.0)
84
+ sqlite3 (1.3.5)
85
+ thor (0.14.6)
86
+ treetop (1.4.10)
87
+ polyglot
88
+ polyglot (>= 0.3.1)
89
+ tzinfo (0.3.31)
90
+
91
+ PLATFORMS
92
+ ruby
93
+
94
+ DEPENDENCIES
95
+ jeweler
96
+ rails (>= 3.0.0)
97
+ rspec-rails (>= 2.0.0)
98
+ sqlite3
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Alexander Gräfenstein
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.
@@ -0,0 +1,81 @@
1
+ = Cronjobber
2
+ Allows to define cronjobs programmatically.
3
+
4
+ == Installation
5
+ In your gemfile
6
+ gem 'cronjobber', :git => 'git://github.com/giniedp/cronjobber.git'
7
+
8
+ Run
9
+ bundle install
10
+
11
+ Cronjobber expects that your Database has a table named 'cronjobs'. Just use the following migration snippet to create the required fields. No, there is no generator for this... don't ask.
12
+ create_table(:cronjobs) do |t|
13
+ t.string :name
14
+ t.datetime :run_at
15
+ t.datetime :locked_at
16
+ t.string :locking_key
17
+ t.integer :duration
18
+ t.text :last_error
19
+ t.integer :total_runs
20
+ t.integer :total_failures
21
+ end
22
+
23
+
24
+ == Define a cronjob
25
+ In your models directory create one file for each task that you want to perform. I prefer to collect them in an extra directory.
26
+
27
+ # app/models/cronjobs/my_task.rb
28
+ class Cronjob::MyTask < Cronjobber::Task
29
+ # initialize the task
30
+ run_task :every => 5.minutes
31
+
32
+ def run
33
+ # task implementation goes here
34
+ end
35
+ end
36
+
37
+ === Options
38
+
39
+ [every] The time between executions. Default is <tt>0.minutes</tt>.
40
+
41
+ [at] An array with time of day strings when the task should be executed e.g. ["12:00", "20:00"]. Default is <tt>[]</tt>
42
+
43
+ [in_background] Tells the plugin to run this task in background or not. Default is <tt>false</tt>. Cronjobber uses delayed_job to delay the execution.
44
+
45
+ [method] The method to use to run the task. Default is <tt>:run</tt>
46
+
47
+ === Examples
48
+ Keep in mind that the exact execution time of a task always depends on how often the Cronjobber is triggered
49
+
50
+ run_task
51
+ Will use the default settings. The cronjob will be executed every time the Cronjobber is triggered
52
+
53
+ run_task :every => 5.minutes
54
+ Will execute the task only if 5 minutes are left since the last execution.
55
+
56
+ run_task :at => %w(12:00 15:00 20:00)
57
+ Will execute the task once between the given times.
58
+
59
+ run_task :every => 30.minutes, :at => "12:00"
60
+ Will execute the task every 30 minutes starting at 12:00.
61
+
62
+ run_task :every => 30.minutes, :at => %w(12:00 15:00 20:00)
63
+ Same as above. Only the first value of the <tt>at</tt> array is respected.
64
+
65
+ == Trigger task execution
66
+ Inside any Controller use the method <tt>execute_cronjob_tasks</tt> to trigger the Cronjobber. For example in your application controller:
67
+
68
+ class ApplicationController < ActionController::Base
69
+ def cronjobs
70
+ tasks, log = execute_cronjob_tasks %w(cronjobs/my_task)
71
+ render :text => log.join("\n")
72
+ end
73
+ end
74
+
75
+ The first returning value is an array of all tasks. The second is an array of status messages for each task.
76
+ In order to activate a cronjob task, you have to list it in the first argument of the <tt>execute_cronjob_tasks</tt> method,
77
+ or in the config.cronjobber.tasks configuration setting in your config/application.rb.
78
+
79
+ Now you have to make sure that the action is visited periodically. For example with a Unix crontab entry:
80
+
81
+ */1 * * * * curl http://your-app.com/cronjobs # visits http://your-app.com/cronjobs every minute
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'bundler/setup'
6
+ rescue LoadError
7
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
8
+ end
9
+
10
+ begin
11
+ require 'jeweler'
12
+ Jeweler::Tasks.new do |gem|
13
+ gem.name = "cronjobber"
14
+ gem.summary = %Q{Cronjob for Rails}
15
+ gem.description = %Q{Enables simple cronjobs for rails}
16
+ gem.email = "giniedp@online.de"
17
+ gem.homepage = "http://github.com/giniedp/cronjob"
18
+ gem.authors = ["Alexander Gräfenstein"]
19
+ # gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
20
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
25
+ end
26
+
27
+ require 'rake'
28
+ require 'rake/rdoctask'
29
+
30
+ require 'rspec/core'
31
+ require 'rspec/core/rake_task'
32
+
33
+ RSpec::Core::RakeTask.new(:spec)
34
+
35
+ task :default => :spec
36
+
37
+ Rake::RDocTask.new do |rdoc|
38
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
39
+
40
+ rdoc.rdoc_dir = 'rdoc'
41
+ rdoc.title = "cronjobber #{version}"
42
+ rdoc.options << '--line-numbers' << '--inline-source'
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.1
@@ -0,0 +1,170 @@
1
+ class Cronjobber::Task < ActiveRecord::Base
2
+ set_table_name "cronjobs"
3
+ attr_accessor :status
4
+
5
+ def initialize options={}
6
+ raise "#{self.class.name} is abstract" if self.class == Cronjobber::Task
7
+ super options
8
+ end
9
+
10
+ def self.cronjob_name
11
+ self.name.underscore
12
+ end
13
+
14
+ def self.cronjob
15
+ @cronjob ||= self.find_by_name(self.cronjob_name)
16
+ @cronjob ||= self.create!(:name => self.cronjob_name)
17
+ @cronjob
18
+ end
19
+
20
+ def self.cronjob_enqueue(key=nil)
21
+ self.delay.cronjob_perform_delayed(key)
22
+ end
23
+
24
+ def self.cronjob_perform
25
+ unless cronjob.lock!
26
+ cronjob.status = "locked"
27
+ return cronjob
28
+ end
29
+
30
+ unless cronjob.should_run?
31
+ cronjob.status = "skipped"
32
+ cronjob.unlock!
33
+ return cronjob
34
+ end
35
+
36
+ if self.cronjob_delayed
37
+ self.cronjob_enqueue(cronjob.locking_key)
38
+ cronjob.status = "enqueued"
39
+ cronjob.last_error = nil
40
+ cronjob.save!
41
+ else
42
+ cronjob.send(self.cronjob_method)
43
+ cronjob.status = "performed"
44
+ cronjob.unlock!
45
+ end
46
+
47
+ return cronjob
48
+ rescue Exception => exception
49
+ cronjob.status = "exception"
50
+ cronjob.unlock!(exception)
51
+ return cronjob
52
+ end
53
+
54
+ def self.cronjob_perform_delayed(key)
55
+ if cronjob.locked?(key)
56
+ cronjob.status = "locked"
57
+ else
58
+ cronjob.send(self.cronjob_method)
59
+ cronjob.status = "performed"
60
+ cronjob.unlock!
61
+ end
62
+ rescue Exception => exception
63
+ cronjob.status = "exception"
64
+ cronjob.unlock!(exception)
65
+ end
66
+
67
+ def locked?(key=nil)
68
+ if key && self.locking_key.to_s == key.to_s
69
+ false
70
+ else
71
+ !self.locked_at.nil?
72
+ end
73
+ end
74
+
75
+ def lock!(key=nil)
76
+ return false if self.locked?
77
+ return self.update_attributes!(:locked_at => DateTime.now, :locking_key => Time.now.to_i)
78
+ end
79
+
80
+ def unlock! exception=nil
81
+ return true unless self.locked?
82
+ if exception
83
+ self.last_error = [exception.message, exception.backtrace].flatten.join("\n")
84
+ self.total_failures = self.total_failures.to_i + 1
85
+ else
86
+ self.last_error = nil
87
+ end
88
+
89
+ self.update_attributes!({
90
+ :total_runs => self.total_runs.to_i + 1,
91
+ :locked_at => nil,
92
+ :locking_key => nil,
93
+ :run_at => Time.now,
94
+ :duration => (Time.now - self.locked_at.to_time) * 1000
95
+ })
96
+ end
97
+
98
+ def self.cronjob_timepoint t1, t2
99
+ result = []
100
+ t1.to_date.upto(t2.to_date) do |date|
101
+ self.cronjob_timesteps.each do |time|
102
+ result << Time.parse([date.year, date.month, date.day].join("-") + " " + time.to_s)
103
+ end
104
+ end
105
+ result.sort.uniq.select { |time| (time > t1 && time < t2) }.first
106
+ end
107
+
108
+ def should_run?(run_at=nil)
109
+ run_at ||= Time.now
110
+ t1, t2 = (self.run_at || run_at), run_at
111
+
112
+ if self.class.cronjob_frequency.to_i == 0
113
+ if self.class.cronjob_timesteps.empty?
114
+ true
115
+ else
116
+ self.class.cronjob_timepoint(t1, t2 + 1.day).present?
117
+ end
118
+ else
119
+ if self.class.cronjob_timesteps.empty?
120
+ (t2 - self.class.cronjob_frequency.to_i.seconds) >= t1
121
+ else
122
+ t3 = Time.parse("#{t1.year}-#{t1.month}-#{t1.day} #{self.class.cronjob_timesteps.first}")
123
+ until t3 > t2 do
124
+ t3 += self.class.cronjob_frequency
125
+ return true if t3 > t1 && t3 <= t2
126
+ end
127
+ false
128
+ end
129
+ end
130
+ end
131
+
132
+ def run
133
+
134
+ end
135
+
136
+ def format
137
+ "#{self.class.cronjob_name} # #{self.status} # #{self.duration}ms # #{self.last_error.to_s[0..64]}"
138
+ end
139
+
140
+ protected
141
+ def self.run_task *arg
142
+ options = arg.extract_options!
143
+ options = { :every => 0.minutes, :in_background => false, :method => :run }.merge(options)
144
+ options[:method] = arg.first if arg.first.is_a?(Symbol)
145
+
146
+ timesteps = Array(options[:at]).compact
147
+ if !timesteps.empty? && timesteps.any? { |time| !time.to_s.match(/\d\d:\d\d/) }
148
+ raise "Invalid cronjob definition. Only 'hh:mm' is supported"
149
+ end
150
+
151
+ class_eval %{
152
+ def self.cronjob_frequency
153
+ #{options[:every].to_i}.seconds
154
+ end
155
+
156
+ def self.cronjob_timesteps
157
+ '#{timesteps.join(',')}'.split(',')
158
+ end
159
+
160
+ def self.cronjob_delayed
161
+ '#{options[:in_background].to_s}' == 'true'
162
+ end
163
+
164
+ def self.cronjob_method
165
+ method = '#{options[:method].to_s}'
166
+ method.blank? ? :run : method
167
+ end
168
+ }
169
+ end
170
+ end
@@ -0,0 +1,88 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "cronjobber"
8
+ s.version = "1.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Alexander Gr\303\244fenstein"]
12
+ s.date = "2012-01-11"
13
+ s.description = "Enables simple cronjobs for rails"
14
+ s.email = "giniedp@online.de"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ "CHANGELOG",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "app/models/cronjobber/task.rb",
28
+ "cronjobber.gemspec",
29
+ "init.rb",
30
+ "lib/cronjobber.rb",
31
+ "lib/cronjobber/tasks_helper.rb",
32
+ "spec/cronjobber_spec.rb",
33
+ "spec/dummy/Rakefile",
34
+ "spec/dummy/app/controllers/application_controller.rb",
35
+ "spec/dummy/app/helpers/application_helper.rb",
36
+ "spec/dummy/app/views/layouts/application.html.erb",
37
+ "spec/dummy/config.ru",
38
+ "spec/dummy/config/application.rb",
39
+ "spec/dummy/config/boot.rb",
40
+ "spec/dummy/config/database.yml",
41
+ "spec/dummy/config/environment.rb",
42
+ "spec/dummy/config/environments/development.rb",
43
+ "spec/dummy/config/environments/production.rb",
44
+ "spec/dummy/config/environments/test.rb",
45
+ "spec/dummy/config/initializers/backtrace_silencers.rb",
46
+ "spec/dummy/config/initializers/inflections.rb",
47
+ "spec/dummy/config/initializers/mime_types.rb",
48
+ "spec/dummy/config/initializers/secret_token.rb",
49
+ "spec/dummy/config/initializers/session_store.rb",
50
+ "spec/dummy/config/locales/en.yml",
51
+ "spec/dummy/config/routes.rb",
52
+ "spec/dummy/db/migrate/20110112183948_create_cronjobs.rb",
53
+ "spec/dummy/db/schema.rb",
54
+ "spec/dummy/public/404.html",
55
+ "spec/dummy/public/422.html",
56
+ "spec/dummy/public/500.html",
57
+ "spec/dummy/public/favicon.ico",
58
+ "spec/dummy/public/javascripts/application.js",
59
+ "spec/dummy/public/javascripts/controls.js",
60
+ "spec/dummy/public/javascripts/dragdrop.js",
61
+ "spec/dummy/public/javascripts/effects.js",
62
+ "spec/dummy/public/javascripts/prototype.js",
63
+ "spec/dummy/public/javascripts/rails.js",
64
+ "spec/dummy/public/stylesheets/.gitkeep",
65
+ "spec/dummy/script/rails",
66
+ "spec/spec_helper.rb"
67
+ ]
68
+ s.homepage = "http://github.com/giniedp/cronjob"
69
+ s.require_paths = ["lib"]
70
+ s.rubygems_version = "1.8.15"
71
+ s.summary = "Cronjob for Rails"
72
+
73
+ if s.respond_to? :specification_version then
74
+ s.specification_version = 3
75
+
76
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
77
+ s.add_development_dependency(%q<rails>, [">= 3.0.0"])
78
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
79
+ else
80
+ s.add_dependency(%q<rails>, [">= 3.0.0"])
81
+ s.add_dependency(%q<jeweler>, [">= 0"])
82
+ end
83
+ else
84
+ s.add_dependency(%q<rails>, [">= 3.0.0"])
85
+ s.add_dependency(%q<jeweler>, [">= 0"])
86
+ end
87
+ end
88
+