navvy-sequelhooks 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,3 @@
1
+ Copyright 2010 Jeff Kreeftmeijer.
2
+ You may use this work without restrictions, as long as this notice is included.
3
+ The work is provided "as is" without warranty of any kind, neither express nor implied.
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'