couch_loafer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|