couch_loafer 0.1.0

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