delayed_job_groups_mongoid 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source :rubygems
2
+
3
+ gem 'rails'
4
+ gem 'delayed_job', '2.1.1'
5
+ gem 'delayed_job_mongoid', '1.0.0'
6
+ gem 'mongoid', '2.0.0.beta.20'
7
+ gem 'bson_ext'
8
+
9
+
10
+ group :test do
11
+ gem 'rspec-rails'
12
+ gem 'sqlite3-ruby'
13
+ gem 'autotest'
14
+ gem 'autotest-fsevent'
15
+ gem 'autotest-growl'
16
+ gem 'database_cleaner'
17
+ gem 'ruby-debug'
18
+ end
data/README.markdown ADDED
@@ -0,0 +1,74 @@
1
+ delayed job groups
2
+ ==================
3
+
4
+ Adds grouping to jobs. Only 1 job in each group will be executed regardless of the number of workers.
5
+
6
+
7
+ Requires
8
+ --------
9
+
10
+ * rails 3
11
+ * Latest version of delayed_job from github - current release(v2.1.0.pre2) does not support enqueue hooks
12
+ * active_record backend - other backends aren't supported
13
+
14
+
15
+ Install
16
+ -------
17
+
18
+ # Gemfile
19
+ gem 'delayed_job_groups'
20
+
21
+ Add migration to add lock_group column
22
+
23
+ # db/migrate/xxx_add_lock_group_to_delayed_jobs
24
+ class AddLockGroupToDelayedJobs < ActiveRecord::Migration
25
+ def self.up
26
+ add_column :delayed_jobs, :lock_group, :string
27
+ end
28
+
29
+ def self.down
30
+ remove_column :delayed_jobs, :lock_group
31
+ end
32
+ end
33
+
34
+
35
+ Usage
36
+ -----
37
+
38
+ Job groups are strings. You need to specify what the job_group should be in a block. Delayed job will only perform 1 job from each group at a time.
39
+
40
+ ### Job groups for standard jobs ###
41
+
42
+ class ResizeImageJob < Struct.new(:format)
43
+ job_group{ |resize_image_job| resize_image_job.format }
44
+
45
+ def perform
46
+ resize_to format
47
+ end
48
+ end
49
+
50
+
51
+ ### Job groups when using delay() ###
52
+
53
+ class Person < ActiveRecord::Base
54
+ job_group{ |person| person.role }
55
+
56
+ def send_welcome
57
+ ...
58
+ end
59
+ end
60
+
61
+ Person.create(:role => "admin").delay.send_welcome
62
+
63
+ ### Job groups when methods are declared asynchonous ###
64
+
65
+ class Person < ActiveRecord::Base
66
+ job_group{ |person| person.role }
67
+
68
+ def send_welcome
69
+ ...
70
+ end
71
+ handle_asynchronously :send_welcome
72
+ end
73
+
74
+ Person.create(:role => "admin").send_welcome
@@ -0,0 +1,39 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile.rb, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{delayed_job_groups_mongoid}
8
+ s.version = "0.2.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["opsb"]
12
+ s.date = %q{2010-11-04}
13
+ s.description = %q{Adds job groups to delayed_job}
14
+ s.email = %q{oliver@opsb.co.uk}
15
+ s.extra_rdoc_files = [
16
+ "README.markdown"
17
+ ]
18
+ s.files = Dir.glob("lib/**/*") + %w(Gemfile delayed_job_groups_mongoid.gemspec README.markdown)
19
+ s.homepage = %q{http://github.com/opsb/delayed_job_groups}
20
+ s.rdoc_options = ["--charset=UTF-8"]
21
+ s.require_paths = ["lib"]
22
+ s.rubygems_version = %q{1.3.7}
23
+ s.summary = %q{Adds job groups to delayed_job}
24
+ s.test_files = [
25
+ "spec/delayed_job_groups_spec.rb",
26
+ "spec/spec_helper.rb"
27
+ ]
28
+
29
+ if s.respond_to? :specification_version then
30
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
31
+ s.specification_version = 3
32
+
33
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
34
+ else
35
+ end
36
+ else
37
+ end
38
+ end
39
+
@@ -0,0 +1,7 @@
1
+ module DelayedJobGroups
2
+ class Railtie < Rails::Railtie
3
+ initializer "delayed_job_groups.initialize" do |app|
4
+ require File.dirname(__FILE__) + '/delayed_job_groups/init.rb'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ require 'delayed_job'
2
+ require File.dirname(__FILE__) + '/mongoid_groups'
3
+ require File.dirname(__FILE__) + '/job_groups'
@@ -0,0 +1,29 @@
1
+ module JobGroups
2
+
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def job_group(&block)
9
+ @lock_group_factory = block
10
+ end
11
+
12
+ def has_job_groups?
13
+ !!@lock_group_factory
14
+ end
15
+
16
+ def lock_group(payload)
17
+ @lock_group_factory.call(payload)
18
+ end
19
+ end
20
+
21
+ def enqueue(job)
22
+ payload = job.payload_object
23
+ target = payload.class == ::Delayed::PerformableMethod ? payload.object : payload
24
+ if target.class.has_job_groups?
25
+ job.lock_group = target.class.lock_group(target)
26
+ job.save
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ require 'delayed_job'
2
+
3
+
4
+ module Delayed
5
+ class Backend::Mongoid::Job
6
+ field :lock_group
7
+ scope :in_unlocked_group, lambda{ where(:lock_group.nin => self.locked_groups) }
8
+
9
+ def self.locked_groups
10
+ Delayed::Job.only(:lock_group).where(:locked_by.ne => nil).map{|grouping| grouping[:lock_group]}
11
+ end
12
+
13
+ def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
14
+ right_now = db_time_now
15
+
16
+ conditions = {:run_at => {"$lte" => right_now}, :failed_at => nil}
17
+ (conditions[:priority] ||= {})['$gte'] = Worker.min_priority.to_i if Worker.min_priority
18
+ (conditions[:priority] ||= {})['$lte'] = Worker.max_priority.to_i if Worker.max_priority
19
+
20
+
21
+ where = "this.locked_at == null || this.locked_at < #{make_date(right_now - max_run_time)}"
22
+ results = self.in_unlocked_group.where(conditions.merge(:locked_by => worker_name)).limit(-limit).order_by([['priority', 1], ['run_at', 1]]).to_a
23
+ results += self.in_unlocked_group.where(conditions.merge('$where' => where)).limit(-limit+results.size).order_by([['priority', 1], ['run_at', 1]]).to_a if results.size < limit
24
+ results
25
+ end
26
+
27
+ end
28
+ end
29
+
30
+ __END__
31
+
32
+ scope :in_unlocked_group, lambda{
33
+ delayed_jobs = self.table_name
34
+ unlocked_groups_select = "select distinct #{delayed_jobs}.lock_group from #{delayed_jobs} where #{delayed_jobs}.locked_by is not null"
35
+ where("#{delayed_jobs}.lock_group not in (#{unlocked_groups_select}) or #{delayed_jobs}.lock_group is null")
36
+ }
37
+ scope :orig_ready_to_run, scopes[:ready_to_run]
38
+ scope :ready_to_run, lambda{ |*args|
39
+ orig_ready_to_run(*args).
40
+ in_unlocked_group
41
+ }
@@ -0,0 +1,107 @@
1
+ require 'spec/spec_helper'
2
+
3
+
4
+ class GroupedJob
5
+ include Mongoid::Document
6
+ include JobGroups
7
+ field :repository
8
+
9
+ job_group{ |simple_job| simple_job.repository }
10
+
11
+ def perform
12
+ puts "running grouped job"
13
+ end
14
+ end
15
+
16
+ class SimpleJob
17
+ include Mongoid::Document
18
+ include JobGroups
19
+
20
+ def perform
21
+ puts "running simple job"
22
+ end
23
+ end
24
+
25
+ class User
26
+ include Mongoid::Document
27
+ include JobGroups
28
+
29
+ job_group{ |user| user.role }
30
+ def send_welcome_email
31
+ puts "thanking user"
32
+ end
33
+ handle_asynchronously :send_welcome_email
34
+ def expensive_operation
35
+ puts "working hard"
36
+ end
37
+ end
38
+
39
+ describe Delayed::Job do
40
+ MAX_RUN_TIME = 4000 # seconds? TBC
41
+ WORKER = 'name_of_worker'
42
+
43
+ context "a job" do
44
+ before do
45
+ Delayed::Job.enqueue GroupedJob.new(:repository => "repo1")
46
+ end
47
+
48
+ it "should have a job group" do
49
+ Delayed::Job.first.lock_group.should == "repo1"
50
+ end
51
+ end
52
+
53
+ context "with 2 jobs in the same group, one locked, one unlocked and 1 job in a different group" do
54
+ before do
55
+ group_a_job1 = GroupedJob.new(:repository => "repo1")
56
+ group_a_job2 = GroupedJob.new(:repository => "repo1")
57
+ @group_b_job1 = GroupedJob.new(:repository => "repo2")
58
+
59
+ [group_a_job1, @group_b_job1].each{ |job| Delayed::Job.enqueue job }
60
+ queued_job = Delayed::Job.enqueue group_a_job2
61
+ queued_job.lock_exclusively!(MAX_RUN_TIME, WORKER)
62
+ end
63
+
64
+ it "should find no unlocked jobs" do
65
+ Delayed::Job.find_available(WORKER, MAX_RUN_TIME).count.should == 1
66
+ end
67
+ end
68
+
69
+ context "job with no group" do
70
+ before do
71
+ Delayed::Job.enqueue SimpleJob.new
72
+ end
73
+ it "should still be queuable" do
74
+ Delayed::Job.find_available(WORKER, MAX_RUN_TIME).count.should == 1
75
+ end
76
+ end
77
+
78
+ context "with 2 jobs in the same group, from methods declared as asynchronous, one unlocked and 1 job in a different group" do
79
+ before do
80
+ 2.times do
81
+ User.create(:role => "admin").send_welcome_email
82
+ end
83
+ 1.times{ Delayed::Job.enqueue GroupedJob.new(:repository => "repo2") }
84
+ Delayed::Job.first.lock_exclusively!(MAX_RUN_TIME, WORKER)
85
+
86
+ end
87
+
88
+ it "should find no jobs that are ready to run" do
89
+ Delayed::Job.find_available(WORKER, MAX_RUN_TIME).count.should == 1
90
+ end
91
+ end
92
+
93
+ context "with 2 jobs in the same group, from delay() calls, one unlocked and 1 job in a different group" do
94
+ before do
95
+ 2.times do
96
+ User.create(:role => "admin").delay.expensive_operation
97
+ end
98
+ 1.times{ Delayed::Job.enqueue GroupedJob.new(:repository => "repo2") }
99
+ Delayed::Job.first.lock_exclusively!(MAX_RUN_TIME, WORKER)
100
+
101
+ end
102
+
103
+ it "should find no jobs that are ready to run" do
104
+ Delayed::Job.find_available(WORKER, MAX_RUN_TIME).count.should == 1
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,41 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+ require 'logger'
6
+ require 'mongoid'
7
+
8
+
9
+
10
+ ENV['RAILS_ENV'] = 'test'
11
+ require 'rails'
12
+
13
+ Mongoid.configure do |config|
14
+ name = "delayed_job_groups_mongoid"
15
+ host = "localhost"
16
+ config.master = Mongo::Connection.new.db(name)
17
+ config.slaves = [
18
+ Mongo::Connection.new(host, 27017, :slave_ok => true).db(name)
19
+ ]
20
+ config.persist_in_safe_mode = false
21
+ end
22
+
23
+
24
+ require 'rspec'
25
+ require 'delayed_job'
26
+
27
+ Delayed::Worker.logger = Logger.new('/tmp/dj.log')
28
+
29
+ Delayed::Worker.backend = :mongoid
30
+
31
+
32
+ require 'delayed_job_groups/init.rb'
33
+ RSpec.configure do |config|
34
+ require 'database_cleaner'
35
+ DatabaseCleaner.strategy = :truncation
36
+ config.after(:each) do
37
+ DatabaseCleaner.clean
38
+ end
39
+ config.filter_run :focus => true
40
+ config.run_all_when_everything_filtered = true
41
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: delayed_job_groups_mongoid
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 2
10
+ version: 0.2.2
11
+ platform: ruby
12
+ authors:
13
+ - opsb
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-04 00:00:00 +00:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Adds job groups to delayed_job
23
+ email: oliver@opsb.co.uk
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README.markdown
30
+ files:
31
+ - lib/delayed_job_groups/init.rb
32
+ - lib/delayed_job_groups/job_groups.rb
33
+ - lib/delayed_job_groups/mongoid_groups.rb
34
+ - lib/delayed_job_groups.rb
35
+ - Gemfile
36
+ - delayed_job_groups_mongoid.gemspec
37
+ - README.markdown
38
+ - spec/delayed_job_groups_spec.rb
39
+ - spec/spec_helper.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/opsb/delayed_job_groups
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ hash: 3
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 3
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.3.7
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Adds job groups to delayed_job
74
+ test_files:
75
+ - spec/delayed_job_groups_spec.rb
76
+ - spec/spec_helper.rb