couch_loafer 0.1.0
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.
- data/LICENSE +20 -0
- data/README.md +7 -0
- data/Rakefile +52 -0
- data/lib/couch_loafer.rb +11 -0
- data/lib/couch_loafer/job.rb +102 -0
- data/lib/couch_loafer/worker.rb +32 -0
- data/spec/couch_loafer/job_spec.rb +133 -0
- data/spec/couch_loafer/worker_spec.rb +26 -0
- data/spec/spec_helper.rb +45 -0
- metadata +75 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Adam Groves
|
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.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require File.join(File.expand_path(File.dirname(__FILE__)),'lib','couch_loafer')
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |s|
|
7
|
+
s.name = "couch_loafer"
|
8
|
+
s.date = "2009-01-22"
|
9
|
+
s.summary = %Q{Foo}
|
10
|
+
s.email = "adam.groves@gmail.com"
|
11
|
+
s.homepage = "http://github.com/addywaddy/couch_loafer"
|
12
|
+
s.description = "Bar"
|
13
|
+
s.has_rdoc = true
|
14
|
+
s.authors = ["Adam Groves"]
|
15
|
+
s.files = %w( LICENSE README.md Rakefile ) + Dir["{lib,spec}/**/*"]
|
16
|
+
s.extra_rdoc_files = %w( README.md LICENSE )
|
17
|
+
s.require_path = "lib"
|
18
|
+
s.add_dependency("couch_surfer", ">= 0.1.0")
|
19
|
+
end
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'rake/rdoctask'
|
25
|
+
Rake::RDocTask.new do |rdoc|
|
26
|
+
rdoc.rdoc_dir = 'rdoc'
|
27
|
+
rdoc.title = 'couch_loafer'
|
28
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
29
|
+
rdoc.rdoc_files.include('README*')
|
30
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
31
|
+
end
|
32
|
+
|
33
|
+
require 'spec/rake/spectask'
|
34
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
35
|
+
t.libs << 'lib' << 'spec'
|
36
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
37
|
+
end
|
38
|
+
|
39
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
40
|
+
t.libs << 'lib' << 'spec'
|
41
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
42
|
+
t.rcov = true
|
43
|
+
end
|
44
|
+
|
45
|
+
begin
|
46
|
+
require 'cucumber/rake/task'
|
47
|
+
Cucumber::Rake::Task.new(:features)
|
48
|
+
rescue LoadError
|
49
|
+
puts "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
50
|
+
end
|
51
|
+
|
52
|
+
task :default => :spec
|
data/lib/couch_loafer.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'extlib'
|
6
|
+
require 'couch_surfer'
|
7
|
+
|
8
|
+
module CouchLoafer
|
9
|
+
autoload :Job, 'couch_loafer/job'
|
10
|
+
autoload :Worker, 'couch_loafer/worker'
|
11
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'couch_surfer'
|
2
|
+
|
3
|
+
module CouchLoafer
|
4
|
+
class Job
|
5
|
+
include CouchSurfer::Model
|
6
|
+
|
7
|
+
cattr_accessor :worker_name
|
8
|
+
cattr_accessor :keep_in_foreground
|
9
|
+
|
10
|
+
self.worker_name = "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
|
11
|
+
|
12
|
+
key_accessor :method_name, :instance_id, :class_name, :priority, :attempts, :owner, :failed_at, :exception, :arguments
|
13
|
+
|
14
|
+
set_default({
|
15
|
+
:priority => 0,
|
16
|
+
:attempts => 0
|
17
|
+
})
|
18
|
+
|
19
|
+
timestamps!
|
20
|
+
|
21
|
+
cast :failed_at, :as => 'Time'
|
22
|
+
|
23
|
+
view_by :importance,
|
24
|
+
:map =>
|
25
|
+
"function(doc) {
|
26
|
+
if ((doc['couchrest-type'] == 'CouchLoafer::Job')) {
|
27
|
+
time = - parseInt(doc.created_at.replace(/\\D/g, ''));
|
28
|
+
emit([doc.priority, time], null);
|
29
|
+
}
|
30
|
+
}"
|
31
|
+
|
32
|
+
view_by :failed_at
|
33
|
+
class << self
|
34
|
+
|
35
|
+
def foreground?
|
36
|
+
@@keep_in_foreground
|
37
|
+
end
|
38
|
+
|
39
|
+
def add(*args)
|
40
|
+
instance = args.first
|
41
|
+
options = extract_options!(args)
|
42
|
+
case instance
|
43
|
+
when CouchSurfer::Model
|
44
|
+
if foreground?
|
45
|
+
instance.send(options[:method])
|
46
|
+
else
|
47
|
+
create_instance_job(options.merge(:class_name => instance.class.name, :instance_id => instance.id))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def available
|
53
|
+
by_importance(:limit => 5)
|
54
|
+
end
|
55
|
+
|
56
|
+
def work_off
|
57
|
+
available.each do |job|
|
58
|
+
job.lock!(worker_name)
|
59
|
+
job.execute!
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def retrieve(id)
|
64
|
+
get(id)
|
65
|
+
rescue RestClient::ResourceNotFound
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def create_instance_job(options)
|
72
|
+
options.merge!(:method_name => options[:method].to_s)
|
73
|
+
options.delete(:method)
|
74
|
+
create(options)
|
75
|
+
end
|
76
|
+
|
77
|
+
def extract_options!(args)
|
78
|
+
args.last.is_a?(Hash) ? args.pop : {}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def lock!(worker_name)
|
83
|
+
update_attributes(:owner => worker_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def fail!(e)
|
87
|
+
update_attributes(:failed_at => Time.now, :owner => nil, :exception => {:message => e.message, :backtrace => e.backtrace})
|
88
|
+
end
|
89
|
+
|
90
|
+
def execute!
|
91
|
+
instance = ::Extlib::Inflection.constantize(class_name).get(instance_id)
|
92
|
+
if arguments
|
93
|
+
instance.send(method_name, *arguments)
|
94
|
+
else
|
95
|
+
instance.send(method_name)
|
96
|
+
end
|
97
|
+
destroy
|
98
|
+
rescue StandardError => e
|
99
|
+
fail!(e)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CouchLoafer
|
2
|
+
class Worker
|
3
|
+
SLEEP = 5
|
4
|
+
|
5
|
+
cattr_accessor :logger
|
6
|
+
self.logger = if defined?(Merb::Logger)
|
7
|
+
Merb.logger
|
8
|
+
elsif defined?(RAILS_DEFAULT_LOGGER)
|
9
|
+
RAILS_DEFAULT_LOGGER
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
trap('TERM') { say 'Exiting...'; $exit = true }
|
14
|
+
trap('INT') { say 'Exiting...'; $exit = true }
|
15
|
+
loop do
|
16
|
+
single_run
|
17
|
+
break if $exit
|
18
|
+
sleep(SLEEP)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def single_run
|
23
|
+
CouchLoafer::Job.work_off
|
24
|
+
end
|
25
|
+
|
26
|
+
def say(text)
|
27
|
+
puts text unless @quiet
|
28
|
+
logger.info text if logger
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe CouchLoafer::Job do
|
4
|
+
class Model
|
5
|
+
def do_something
|
6
|
+
"OK"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
describe "when adding an instance job" do
|
10
|
+
before(:all) do
|
11
|
+
@dispatch = Dispatch.create()
|
12
|
+
@job = CouchLoafer::Job.add(@dispatch, :method => :prepare_archive)
|
13
|
+
end
|
14
|
+
it "should serialize the method name" do
|
15
|
+
@job.method_name.should == "prepare_archive"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should serialize the instance's ID" do
|
19
|
+
@job.instance_id.should == @dispatch.id
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should specify the instance's class name" do
|
23
|
+
@job.class_name.should == "Dispatch"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should have timestamps" do
|
27
|
+
@job.created_at.should == Time.parse(@job['created_at'])
|
28
|
+
@job.updated_at.should == Time.parse(@job['updated_at'])
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should have an initial priority" do
|
32
|
+
@job.priority.should == 0
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should have an initial number of attempts" do
|
36
|
+
@job.attempts.should == 0
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should start off without an owner" do
|
40
|
+
@job.owner.should be_nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "when adding an instance job with arguments" do
|
45
|
+
before(:all) do
|
46
|
+
@dispatch = Dispatch.create()
|
47
|
+
@job = CouchLoafer::Job.add(@dispatch, :method => :update_values, :arguments => ["foo", 1])
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should serialie the arguments" do
|
51
|
+
@job.arguments.should == ["foo", 1]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "when listing the most important jobs" do
|
56
|
+
before(:all) do
|
57
|
+
reset_db!
|
58
|
+
dispatch = Dispatch.create()
|
59
|
+
10.times do |i|
|
60
|
+
CouchLoafer::Job.add(dispatch, :method => :prepare_archive, :priority => 10-i)
|
61
|
+
end
|
62
|
+
sleep(1)
|
63
|
+
CouchLoafer::Job.add(dispatch, :method => :prepare_archive, :priority => 5)
|
64
|
+
@most_important = CouchLoafer::Job.add(dispatch, :method => :prepare_archive)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should return the top five jobs" do
|
68
|
+
CouchLoafer::Job.available.size.should == 5
|
69
|
+
end
|
70
|
+
it "should have the most important job first" do
|
71
|
+
CouchLoafer::Job.available.first.should == @most_important
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "when running a job" do
|
76
|
+
before(:each) do
|
77
|
+
@dispatch = Dispatch.create()
|
78
|
+
@job = CouchLoafer::Job.add(@dispatch, :method => :prepare_archive)
|
79
|
+
@bad_job = CouchLoafer::Job.add(@dispatch, :method => :non_existant_method)
|
80
|
+
end
|
81
|
+
it "should execute the method on the model instance" do
|
82
|
+
@job.execute!
|
83
|
+
Dispatch.get(@dispatch.id).archived.should be_true
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should mark it as failed, unlock it and log the errors if an Exception occurs" do
|
87
|
+
@bad_job.execute!
|
88
|
+
reloaded_bad_job = CouchLoafer::Job.get(@bad_job.id)
|
89
|
+
reloaded_bad_job.failed_at.should be_kind_of(Time)
|
90
|
+
reloaded_bad_job.exception['message'].should match(/undefined method/)
|
91
|
+
reloaded_bad_job.owner.should be_nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "when running a job with arguments" do
|
96
|
+
before(:each) do
|
97
|
+
@dispatch = Dispatch.create()
|
98
|
+
@job = CouchLoafer::Job.add(@dispatch, :method => :update_values, :arguments => ["foo", 1])
|
99
|
+
end
|
100
|
+
it "should execute the method on the model instance" do
|
101
|
+
@job.execute!
|
102
|
+
Dispatch.get(@dispatch.id).value1.should == "foo"
|
103
|
+
Dispatch.get(@dispatch.id).value2.should == 1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "when retrieving a job" do
|
108
|
+
before(:each) do
|
109
|
+
@dispatch = Dispatch.create()
|
110
|
+
@job = CouchLoafer::Job.add(@dispatch, :method => :update_values, :arguments => ["foo", 1])
|
111
|
+
end
|
112
|
+
it "should retrieve the if it exists" do
|
113
|
+
CouchLoafer::Job.retrieve(@job.id).id.should == @job.id
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should return nil if the job doesn't exist" do
|
117
|
+
CouchLoafer::Job.get(@job.id).send(:execute!)
|
118
|
+
CouchLoafer::Job.retrieve(@job.id).should be_nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "keeping a job in the foreground" do
|
123
|
+
after(:each) do
|
124
|
+
CouchLoafer::Job.keep_in_foreground = false
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should do that" do
|
128
|
+
CouchLoafer::Job.keep_in_foreground = true
|
129
|
+
thing = Dispatch.create()
|
130
|
+
CouchLoafer::Job.add(thing, :method => :do_something).should == "OK"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe CouchLoafer::Worker do
|
4
|
+
describe "when started" do
|
5
|
+
before(:all) do
|
6
|
+
reset_db!
|
7
|
+
9.times do |i|
|
8
|
+
dispatch = Dispatch.create()
|
9
|
+
CouchLoafer::Job.add(dispatch, :method => :prepare_archive)
|
10
|
+
end
|
11
|
+
CouchLoafer::Job.add(Dispatch.create, :method => :no_method)
|
12
|
+
CouchLoafer::Worker.new.single_run
|
13
|
+
end
|
14
|
+
it "should delete the jobs once executed" do
|
15
|
+
CouchLoafer::Job.all.size.should == 6
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should mark one job as having failed" do
|
19
|
+
CouchLoafer::Job.by_failed_at.size.should == 1
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should carry out the work" do
|
23
|
+
Dispatch.by_archived.size.should == 4
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
begin
|
2
|
+
require 'spec'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems'
|
5
|
+
gem 'rspec'
|
6
|
+
require 'spec'
|
7
|
+
end
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
|
10
|
+
|
11
|
+
require 'couch_loafer'
|
12
|
+
|
13
|
+
COUCHHOST = "http://127.0.0.1:5984"
|
14
|
+
TESTDB = 'couch_loafer-test'
|
15
|
+
def reset_db!
|
16
|
+
cr = CouchRest.new(COUCHHOST)
|
17
|
+
db = cr.database(TESTDB)
|
18
|
+
db.delete! rescue nil
|
19
|
+
CouchSurfer::Model.default_database = CouchRest.database!("#{COUCHHOST}/#{TESTDB}")
|
20
|
+
end
|
21
|
+
|
22
|
+
class Dispatch
|
23
|
+
include CouchSurfer::Model
|
24
|
+
key_accessor :archived, :value1, :value2
|
25
|
+
|
26
|
+
view_by :archived
|
27
|
+
|
28
|
+
def prepare_archive
|
29
|
+
update_attributes(:archived => true)
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_values(v1, v2)
|
33
|
+
update_attributes(:value1 => v1, :value2 => v2)
|
34
|
+
end
|
35
|
+
|
36
|
+
def do_something
|
37
|
+
"OK"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Spec::Runner.configure do |config|
|
42
|
+
require 'couch_surfer'
|
43
|
+
require 'couchrest'
|
44
|
+
reset_db!
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: couch_loafer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Groves
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-12 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: couch_surfer
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.3.2
|
24
|
+
version:
|
25
|
+
description: Bar
|
26
|
+
email: adam.groves@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.md
|
34
|
+
files:
|
35
|
+
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
- lib/couch_loafer.rb
|
39
|
+
- lib/couch_loafer/job.rb
|
40
|
+
- lib/couch_loafer/worker.rb
|
41
|
+
- spec/couch_loafer/job_spec.rb
|
42
|
+
- spec/couch_loafer/worker_spec.rb
|
43
|
+
- spec/spec_helper.rb
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: http://github.com/addywaddy/couch_loafer
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options:
|
50
|
+
- --charset=UTF-8
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.3.4
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Foo
|
72
|
+
test_files:
|
73
|
+
- spec/couch_loafer/job_spec.rb
|
74
|
+
- spec/couch_loafer/worker_spec.rb
|
75
|
+
- spec/spec_helper.rb
|