navvy-sequelhooks 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +3 -0
- data/README.textile +15 -0
- data/generators/navvy/navvy_generator.rb +20 -0
- data/generators/navvy/templates/active_record_migration.rb +22 -0
- data/generators/navvy/templates/sequel_migration.rb +23 -0
- data/lib/generators/navvy_generator.rb +32 -0
- data/lib/navvy.rb +34 -0
- data/lib/navvy/configuration.rb +13 -0
- data/lib/navvy/job.rb +173 -0
- data/lib/navvy/job/active_record.rb +136 -0
- data/lib/navvy/job/data_mapper.rb +154 -0
- data/lib/navvy/job/mongo_mapper.rb +150 -0
- data/lib/navvy/job/mongoid.rb +139 -0
- data/lib/navvy/job/sequel.rb +138 -0
- data/lib/navvy/job/sequelhooks.rb +144 -0
- data/lib/navvy/logger.rb +38 -0
- data/lib/navvy/tasks.rb +16 -0
- data/lib/navvy/worker.rb +50 -0
- data/spec/configuration_spec.rb +68 -0
- data/spec/job_spec.rb +505 -0
- data/spec/logger_spec.rb +23 -0
- data/spec/setup/active_record.rb +20 -0
- data/spec/setup/data_mapper.rb +4 -0
- data/spec/setup/mongo_mapper.rb +2 -0
- data/spec/setup/mongoid.rb +9 -0
- data/spec/setup/sequel.rb +21 -0
- data/spec/setup/sequelhooks.rb +21 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/worker_spec.rb +27 -0
- data/spec/zhooks.rb +47 -0
- metadata +160 -0
data/LICENSE
ADDED
data/README.textile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
h1. Navvy "!http://stillmaintained.com/jeffkreeftmeijer/navvy.png!":http://stillmaintained.com/jeffkreeftmeijer/navvy
|
2
|
+
|
3
|
+
Navvy is a simple Ruby background job processor inspired by "delayed_job":http://github.com/tobi/delayed_job, but aiming for database agnosticism. Currently Navvy supports ActiveRecord, MongoMapper, Sequel, DataMapper and Mongoid but it's extremely easy to write an adapter for your favorite ORM.
|
4
|
+
|
5
|
+
Navvy doesn't depend on Rails, it's a pure Ruby library. There are generators for Rails 2 and Rails 3, though.
|
6
|
+
|
7
|
+
??“Navvy is a shorter form of navigator (UK) or navigational engineer (USA) and is particularly applied to describe the manual labourers working on major civil engineering projects. The term was coined in the late 18th century in Britain when numerous canals were being built, which were also sometimes known as "navigations". Canal navvies typically worked with shovels, pickaxes and barrows.”?? - "Wikipedia":http://en.wikipedia.org/wiki/Navvy
|
8
|
+
|
9
|
+
h2. Using Navvy
|
10
|
+
|
11
|
+
Check out the "Installation Guide":http://wiki.github.com/jeffkreeftmeijer/navvy/installation and the "Getting Started Guide":http://wiki.github.com/jeffkreeftmeijer/navvy/getting-started to get up and running. If you have any questions or problems, don't hesitate to "ask":http://github.com/inbox/new/jeffkreeftmeijer.
|
12
|
+
|
13
|
+
h2. Contributing
|
14
|
+
|
15
|
+
Found an issue? Have a great idea? Want to help? Great! Create an issue "issue":http://github.com/jeffkreeftmeijer/navvy/issues for it, "ask":http://github.com/inbox/new/jeffkreeftmeijer, or even better; fork the project and fix the problem yourself. Pull requests are always welcome. :)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class NavvyGenerator < Rails::Generator::Base
|
2
|
+
default_options :orm => 'active_record'
|
3
|
+
|
4
|
+
def manifest
|
5
|
+
record do |m|
|
6
|
+
m.migration_template "#{options[:orm]}_migration.rb", 'db/migrate', {:migration_file_name => 'create_jobs'}
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_options!(opt)
|
11
|
+
opt.separator ''
|
12
|
+
opt.separator 'Options:'
|
13
|
+
opt.on('--active_record', 'Generate a migration file for ActiveRecord. (default)') { options[:orm] = 'active_record' }
|
14
|
+
opt.on('--sequel', 'Generate a migration file for Sequel.') { options[:orm] = 'sequel' }
|
15
|
+
end
|
16
|
+
|
17
|
+
def banner
|
18
|
+
"Usage: #{$0} #{spec.name}"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class CreateJobs < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :jobs, :force => true do |t|
|
4
|
+
t.string :object
|
5
|
+
t.string :method_name
|
6
|
+
t.text :arguments
|
7
|
+
t.integer :priority, :default => 0
|
8
|
+
t.string :return
|
9
|
+
t.string :exception
|
10
|
+
t.integer :parent_id
|
11
|
+
t.datetime :created_at
|
12
|
+
t.datetime :run_at
|
13
|
+
t.datetime :started_at
|
14
|
+
t.datetime :completed_at
|
15
|
+
t.datetime :failed_at
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.down
|
20
|
+
drop_table :jobs
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
up do
|
3
|
+
create_table(:jobs) do
|
4
|
+
primary_key :id, :type => Integer
|
5
|
+
String :object
|
6
|
+
String :method_name
|
7
|
+
String :arguments, :text => true
|
8
|
+
Integer :priority, :default => 0
|
9
|
+
String :return
|
10
|
+
String :exception
|
11
|
+
Integer :parent_id
|
12
|
+
DateTime :created_at
|
13
|
+
DateTime :run_at
|
14
|
+
DateTime :started_at
|
15
|
+
DateTime :completed_at
|
16
|
+
DateTime :failed_at
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
down do
|
21
|
+
drop_table(:jobs)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rails/generators/migration'
|
2
|
+
class NavvyGenerator < Rails::Generators::Base
|
3
|
+
include Rails::Generators::Migration
|
4
|
+
|
5
|
+
class_option :active_record,
|
6
|
+
:desc => 'Generate a migration file for ActiveRecord. (default)',
|
7
|
+
:type => 'boolean'
|
8
|
+
|
9
|
+
class_option :sequel,
|
10
|
+
:desc => 'Generate a migration file for Sequel.',
|
11
|
+
:type => 'boolean'
|
12
|
+
|
13
|
+
def self.source_root
|
14
|
+
File.join(File.dirname(__FILE__), '..', '..', 'generators', 'navvy', 'templates')
|
15
|
+
end
|
16
|
+
|
17
|
+
def install_navvy
|
18
|
+
migration_template(
|
19
|
+
"#{orm}_migration.rb",
|
20
|
+
'db/migrate/create_jobs.rb'
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def orm
|
25
|
+
options[:sequel] ? 'sequel' : 'active_record'
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
def self.next_migration_number(dirname) #:nodoc:
|
30
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
31
|
+
end
|
32
|
+
end
|
data/lib/navvy.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'navvy/worker'
|
2
|
+
require 'navvy/logger'
|
3
|
+
require 'navvy/configuration'
|
4
|
+
|
5
|
+
module Navvy
|
6
|
+
class << self
|
7
|
+
attr_writer :configuration
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.logger
|
11
|
+
@logger || Navvy.configuration.logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configuration
|
15
|
+
@configuration ||= Configuration.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.configure
|
19
|
+
yield(self.configuration)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
if defined?(Rails)
|
25
|
+
|
26
|
+
module Navvy
|
27
|
+
class Railtie < Rails::Railtie
|
28
|
+
rake_tasks do
|
29
|
+
require 'navvy/tasks.rb'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Navvy
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :job_limit, :keep_jobs, :logger, :sleep_time, :max_attempts
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@job_limit = 100
|
7
|
+
@keep_jobs = false
|
8
|
+
@logger = Navvy::Logger.new
|
9
|
+
@sleep_time = 5
|
10
|
+
@max_attempts = 25
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/navvy/job.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
module Navvy
|
2
|
+
class Job
|
3
|
+
class << self
|
4
|
+
attr_writer :limit, :keep, :max_attempts
|
5
|
+
end
|
6
|
+
|
7
|
+
##
|
8
|
+
# Limit of jobs to be fetched at once. Will use the value stored in
|
9
|
+
# Navvy.configuration (defaults to 100), or -- for backwards compatibility
|
10
|
+
# -- Navvy::Job.limit.
|
11
|
+
#
|
12
|
+
# @return [Integer] limit
|
13
|
+
|
14
|
+
def self.limit
|
15
|
+
@limit || Navvy.configuration.job_limit
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# If and how long the jobs should be kept. Will use the value stored in
|
20
|
+
# Navvy.configuration (defaults to false), or -- for backwards
|
21
|
+
# compatibility -- Navvy::Job.keep.
|
22
|
+
#
|
23
|
+
# @return [Fixnum, true, false] keep
|
24
|
+
|
25
|
+
def self.keep
|
26
|
+
@keep || Navvy.configuration.keep_jobs
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# How often should a job be retried? Will use the value stored in
|
31
|
+
# Navvy.configuration (defaults to 24), or -- for backwards compatibility
|
32
|
+
# -- Navvy::Job.max_attempts.
|
33
|
+
#
|
34
|
+
# @return [Fixnum] max_attempts
|
35
|
+
|
36
|
+
def self.max_attempts
|
37
|
+
@max_attempts || Navvy.configuration.max_attempts
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Should the job be kept? Will calculate if the keeptime has passed if
|
42
|
+
# @keep is a Fixnum. Otherwise, it'll just return the @keep value since
|
43
|
+
# it's probably a boolean.
|
44
|
+
#
|
45
|
+
# @return [true, false] keep
|
46
|
+
|
47
|
+
def self.keep?
|
48
|
+
return (Time.now + self.keep) >= Time.now if self.keep.is_a? Fixnum
|
49
|
+
self.keep
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Run the job. Will delete the Navvy::Job record and return its return
|
54
|
+
# value if it runs successfully unless Navvy::Job.keep is set. If a job
|
55
|
+
# fails, it'll call Navvy::Job#failed and pass the exception message.
|
56
|
+
# Failed jobs will _not_ get deleted.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# job = Navvy::Job.next # finds the next available job in the queue
|
60
|
+
# job.run # runs the job and returns the job's return value
|
61
|
+
#
|
62
|
+
# @return [String] return value or exception message of the called method.
|
63
|
+
|
64
|
+
def run
|
65
|
+
begin
|
66
|
+
started
|
67
|
+
result = constantize(object).send(method_name, *args).inspect
|
68
|
+
Navvy::Job.keep? ? completed(result) : destroy
|
69
|
+
result
|
70
|
+
rescue Exception => exception
|
71
|
+
failed(exception.message)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Retry the current job. Will add self to the queue again, giving the clone
|
77
|
+
# a parend_id equal to self.id. Also, the priority of the new job will be
|
78
|
+
# the same as its parent's and it'll set the :run_at date to N ** 4, where
|
79
|
+
# N is the times_failed count.
|
80
|
+
#
|
81
|
+
# @return [Navvy::Job] job the new job it created.
|
82
|
+
|
83
|
+
def retry
|
84
|
+
self.class.enqueue(
|
85
|
+
object,
|
86
|
+
method_name,
|
87
|
+
*(args << {
|
88
|
+
:job_options => {
|
89
|
+
:parent_id => parent_id || id,
|
90
|
+
:run_at => Time.now + times_failed ** 4,
|
91
|
+
:priority => priority
|
92
|
+
}
|
93
|
+
})
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Check if the job has been run.
|
99
|
+
#
|
100
|
+
# @return [true, false] ran
|
101
|
+
|
102
|
+
def ran?
|
103
|
+
completed? || failed?
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Check how long it took for a job to complete or fail.
|
108
|
+
#
|
109
|
+
# @return [Time, Integer] time the time it took.
|
110
|
+
|
111
|
+
def duration
|
112
|
+
ran? ? (completed_at || failed_at) - started_at : 0
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Check if completed_at is set.
|
117
|
+
#
|
118
|
+
# @return [true, false] set?
|
119
|
+
|
120
|
+
def completed_at?
|
121
|
+
!completed_at.nil?
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Check if failed_at is set.
|
126
|
+
#
|
127
|
+
# @return [true, false] set?
|
128
|
+
|
129
|
+
def failed_at?
|
130
|
+
!failed_at.nil?
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Get the job arguments as an array.
|
135
|
+
#
|
136
|
+
# @return [array] arguments
|
137
|
+
|
138
|
+
def args
|
139
|
+
arguments.is_a?(Array) ? arguments : YAML.load(arguments)
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Get the job status
|
144
|
+
#
|
145
|
+
# @return [:pending, :completed, :failed] status
|
146
|
+
|
147
|
+
def status
|
148
|
+
return :completed if completed?
|
149
|
+
return :failed if failed?
|
150
|
+
:pending
|
151
|
+
end
|
152
|
+
|
153
|
+
alias_method :completed?, :completed_at?
|
154
|
+
alias_method :failed?, :failed_at?
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
##
|
159
|
+
# Turn a constant with potential namespacing into an object
|
160
|
+
#
|
161
|
+
# @return [Class] class
|
162
|
+
|
163
|
+
def constantize(str)
|
164
|
+
names = str.split('::')
|
165
|
+
names.shift if names.empty? || names.first.empty?
|
166
|
+
constant = Object
|
167
|
+
names.each do |name|
|
168
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
169
|
+
end
|
170
|
+
constant
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Navvy
|
4
|
+
class Job < ActiveRecord::Base
|
5
|
+
|
6
|
+
##
|
7
|
+
# Add a job to the job queue.
|
8
|
+
#
|
9
|
+
# @param [Object] object the object you want to run a method from
|
10
|
+
# @param [Symbol, String] method_name the name of the method you want to run
|
11
|
+
# @param [*] arguments optional arguments you want to pass to the method
|
12
|
+
#
|
13
|
+
# @return [Job, false] created Job or false if failed
|
14
|
+
|
15
|
+
def self.enqueue(object, method_name, *args)
|
16
|
+
options = {}
|
17
|
+
if args.last.is_a?(Hash)
|
18
|
+
options = args.last.delete(:job_options) || {}
|
19
|
+
args.pop if args.last.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
create(
|
23
|
+
:object => object.to_s,
|
24
|
+
:method_name => method_name.to_s,
|
25
|
+
:arguments => args.to_yaml,
|
26
|
+
:priority => options[:priority] || 0,
|
27
|
+
:parent_id => options[:parent_id],
|
28
|
+
:run_at => options[:run_at] || Time.now,
|
29
|
+
:created_at => Time.now
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Find the next available jobs in the queue. This will not include failed
|
35
|
+
# jobs (where :failed_at is not nil) and jobs that should run in the future
|
36
|
+
# (where :run_at is greater than the current time).
|
37
|
+
#
|
38
|
+
# @param [Integer] limit the limit of jobs to be fetched. Defaults to
|
39
|
+
# Navvy::Job.limit
|
40
|
+
#
|
41
|
+
# @return [array, nil] the next available jobs in an array or nil if no
|
42
|
+
# jobs were found.
|
43
|
+
|
44
|
+
def self.next(limit = self.limit)
|
45
|
+
all(
|
46
|
+
:conditions => [
|
47
|
+
"#{connection.quote_column_name('failed_at')} IS NULL AND #{connection.quote_column_name('completed_at')} IS NULL AND #{connection.quote_column_name('run_at')} <= ?",
|
48
|
+
Time.now
|
49
|
+
],
|
50
|
+
:limit => limit,
|
51
|
+
:order => 'priority desc, created_at'
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Clean up jobs that we don't need to keep anymore. If Navvy::Job.keep is
|
57
|
+
# false it'll delete every completed job, if it's a timestamp it'll only
|
58
|
+
# delete completed jobs that have passed their keeptime.
|
59
|
+
#
|
60
|
+
# @return [true, false] delete_all the result of the delete_all call
|
61
|
+
|
62
|
+
def self.cleanup
|
63
|
+
if keep.is_a? Fixnum
|
64
|
+
delete_all([
|
65
|
+
"#{connection.quote_column_name('completed_at')} <= ?",
|
66
|
+
keep.ago
|
67
|
+
])
|
68
|
+
else
|
69
|
+
delete_all(
|
70
|
+
"#{connection.quote_column_name('completed_at')} IS NOT NULL"
|
71
|
+
) unless keep?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Mark the job as started. Will set started_at to the current time.
|
77
|
+
#
|
78
|
+
# @return [true, false] update_attributes the result of the
|
79
|
+
# update_attributes call
|
80
|
+
|
81
|
+
def started
|
82
|
+
update_attributes({
|
83
|
+
:started_at => Time.now
|
84
|
+
})
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Mark the job as completed. Will set completed_at to the current time and
|
89
|
+
# optionally add the return value if provided.
|
90
|
+
#
|
91
|
+
# @param [String] return_value the return value you want to store.
|
92
|
+
#
|
93
|
+
# @return [true, false] update_attributes the result of the
|
94
|
+
# update_attributes call
|
95
|
+
|
96
|
+
def completed(return_value = nil)
|
97
|
+
update_attributes({
|
98
|
+
:completed_at => Time.now,
|
99
|
+
:return => return_value
|
100
|
+
})
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Mark the job as failed. Will set failed_at to the current time and
|
105
|
+
# optionally add the exception message if provided. Also, it will retry
|
106
|
+
# the job unless max_attempts has been reached.
|
107
|
+
#
|
108
|
+
# @param [String] exception the exception message you want to store.
|
109
|
+
#
|
110
|
+
# @return [true, false] update_attributes the result of the
|
111
|
+
# update_attributes call
|
112
|
+
|
113
|
+
def failed(message = nil)
|
114
|
+
self.retry unless times_failed >= self.class.max_attempts
|
115
|
+
update_attributes(
|
116
|
+
:failed_at => Time.now,
|
117
|
+
:exception => message
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Check how many times the job has failed. Will try to find jobs with a
|
123
|
+
# parent_id that's the same as self.id and count them
|
124
|
+
#
|
125
|
+
# @return [Integer] count the amount of times the job has failed
|
126
|
+
|
127
|
+
def times_failed
|
128
|
+
i = parent_id || id
|
129
|
+
self.class.count(
|
130
|
+
:conditions => "(#{connection.quote_column_name('id')} = '#{i}' OR #{connection.quote_column_name('parent_id')} = '#{i}') AND #{connection.quote_column_name('failed_at')} IS NOT NULL"
|
131
|
+
)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
require 'navvy/job'
|