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 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
@@ -0,0 +1,7 @@
1
+ couch_loafer===========
2
+ Description goes here.
3
+
4
+ COPYRIGHT
5
+ =========
6
+
7
+ Copyright (c) 2008 Adam Groves. See LICENSE for details.
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
@@ -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
@@ -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