ruote-resque 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.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rubocop.yml +4 -0
- data/.travis.yml +14 -0
- data/.yardopts +4 -0
- data/Gemfile +13 -0
- data/LICENSE +22 -0
- data/README.md +156 -0
- data/Rakefile +1 -0
- data/lib/ruote/resque.rb +40 -0
- data/lib/ruote/resque/client.rb +62 -0
- data/lib/ruote/resque/job.rb +47 -0
- data/lib/ruote/resque/participant.rb +86 -0
- data/lib/ruote/resque/participant_registrar.rb +42 -0
- data/lib/ruote/resque/receiver.rb +140 -0
- data/lib/ruote/resque/reply_job.rb +23 -0
- data/lib/ruote/resque/version.rb +7 -0
- data/ruote-resque.gemspec +28 -0
- data/spec/lib/ruote/resque/job_spec.rb +129 -0
- data/spec/lib/ruote/resque/participant_registrar_spec.rb +52 -0
- data/spec/lib/ruote/resque/participant_spec.rb +118 -0
- data/spec/lib/ruote/resque/receiver_spec.rb +208 -0
- data/spec/lib/ruote/resque/reply_job_spec.rb +38 -0
- data/spec/lib/ruote/resque_spec.rb +99 -0
- data/spec/spec_helper.rb +21 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NmE2NDVmYTU0NzNmNWZkZTk5MjgxYWZlOGQ4YTk0MDNjOWFmMDZmNg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
M2M3YTFmMmM2YmQ3NTgwNDg4MDhlMzE2ODNhMDI5YzgyOGI2NDBlMA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YTUwNjUzMzA5N2MzOGYzZjY5NWYwOGZjZTk2YTY5ZjNkM2U1M2E4NGQwMTE4
|
10
|
+
OTQ1YWE0YmJhYTA5ODkyZjEzYmM3Y2ZkY2JjYWRjZmVkMjliNmJkNTMyOGEx
|
11
|
+
OWVkZGYyYTc1ZjA4OWY3YTY0MDQ1MDA1ODU0ODYwOTE2YzU5ZDE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NGZiMTY5MjU1ZmY1NWQ1ZjVlNDYyYjJmNzAyZDFjMDgyY2Y2NTE2YmQ0MWYy
|
14
|
+
NzlhMTU5MDY4NTQ3NzY0ZGYwODViZjFjNTE3YjM3N2QzNmZhODNiYTM4OGY4
|
15
|
+
NWE2ZGRjZDVhMmVmYmI5ZTVjZDQ5MzgxYTVlNmU0NDYwZjIxNTc=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in ruote-resque.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem "ruote", :git => 'git://github.com/jmettraux/ruote.git'
|
7
|
+
gem "json"
|
8
|
+
|
9
|
+
group :test do
|
10
|
+
gem 'rake'
|
11
|
+
gem 'rspec'
|
12
|
+
gem 'coveralls', :require => false
|
13
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Adrien Kohlbecker
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
[](https://travis-ci.org/adrienkohlbecker/ruote-resque) [](https://coveralls.io/r/adrienkohlbecker/ruote-resque) [](https://codeclimate.com/github/adrienkohlbecker/ruote-resque) [](https://gemnasium.com/adrienkohlbecker/ruote-resque)
|
2
|
+
|
3
|
+
# Ruote::Resque
|
4
|
+
|
5
|
+
- **Homepage**: [github.com/adrienkohlbecker/ruote-resque](https://github.com/adrienkohlbecker/ruote-resque)
|
6
|
+
- **Rdoc**: [rdoc.info/gems/ruote-resque](http://rdoc.info/gems/ruote-resque)
|
7
|
+
- **Author**: Adrien Kohlbecker
|
8
|
+
- **License**: MIT License
|
9
|
+
- **Latest Version**: 0.1.0
|
10
|
+
- **Release Date**: June 23, 2013
|
11
|
+
|
12
|
+
## Synopsis
|
13
|
+
|
14
|
+
Ruote::Resque allows a Ruote engine to delegates the work of it's participants to Resque workers.
|
15
|
+
|
16
|
+
Common use cases include:
|
17
|
+
|
18
|
+
- You run a lot of jobs and need the reliability of Resque to process your jobs
|
19
|
+
- You have a Resque system you need to integrate with
|
20
|
+
- You want a separation of concerns between process orchestration and actual execution
|
21
|
+
|
22
|
+
## How it works
|
23
|
+
|
24
|
+
- The library uses a specific Resque queue for message passing.
|
25
|
+
- Participants are empty shells that just queue jobs to Resque, and the job itself replies (via an after_perform hook) with the mutated workitem.
|
26
|
+
- Under the hood, a thread polls the reply queue every five seconds for new replies.
|
27
|
+
- When a reply is received, the process continues with the new workitem.
|
28
|
+
|
29
|
+
Note that:
|
30
|
+
|
31
|
+
- Ruote is not needed as a dependency on the Resque side.
|
32
|
+
- Error handling is supported on both sides (exceptions show up both on the Resque failure backend and in ruote-kit)
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
### Set Up
|
37
|
+
|
38
|
+
#### Inside your Ruote instance
|
39
|
+
|
40
|
+
Add to your Ruote instance gemfile :
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
# The version released on RubyGems is old and not supported.
|
44
|
+
# Note that ruote is not a hard dependency of ruote-resque,
|
45
|
+
# to allow for lightweight use as a client (see below)
|
46
|
+
gem 'ruote', :git => 'git://github.com/jmettraux/ruote.git'
|
47
|
+
gem 'ruote-resque'
|
48
|
+
```
|
49
|
+
|
50
|
+
Then when booting your engine do this :
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require 'ruote/resque'
|
54
|
+
|
55
|
+
# Run the poller thread.
|
56
|
+
Ruote::Resque::Receiver.new(dashboard)
|
57
|
+
```
|
58
|
+
|
59
|
+
#### Inside your Resque worker instance
|
60
|
+
|
61
|
+
Add to your Resque worker instance gemfile :
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
gem 'ruote-resque', :require => false
|
65
|
+
```
|
66
|
+
|
67
|
+
Then when booting your worker do this :
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# You should not require the full library inside your worker, the client will suffice
|
71
|
+
require 'ruote/resque/client'
|
72
|
+
```
|
73
|
+
|
74
|
+
### Participants
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
# Inside your worker, add Ruote::Resque::Job to your jobs
|
78
|
+
class MyAwesomeJob
|
79
|
+
extend Ruote::Resque::Job
|
80
|
+
|
81
|
+
@queue = :my_queue
|
82
|
+
|
83
|
+
def self.perform(workitem)
|
84
|
+
workitem['fields']['be_awesome'] = true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Inside your Ruote instance
|
89
|
+
Ruote::Resque.register(dashboard) do
|
90
|
+
be_awesome 'MyAwesomeJob', :my_queue
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
There are two other ways to register participants:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
Ruote::Resque.register(dashboard) do
|
98
|
+
participant 'be_awesome', 'MyAwesomeJob', :my_queue
|
99
|
+
end
|
100
|
+
|
101
|
+
# OR (not recommended)
|
102
|
+
|
103
|
+
dashboard.register_participant 'be_awesome', Ruote::Resque::Participant, :class => 'MyAwesomeJob', :queue => :my_queue
|
104
|
+
```
|
105
|
+
|
106
|
+
Note that you can pass options to the participant:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
Ruote::Resque.register(dashboard) do
|
110
|
+
be_awesome 'MyAwesomeJob', :my_queue, :forget => true
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
### Configuration
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# Configure the library (optional, default values shown)
|
118
|
+
# You should duplicate this configuration on both
|
119
|
+
# your Ruote instance and your Resque instance
|
120
|
+
Ruote::Resque.configure do |config|
|
121
|
+
config.reply_queue = :ruote_replies
|
122
|
+
config.logger = Logger.new(STDOUT).tap { |log| log.level = Logger::INFO }
|
123
|
+
config.interval = 5
|
124
|
+
end
|
125
|
+
|
126
|
+
# Override the default error handler (optional, but recommended. Default is to log at ERROR level)
|
127
|
+
# Do this in your Ruote instance
|
128
|
+
class Ruote::Resque::Receiver
|
129
|
+
def handle_error(e)
|
130
|
+
MyErrorHandler.handle(e)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
## Requirements
|
136
|
+
|
137
|
+
A functional installation of [Ruote](http://ruote.rubyforge.org) and [Resque](http://github.com/resque/resque) is needed.
|
138
|
+
Note that at the time of writing the release of Ruote on rubygems is very old and not supported. Use it from git.
|
139
|
+
|
140
|
+
ruote-resque has been tested on Ruby 1.8+.
|
141
|
+
|
142
|
+
## Contributing
|
143
|
+
|
144
|
+
1. Fork it
|
145
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
146
|
+
3. Do some changes
|
147
|
+
4. Run the tests (`bundle exec rspec`)
|
148
|
+
5. Commit your changes (`git commit -am 'Add some feature'`)
|
149
|
+
6. Push to the branch (`git push origin my-new-feature`)
|
150
|
+
7. Create new Pull Request
|
151
|
+
|
152
|
+
## Changelog
|
153
|
+
|
154
|
+
- **June 23, 2013**: 0.1.0 release
|
155
|
+
- Initial release
|
156
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/ruote/resque.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'ruote'
|
4
|
+
require 'ruote/resque/client'
|
5
|
+
require 'ruote/resque/participant'
|
6
|
+
require 'ruote/resque/participant_registrar'
|
7
|
+
require 'ruote/resque/receiver'
|
8
|
+
|
9
|
+
module Ruote
|
10
|
+
# Ruote::Resque allows a Ruote engine to delegates the work of it's participants
|
11
|
+
# to Resque workers.
|
12
|
+
#
|
13
|
+
# Common use cases include:
|
14
|
+
#
|
15
|
+
# - You run a lot of jobs and need the reliability of Resque to process your jobs
|
16
|
+
# - You have a Resque system you need to integrate with
|
17
|
+
# - You want a separation of concerns between process orchestration and actual execution
|
18
|
+
#
|
19
|
+
# See the {file:README} for usage instructions
|
20
|
+
module Resque
|
21
|
+
|
22
|
+
# Registers resque participants using a DSL.
|
23
|
+
# @example Using the dsl
|
24
|
+
# Ruote::Resque.register dashboard do
|
25
|
+
# be_awesome MyAwesomeJob, :my_queue
|
26
|
+
# be_really_awesome 'MyReallyAwesomeJob', :my_queue, :forget => true
|
27
|
+
# end
|
28
|
+
# @example Using the participant method
|
29
|
+
# Ruote::Resque.register dashboard do
|
30
|
+
# participant /be_.*/, BeSomething, :my_queue
|
31
|
+
# end
|
32
|
+
# @param [Ruote::Dashboard] dashboard the ruote dashboard
|
33
|
+
# @return [void]
|
34
|
+
def self.register(dashboard, &block)
|
35
|
+
registrar = Ruote::Resque::ParticipantRegistrar.new(dashboard)
|
36
|
+
registrar.instance_eval(&block)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'ruote/resque/job'
|
5
|
+
require 'ruote/resque/reply_job'
|
6
|
+
require 'ruote/resque/version'
|
7
|
+
|
8
|
+
module Ruote
|
9
|
+
module Resque
|
10
|
+
|
11
|
+
# A basic configuration object
|
12
|
+
# @example Setting up ruote-resque (default values shown)
|
13
|
+
# Ruote::Resque.configure do |config|
|
14
|
+
# config.reply_queue = :ruote_replies
|
15
|
+
# config.logger = Logger.new(STDOUT).tap { |log| log.level = Logger::INFO }
|
16
|
+
# config.interval = 5
|
17
|
+
# end
|
18
|
+
class Configuration
|
19
|
+
# The queue used for message passing between Resque jobs and Ruote (defaults to `:ruote_replies`)
|
20
|
+
attr_accessor :reply_queue
|
21
|
+
# The logger used (defaults to STDOUT with log level INFO)
|
22
|
+
attr_accessor :logger
|
23
|
+
# The interval used by {Receiver} when polling Resque
|
24
|
+
attr_accessor :interval
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
|
29
|
+
# Returns the current {Configuration}
|
30
|
+
attr_accessor :configuration
|
31
|
+
|
32
|
+
# Enqueues a ReplyJob with the given arguments.
|
33
|
+
# @return true if the job was queued, nil if the job was rejected by a before_enqueue hook.
|
34
|
+
# @example
|
35
|
+
# Ruote::Resque.reply(workitem)
|
36
|
+
def reply(*args)
|
37
|
+
::Resque.enqueue(Ruote::Resque::ReplyJob, *args)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Logger] the logger to be used inside ruote-resque
|
41
|
+
def logger
|
42
|
+
configuration.logger
|
43
|
+
end
|
44
|
+
|
45
|
+
# This method allows you to customize the ruote-resque configuration.
|
46
|
+
# @see Configuration
|
47
|
+
# @yield [Configuration]
|
48
|
+
# @return [void]
|
49
|
+
def configure
|
50
|
+
self.configuration ||= Ruote::Resque::Configuration.new
|
51
|
+
yield(configuration) if block_given?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# setup default configuration
|
58
|
+
Ruote::Resque.configure do |config|
|
59
|
+
config.reply_queue = :ruote_replies
|
60
|
+
config.logger = Logger.new(STDOUT).tap { |log| log.level = Logger::INFO }
|
61
|
+
config.interval = 5
|
62
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Ruote
|
4
|
+
module Resque
|
5
|
+
|
6
|
+
# Include this module inside your Resque jobs to enable
|
7
|
+
# them to respond to Ruote.
|
8
|
+
#
|
9
|
+
# - Note that the arity should be 1 for the `self.perform` method.
|
10
|
+
# - The workitem will be sent as a Hash. (via `Ruote::Workitem#to_h`)
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# class MyAwesomeJob
|
14
|
+
# extend Ruote::Resque::Job
|
15
|
+
#
|
16
|
+
# def self.perform(workitem)
|
17
|
+
# workitem['fields']['awesome'] = true
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
module Job
|
21
|
+
|
22
|
+
# after_perform hook to send a reply to the Ruote process.
|
23
|
+
# @param [Hash] workitem the workitem sent to the current Job
|
24
|
+
# @return [void]
|
25
|
+
def after_perform_reply_to_ruote(workitem)
|
26
|
+
Ruote::Resque.reply(workitem)
|
27
|
+
end
|
28
|
+
|
29
|
+
# on_failure hook to send a reply to the Ruote process.
|
30
|
+
# Will collect the exception details and send them along.
|
31
|
+
# @param [Exception] exception the raised exception
|
32
|
+
# @param [Hash] workitem the workitem sent to the current Job.
|
33
|
+
# TODO: this may be mutated from the original workitem, handle it.
|
34
|
+
# @return [void]
|
35
|
+
def on_failure_reply_to_ruote(exception, workitem)
|
36
|
+
|
37
|
+
klass = exception.class.to_s
|
38
|
+
message = exception.message
|
39
|
+
backtrace = exception.backtrace
|
40
|
+
|
41
|
+
Ruote::Resque.reply(klass, message, backtrace, workitem)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# A module to be included in your Resque jobs
|
4
|
+
# to be able to register them as participants.
|
5
|
+
#
|
6
|
+
# Note that the participant should have a queue, either via the `@queue` variable
|
7
|
+
# when the class is accessible to Ruote, or via the `:queue` option on registration
|
8
|
+
#
|
9
|
+
# @example Register a participant
|
10
|
+
# # This is defined on a remote Resque worker
|
11
|
+
# # You do not need to extend Participant in this case
|
12
|
+
# class MyAwesomeJob
|
13
|
+
# extend Ruote::Resque::Job
|
14
|
+
# @queue = :my_queue
|
15
|
+
#
|
16
|
+
# def self.perform(workitem)
|
17
|
+
# workitem['fields']['awesome'] = true
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # Use it like this in your Ruote process
|
22
|
+
# engine.register_participant 'be_awesome', Ruote::Resque::Participant, :class => 'MyAwesomeJob', :queue => :my_queue
|
23
|
+
# # Or register it va the DSL
|
24
|
+
# Ruote::Resque.register(dashboard) do
|
25
|
+
# be_awesome 'MyAwesomeJob', :my_queue
|
26
|
+
# end
|
27
|
+
# A resque participant implementation.
|
28
|
+
class Ruote::Resque::Participant
|
29
|
+
include Ruote::LocalParticipant
|
30
|
+
|
31
|
+
# Called with the options on `engine.register_participant`
|
32
|
+
# @param [Hash] opts
|
33
|
+
# @option opts [#to_s] :class (self.class) the job class to enqueue when called
|
34
|
+
# @option opts [#to_s] :queue (Resque.queue_from_class(class)) the queue to enqueue the job in
|
35
|
+
# @option opts [Boolean] :forget (false) wait for the worker's reply if false
|
36
|
+
def initialize(opts = {})
|
37
|
+
|
38
|
+
@job_klass = opts.delete('class')
|
39
|
+
@job_queue = opts.delete('queue')
|
40
|
+
@should_forget = opts.delete('forget') || false
|
41
|
+
|
42
|
+
# Called here to raise eventual exceptions on initialization
|
43
|
+
::Resque.validate(@job_klass, @job_queue)
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
# Called when the participant is handed a workitem.
|
48
|
+
# Enqueues the job to Resque
|
49
|
+
# @return [void]
|
50
|
+
def on_workitem
|
51
|
+
|
52
|
+
payload = encode_workitem(workitem)
|
53
|
+
::Resque::Job.create(@job_queue, @job_klass, payload)
|
54
|
+
|
55
|
+
reply if @should_forget
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
# Called when Ruote has to cancel an active workitem for this participant.
|
60
|
+
# Destroys the job from the Resque queue.
|
61
|
+
#
|
62
|
+
# Note that if the job is being processed by the worker or if the job has been processed but the reply has not,
|
63
|
+
# this method will do nothing.
|
64
|
+
# @return [Boolean] wether the job was deleted or not.
|
65
|
+
def on_cancel
|
66
|
+
|
67
|
+
payload = encode_workitem(applied_workitem)
|
68
|
+
::Resque::Job.destroy(@job_queue, @job_klass, payload)
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns a representation of a workitem that is suitable for use in Resque.
|
73
|
+
# @param [Ruote::Workitem] workitem
|
74
|
+
# @return [Hash] the workitem as a hash
|
75
|
+
def encode_workitem(workitem)
|
76
|
+
|
77
|
+
workitem.to_h
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns true because enqueing a job in Resque is sufficiently fast to happen in the main thread.
|
82
|
+
# @return [true]
|
83
|
+
def do_not_thread
|
84
|
+
true
|
85
|
+
end
|
86
|
+
end
|