unique_delayed_job 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -12,19 +12,55 @@ duplicate key is raised on insert, then the insert will just be ignored. There
12
12
  are factory methods for creating a delayed job in the following ways:
13
13
  * with a delayed job handler class (one that responds to perform())
14
14
  * with an object, method and method arguments
15
- * with a code block
15
+ * with a string to be evaled see the Delayed::Job.enqueue() method that accepts
16
+ a block. (looks like the idea may have been added to delayed job here:
17
+ http://github.com/dhh/delayed_job/commit/89c3a0b77470c0f8510d3c7a51a36cd07747d9a9)
18
+ the block is yielded to, and the result (assumed to be a string) is stored as
19
+ the code to be evaled when the job is deserialized.
20
+
21
+ == Configuration
22
+
23
+ You can add whatever columns you need to the delayed_jobs table, and put any
24
+ uniqueness constraints (or not) on them as you wish (unique columns, unique
25
+ composite keys etc.).
26
+
27
+ Note that by default, UniqueDelayedJob will modify any jobs that are currently locked
28
+ (locked_by is not null)...it will set all of the additional columns specified
29
+ on the job to NULL. (This allows for inserting a new job if a matching job is
30
+ already in process--and thus might be using out-of-date data.) If you do NOT
31
+ want this behavior, and do not want to insert a matching job if another job is
32
+ currently locked/being run, then make the following call. If you like, you can
33
+ change this before/after every job you insert if you want different jobs
34
+ to use different policies.
35
+
36
+ UniqueDelayedJob.do_not_mark_locked_jobs_with_null()
16
37
 
17
38
  == Examples
18
39
 
19
- # use a custom handler
20
- job = Delayed::UniqueDelayedJob.use_handler(MyHandlerClass.new( ...), :user_id => 123)
21
- job.enqueue # use default priority and run_at
40
+ In each example below, the :user_id => 123 is just to illustrate that you can add
41
+ arbitrary additional column(s) to the delayed job row being inserted. Presumably,
42
+ the user_id column has uniqueness constraints on it...
43
+
44
+ (More than one column could be specified; uniqueness is enforced by the db schema,
45
+ and unique delayed job just catches and swallows any duplicate key exceptions.)
46
+
47
+ # use a custom handler
48
+ job = Delayed::UniqueDelayedJob.use_handler(MyHandlerClass.new( ...), :user_id => 123)
49
+ job.enqueue # use default priority and run_at
22
50
 
23
- # use a method call (similar to using send_later on the object)
24
- record = MyActiveRecord.find(1)
25
- job = Delayed::UniqueDelayedJob.call_method(record, :a_method, [arg1, arg2], :user_id => 123)
26
- job.enqueue(1) # use priority of 1
51
+ # use a method call (similar to using send_later on the object)
52
+ record = MyActiveRecord.find(1)
53
+ job = Delayed::UniqueDelayedJob.call_method(record,
54
+ :a_method,
55
+ [arg1, arg2],
56
+ :user_id => 123)
57
+ job.enqueue(1) # use priority of 1
27
58
 
28
- # use a code block
29
- job = Delayed::UniqueDelayedJob.run_block(:user_id => 123) { run_some_code }
30
- job.enqueue(2, 1.hour.from_now) # priority 1, run at 1 hour from now
59
+ # specify a block of code to execute with string (inside a block)
60
+ # note that the block could be complex logic that results in a string that is
61
+ # arbitrary ruby code to be evaled
62
+ # see Delayed::Job::enqueue(args, &block)
63
+ job = Delayed::UniqueDelayedJob.run_eval(:user_id => 123) do
64
+ 'SomeClass.perform_some_slow_action'
65
+ end
66
+ job.enqueue(2, 1.hour.from_now) # run with priority 2 and run at 1 hour from now
@@ -0,0 +1,157 @@
1
+ module Delayed
2
+
3
+ # allows for specifying additional columns on the delayed_jobs table to help
4
+ # prevent duplicate delayed jobs from being entered into the queue...but still
5
+ # keep an easy interface to enqueuing delayed jobs
6
+ #-----------------------------------------------------------------------------
7
+ class UniqueDelayedJob
8
+
9
+ cattr_reader :mark_all_locked_jobs_with_null
10
+
11
+ @@mark_all_locked_jobs_with_null = true
12
+
13
+ # enable the polciy that all delayed job rows that are marked with non-null
14
+ # locked_by should have their columns set to null...ensuring that if a
15
+ # job is currently executing then we CAN insert another delayed job with
16
+ # the same unique keys.
17
+ # This is useful if you want to run a job every time some event occurs, but
18
+ # you're using this gem just to prevent having the same job twice in the
19
+ # queue. But if a job is currently running, it may miss the change in state
20
+ # due to the latest event, so a new job does need to be added to the queue.
21
+ # setting the columns to null on any jobs currently locked should
22
+ # ensure this behavior.
23
+ # This is the default behavior.
24
+ #-----------------------------------------------------------------------------
25
+ def self.mark_all_locked_jobs_with_null
26
+ @mark_all_locked_jobs_with_null = true
27
+ end
28
+
29
+ # override the default behavior, and DO NOT mark the columns on a delayed
30
+ # job that is currently locked to null. this prevents inserting a duplicate
31
+ # job with a job that is currently running (e.g. if you only want to run
32
+ # the job once). Default is mark_all_locked_jobs_with_null (see that method)
33
+ #-----------------------------------------------------------------------------
34
+ def self.do_not_mark_locked_jobs_with_null
35
+ @mark_all_locked_jobs_with_null = false
36
+ end
37
+
38
+
39
+ # factory method to create a new UniqueDelayedJob from a delayed job handler
40
+ # object (see delayed job documentation for requirements)
41
+ #
42
+ # arguments:
43
+ # - handler: the delayed jobs handler object you're using
44
+ # - columns: hash of column names and values to insert into the delayed
45
+ # jobs table in addition to the handler
46
+ #---------------------------------------------------------------------------
47
+ def self.use_handler(handler, columns = {})
48
+ job = self.new(handler, columns)
49
+ end
50
+
51
+
52
+ # factory method to create a new UniqueDelayedJob by specifying a method ton
53
+ # call. will use delayed jobs' PerformableMethod class to enqueue the job
54
+ #
55
+ # arguments:
56
+ # - object: the object (or class or module) on which to call the method
57
+ # - method: the method to call (specify a symbol or string)
58
+ # - args_arr: an array of arguments to pass in the method call)
59
+ # - columns: hash of column names and values to insert into the delayed
60
+ # jobs table in addition to the handler
61
+ #---------------------------------------------------------------------------
62
+ def self.call_method(object, method, args_arr, columns = {})
63
+ job = self.new(Delayed::PerformableMethod.new(object, method, args_arr),
64
+ columns)
65
+ end
66
+
67
+
68
+ # factory method to create a UniqueDelayedJob by specifying a block that
69
+ # is executed and whose result is stored as a string to be evaled by
70
+ # delayed_job
71
+ # arguments:
72
+ # - columns: hash of column names and values to insert into the delayed
73
+ # jobs table in addition to the handler
74
+ # NOTE: a block is expected
75
+ #---------------------------------------------------------------------------
76
+ def self.run_eval(columns = {}, &block)
77
+ raise "missing a block in call to run_block" if !block_given?
78
+ job = self.new(Delayed::EvaledJob.new(&block), columns)
79
+ end
80
+
81
+
82
+ # specify some additional columns to set in the delayed jobs table for this
83
+ # row. be sure that you've migrated to add these columns to the delayed jobs
84
+ # table. it is up to you to specify uniqueness constraints on any of the
85
+ # columns you'd like to use to prevent duplicate entries in the delayed jobs
86
+ # table. (it's also fine for some of these columns to not have unique
87
+ # constraints, though this class will not prevent duplicate values for those
88
+ # and they'll be for your use for other purposes.)
89
+ #---------------------------------------------------------------------------
90
+ def add_delayed_jobs_columns(new_columns)
91
+ columns.merge! new_columns
92
+ end
93
+
94
+
95
+ # put the job on the delayed jobs queue. if there already is a row in the
96
+ # delayed jobs table with the same value in any of the unique columns
97
+ # (enforced in the db), then the row will not be inserted
98
+ #
99
+ # return value:
100
+ # - if a new delayed job is inserted, the job object is returned
101
+ # - otherwise, returns nil
102
+ #
103
+ # arguments:
104
+ # priority:
105
+ # run_at:
106
+ #---------------------------------------------------------------------------
107
+ def enqueue(priority = nil, run_at = nil)
108
+ if mark_all_locked_jobs_with_null && !columns.blank?
109
+ null_setting_strings = []
110
+
111
+ columns.each_key do |c|
112
+ null_setting_strings << "#{c.to_s} = NULL"
113
+ end
114
+
115
+ Delayed::Job.update_all(null_setting_strings.join("\n ,"),
116
+ "locked_by IS NOT NULL")
117
+ end
118
+
119
+ cols_to_insert = columns
120
+ cols_to_insert.merge! :priority => priority if priority
121
+ cols_to_insert.merge! :run_at => run_at if run_at
122
+ cols_to_insert.merge! :handler => handler
123
+
124
+ job = nil
125
+
126
+ # try to catch if this raises an exception because of a duplicate key
127
+ # error this should work for mysql and postgresql which both have the
128
+ # word 'duplicate' followed (not necessarily immediately) by 'key'.
129
+ # ignoring case cause case differs between the two cases
130
+ # if doesn't look like a dupe key error, then reraise the exception
131
+ begin
132
+ job = Delayed::Job.create(cols_to_insert)
133
+ rescue => e
134
+ if /(duplicate).*(key)/i !~ e.message
135
+ raise e
136
+ end
137
+ end
138
+
139
+ return job
140
+ end
141
+
142
+ attr_accessor :columns
143
+
144
+ protected
145
+
146
+ attr_accessor :handler
147
+
148
+ # constructor used by the factory methods
149
+ #---------------------------------------------------------------------------
150
+ def initialize(handler, columns = {})
151
+ @handler = handler
152
+ @columns = columns
153
+ end
154
+
155
+ end
156
+
157
+ end
@@ -1,116 +1 @@
1
- module Delayed
2
-
3
- # allows for specifying additional columns on the delayed_jobs table to help
4
- # prevent duplicate delayed jobs from being entered into the queue...but still
5
- # keep an easy interface to enqueuing delayed jobs
6
- #-----------------------------------------------------------------------------
7
- class UniqueDelayedJob
8
-
9
- # factory method to create a new UniqueDelayedJob from a delayed job handler
10
- # object (see delayed job documentation for requirements)
11
- #
12
- # arguments:
13
- # - handler: the delayed jobs handler object you're using
14
- # - columns: hash of column names and values to insert into the delayed
15
- # jobs table in addition to the handler
16
- #---------------------------------------------------------------------------
17
- def self.use_handler(handler, columns = {})
18
- job = self.new
19
- job.handler = handler
20
-
21
- job
22
- end
23
-
24
- # factory method to create a new UniqueDelayedJob by specifying a method ton
25
- # call. will use delayed jobs' PerformableMethod class to enqueue the job
26
- #
27
- # arguments:
28
- # - object: the object (or class or module) on which to call the method
29
- # - method: the method to call (specify a symbol or string)
30
- # - args_arr: an array of arguments to pass in the method call)
31
- # - columns: hash of column names and values to insert into the delayed
32
- # jobs table in addition to the handler
33
- #---------------------------------------------------------------------------
34
- def self.call_method(object, method, args_arr, columns = {})
35
- job = self.new
36
- job.handler = Delayed::PerformableMethod.new(object, method, args_arr)
37
-
38
- job
39
- end
40
-
41
-
42
- # factory method to create a UniqueDelayedJob by specifying a block to
43
- # execute asynchronously
44
- # arguments:
45
- # - columns: hash of column names and values to insert into the delayed
46
- # jobs table in addition to the handler
47
- # NOTE: a block is expected
48
- #---------------------------------------------------------------------------
49
- def self.run_block(columns = {}, &block)
50
- raise "missing a block in call to run_block" if !block_given?
51
- job = self.new
52
- job.handler = Delayed::EvaledJob.new(&block)
53
-
54
- job
55
- end
56
-
57
-
58
- # specify some additional columns to set in the delayed jobs table for this
59
- # row. be sure that you've migrated to add these columns to the delayed jobs
60
- # table. it is up to you to specify uniqueness constraints on any of the
61
- # columns you'd like to use to prevent duplicate entries in the delayed jobs
62
- # table. (it's also fine for some of these columns to not have unique
63
- # constraints, though this class will not prevent duplicate values for those
64
- # and they'll be for your use for other purposes.)
65
- #---------------------------------------------------------------------------
66
- def add_delayed_jobs_columns(new_columns)
67
- columns.merge! new_columns
68
- end
69
-
70
- # put the job on the delayed jobs queue. if there already is a row in the
71
- # delayed jobs table with the same value in any of the unique columns
72
- # (enforced in the db), then the row will not be inserted
73
- #
74
- # arguments:
75
- # priority:
76
- # run_at:
77
- #---------------------------------------------------------------------------
78
- def enqueue(priority = nil, run_at = nil)
79
- cols_to_insert = columns
80
- cols_to_insert.merge! :priority => priority if priority
81
- cols_to_insert.merge! :run_at => run_at if run_at
82
- cols_to_insert.merge! :handler => handler
83
-
84
- # try to catch if this raises an exception because of a duplicate key
85
- # error this should work for mysql and postgresql which both have the
86
- # word 'duplicate' followed (not necessarily immediately) by 'key'.
87
- # ignoring case cause case differs between the two cases
88
- # if doesn't look like a dupe key error, then reraise the exception
89
- begin
90
- Delayed::Job.create(cols_to_insert)
91
- rescue => e
92
- if /(duplicate).*(key)/i !~ e.message
93
- raise e
94
- end
95
- end
96
-
97
- end
98
-
99
- attr_accessor :columns
100
-
101
- protected
102
-
103
- attr_accessor :handler
104
-
105
-
106
- # constructor used by the factory methods
107
- #---------------------------------------------------------------------------
108
- def initialize(handler, columns = {})
109
- @handler = nil
110
- @call_method = { :method => nil, args => nil }
111
- @columns = {}
112
- end
113
-
114
- end
115
-
116
- end
1
+ require 'delayed/unique_delayed_job'
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{unique_delayed_job}
8
- s.version = "0.0.1"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Brian Percival"]
12
- s.date = %q{2009-11-18}
12
+ s.date = %q{2009-11-19}
13
13
  s.description = %q{Class for creating delayed jobs that can be de-duped with existing delayed jobs
14
14
  already in the delayed jobs table. You just specify some additional columns on
15
15
  your delayed_jobs table and set them to have uniqueness constraints. Then
@@ -18,7 +18,7 @@ duplicate key is raised on insert, then the insert will just be ignored. There
18
18
  are factory methods for creating a delayed job in the following ways:
19
19
  * with a delayed job handler class (one that responds to perform())
20
20
  * with an object, method and method arguments
21
- * with a code block
21
+ * with a code string to be evaled
22
22
 
23
23
  NOTE: you must have delayed_job installed as a gem or plugin
24
24
  }
@@ -27,7 +27,8 @@ NOTE: you must have delayed_job installed as a gem or plugin
27
27
  "README"
28
28
  ]
29
29
  s.files = [
30
- "lib/unique_delayed_job.rb",
30
+ "lib/delayed/unique_delayed_job.rb",
31
+ "lib/unique_delayed_job.rb",
31
32
  "unique_delayed_job.gemspec"
32
33
  ]
33
34
  s.homepage = %q{http://github.com/bmpercy/unique_delayed_job}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unique_delayed_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Percival
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-18 00:00:00 -08:00
12
+ date: 2009-11-19 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -22,7 +22,7 @@ description: |
22
22
  are factory methods for creating a delayed job in the following ways:
23
23
  * with a delayed job handler class (one that responds to perform())
24
24
  * with an object, method and method arguments
25
- * with a code block
25
+ * with a code string to be evaled
26
26
 
27
27
  NOTE: you must have delayed_job installed as a gem or plugin
28
28
 
@@ -34,6 +34,7 @@ extensions: []
34
34
  extra_rdoc_files:
35
35
  - README
36
36
  files:
37
+ - lib/delayed/unique_delayed_job.rb
37
38
  - lib/unique_delayed_job.rb
38
39
  - unique_delayed_job.gemspec
39
40
  - README