qujo 0.1.2 → 0.1.3
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/README.md +265 -0
- data/Rakefile +1 -1
- data/app/assets/stylesheets/qujo/qujo.css.scss +15 -0
- data/lib/qujo/concerns/common.rb +0 -32
- data/lib/qujo/version.rb +1 -1
- metadata +6 -5
- data/README.rdoc +0 -3
data/README.md
ADDED
@@ -0,0 +1,265 @@
|
|
1
|
+
# Qujo
|
2
|
+
|
3
|
+
The basic idea behind Qujo came from a simple idea:
|
4
|
+
|
5
|
+
> Store information about jobs in my application domain database, rather than in Redis
|
6
|
+
|
7
|
+
Qujo wraps a Resque worker with a model class and some basic intelligence around
|
8
|
+
executing jobs and storing their output. It also provides a logger for Jobs, retry
|
9
|
+
functionality, Rails integration, Bootstrap integration, polling javascript for
|
10
|
+
displaying the running jobs and workers in a page within your app.
|
11
|
+
|
12
|
+
## Databases
|
13
|
+
|
14
|
+
Currently, Qujo only works for Mongoid. It doesn't lend well to ActiveRecord-based
|
15
|
+
models, as it talks to the database a lot (probably more than it should).
|
16
|
+
|
17
|
+
## Queue support
|
18
|
+
|
19
|
+
I've started some work seeing if I can get Qujo to work with Sidekiq, but I'm not sure
|
20
|
+
it's worth the effort. Sidekiq stands much better on it's own, having solved some of the
|
21
|
+
problems that I experienced with Resque. Though you still have to look somewhere
|
22
|
+
else for errors and stack traces.
|
23
|
+
|
24
|
+
## Setup
|
25
|
+
|
26
|
+
Add Qujo to your Gemfile
|
27
|
+
|
28
|
+
```
|
29
|
+
gem 'qujo'
|
30
|
+
```
|
31
|
+
|
32
|
+
### Create an initializer for Rails
|
33
|
+
|
34
|
+
```Ruby
|
35
|
+
# config/initializers/qujo.rb
|
36
|
+
require 'qujo/queue/resque' # we're using Resque
|
37
|
+
require 'qujo/database/mongoid' # we're using Mongoid
|
38
|
+
|
39
|
+
Qujo.configure do |config|
|
40
|
+
# use Yell for our logger
|
41
|
+
config.logger = Yell.new do |l|
|
42
|
+
l.level = [:info, :warn, :error, :fatal]
|
43
|
+
l.adapter :file, File.join(Rails.root, "log", "qujo.log")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
### Create a Job model
|
49
|
+
|
50
|
+
The following Job model is the parent of all Jobs that do the actual work.
|
51
|
+
|
52
|
+
```Ruby
|
53
|
+
# app/models/job.rb
|
54
|
+
class Job
|
55
|
+
include Qujo::Database::Mongoid
|
56
|
+
|
57
|
+
include Qujo::Queue::Resque
|
58
|
+
|
59
|
+
include Qujo::Concerns::Common
|
60
|
+
include Qujo::Concerns::Logging
|
61
|
+
include Qujo::Concerns::Status
|
62
|
+
|
63
|
+
# any custom functionality shared for all jobs
|
64
|
+
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
### Mount the Qujo engine
|
69
|
+
|
70
|
+
```Ruby
|
71
|
+
# config/routes.rb
|
72
|
+
MyApp::Application.routes.draw do
|
73
|
+
# ... normal routes
|
74
|
+
|
75
|
+
mount Qujo::Engine => "/"
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
After this is configured, you can view the jobs at `localhost:3000/jobs`.
|
80
|
+
|
81
|
+
### Bootstrap
|
82
|
+
|
83
|
+
If you're using bootstrap, there is a Qujo template to integrate some status
|
84
|
+
information into your bootstrap navigation.
|
85
|
+
|
86
|
+
```
|
87
|
+
<header class="navbar navbar-fixed-top">
|
88
|
+
<nav class="navbar-inner">
|
89
|
+
<div class="container">
|
90
|
+
<!-- ... -->
|
91
|
+
<ul class="nav pull-right">
|
92
|
+
<!-- ... -->
|
93
|
+
<li>
|
94
|
+
<%= render 'qujo/common/bootstrap', jobs: {header: ["Jobs::Class::One", "Jobs::Class::Two"], background: ["Jobs::Class::Three"]} %>
|
95
|
+
</li>
|
96
|
+
</ul>
|
97
|
+
<!-- ... -->
|
98
|
+
|
99
|
+
```
|
100
|
+
|
101
|
+
The data you pass to jobs should be a hash of keys and arrays. The keys are the header names, the arrays are a list
|
102
|
+
of strings of Job class names.
|
103
|
+
|
104
|
+
When this data is configured, you can kick off jobs from the UI, just by clicking on their link in the dropdown.
|
105
|
+
|
106
|
+
Color codes:
|
107
|
+
|
108
|
+
* black
|
109
|
+
|
110
|
+
> making request to server (normally it will blink black every 2 seconds)
|
111
|
+
|
112
|
+
* gray
|
113
|
+
|
114
|
+
> normal - things are good
|
115
|
+
|
116
|
+
* red
|
117
|
+
|
118
|
+
> errors - job with error, resque has failed jobs, or can't talk to resque (workers aren't running)
|
119
|
+
|
120
|
+
* orange
|
121
|
+
|
122
|
+
> after 10 minutes, the page will stop polling, just to lower the traffic to the server and not fill logs with polling
|
123
|
+
|
124
|
+
### Create your first Job
|
125
|
+
|
126
|
+
This job inherits from the previous Job parent class and defines a work method.
|
127
|
+
|
128
|
+
```Ruby
|
129
|
+
# app/models/jobs/hello/world.rb
|
130
|
+
class Jobs::Hello::World < Job
|
131
|
+
# the work method is where the work goes
|
132
|
+
def work
|
133
|
+
info "Hello world!"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
### Integrate with your other models
|
139
|
+
|
140
|
+
Imagine that you have a model in your app that tracks purchases. You want to let
|
141
|
+
the customer know that the purchase has succeeded.
|
142
|
+
|
143
|
+
Create a Job class named `Jobs::Purchase::Notify`
|
144
|
+
|
145
|
+
```Ruby
|
146
|
+
# app/models/jobs/purchase/notify.rb
|
147
|
+
class Jobs::Purchase::Notify < Job
|
148
|
+
def work
|
149
|
+
# notify the customer
|
150
|
+
purchase = model # model is a helper to grab the associated model
|
151
|
+
purchase.do_notify
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
Now if you want to queue that job to run, all you need to do (from the controller, or another job)
|
157
|
+
|
158
|
+
```Ruby
|
159
|
+
purchase.enqueue(:notify, optionshash)
|
160
|
+
```
|
161
|
+
|
162
|
+
Qujo will take care of the rest.
|
163
|
+
|
164
|
+
How Qujo does this, it figures out the job class like so: `Jobs::<Model>::<Action>`
|
165
|
+
|
166
|
+
In the above example, `notify` is the action, so it translates this to `Jobs::Purchase::Notify`,
|
167
|
+
creates an instance, stores it in the database and tells Resque to enqueue it.
|
168
|
+
|
169
|
+
### Passing options to jobs
|
170
|
+
|
171
|
+
```Ruby
|
172
|
+
purchase.enqueue(:notify, optionshash)
|
173
|
+
```
|
174
|
+
|
175
|
+
In the above, `optionshash` is just a hash of any data that you want to pass into the Job.
|
176
|
+
It is accessible from within the job with the `data` helper.
|
177
|
+
|
178
|
+
Keep in mind, because of the way that Resque stores (marshalls and unmarshalls) data in
|
179
|
+
Redis, all keys will be converted to strings.
|
180
|
+
|
181
|
+
```Ruby
|
182
|
+
# either
|
183
|
+
purchase.enqueue(:notify, :blarg => true)
|
184
|
+
|
185
|
+
# or this
|
186
|
+
purchase.enqueue(:notify, blarg: true)
|
187
|
+
|
188
|
+
# or this
|
189
|
+
purchase.enqueue(:notify, {"blarg" => true})
|
190
|
+
# or any other hash
|
191
|
+
|
192
|
+
# will result in:
|
193
|
+
data = {
|
194
|
+
"blarg" => true
|
195
|
+
}
|
196
|
+
```
|
197
|
+
|
198
|
+
### The views
|
199
|
+
|
200
|
+
By default, Qujo provides some views and routes to handle querying the job models.
|
201
|
+
|
202
|
+
## Some of the other things that Qujo provides
|
203
|
+
|
204
|
+
### Wait
|
205
|
+
|
206
|
+
A method to have a job wait while the return value is true. I've considered inversing this,
|
207
|
+
as it probably makes more sense to wait while false.
|
208
|
+
|
209
|
+
```Ruby
|
210
|
+
# app/models/jobs/purchase/notify.rb
|
211
|
+
class Jobs::Purchase::Notify < Job
|
212
|
+
def work
|
213
|
+
# notify the customer
|
214
|
+
purchase = model # model is a helper to grab the associated model
|
215
|
+
|
216
|
+
# this could potentially be calling another job that sets
|
217
|
+
# email#sent to true once it has completed successfully
|
218
|
+
email = purchase.do_notify
|
219
|
+
|
220
|
+
# email#sent returns true once the email has been delivered
|
221
|
+
wait do
|
222
|
+
! email.sent
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
```
|
227
|
+
|
228
|
+
The `wait` method supports a few options
|
229
|
+
|
230
|
+
```Yaml
|
231
|
+
interval: integer - the length of time to sleep in seconds for each loop (default: 3)
|
232
|
+
maximum: integer - the maximum time in seconds to wait (default: 600)
|
233
|
+
```
|
234
|
+
|
235
|
+
Keep in mind, that right now jobs are not re-entrant, so when a job is waiting, the worker
|
236
|
+
is doing nothing. If you have a lot of jobs that wait, you could end up having all of your
|
237
|
+
workers waiting for something to complete.
|
238
|
+
|
239
|
+
I have a plan to change this functionality to work around the re-entrancy problem.
|
240
|
+
|
241
|
+
### Parent jobs
|
242
|
+
|
243
|
+
Qujo can also store parent jobs, when jobs need to run a set of things in order. The API for
|
244
|
+
this is still being worked out.
|
245
|
+
|
246
|
+
### Workflow
|
247
|
+
|
248
|
+
Some basic workflow handling. A job transitions through the following states:
|
249
|
+
|
250
|
+
```
|
251
|
+
new -> working -> complete
|
252
|
+
-> error -> retry => working ...
|
253
|
+
```
|
254
|
+
|
255
|
+
## TODO
|
256
|
+
|
257
|
+
[ ] Smarter workflows, use `workflow` gem
|
258
|
+
[ ] Job re-entrancy.
|
259
|
+
When a job waits, just schedule it 5 seconds from now and pick up where it left off.
|
260
|
+
[ ] Sidekiq support?
|
261
|
+
[ ] ActiveRecord support?
|
262
|
+
|
263
|
+
## License
|
264
|
+
|
265
|
+
MIT-LICENSE
|
data/Rakefile
CHANGED
data/lib/qujo/concerns/common.rb
CHANGED
@@ -5,12 +5,8 @@ module Qujo
|
|
5
5
|
|
6
6
|
included do
|
7
7
|
extend ClassMethods
|
8
|
-
attr_accessor :model, :parent
|
9
8
|
|
10
9
|
def run
|
11
|
-
#self.load_model
|
12
|
-
#self.load_parent
|
13
|
-
|
14
10
|
self.status = :working
|
15
11
|
self.save!
|
16
12
|
|
@@ -43,34 +39,6 @@ module Qujo
|
|
43
39
|
raise "wait timeout count=#{count} interval=#{interval} maximum=#{maximum}" if ((count * interval) >= maximum)
|
44
40
|
end
|
45
41
|
|
46
|
-
#def model
|
47
|
-
# @model ||= begin
|
48
|
-
# if data && data["id"] && data["class"]
|
49
|
-
# begin
|
50
|
-
# c = data["class"].constantize
|
51
|
-
# c.find(data["id"]) # rescue nil #TODO: make this smarter
|
52
|
-
# #rescue Mongoid::Errors::DocumentNotFound => e
|
53
|
-
# # logger.error "document not found"
|
54
|
-
# # nil
|
55
|
-
# end
|
56
|
-
# end
|
57
|
-
# end
|
58
|
-
#end
|
59
|
-
#
|
60
|
-
#def parent
|
61
|
-
# @parent ||= begin
|
62
|
-
# if data["parent"] && data["parent"]["type"] && data["parent"]["id"]
|
63
|
-
# begin
|
64
|
-
# c = data["parent"]["type"].constantize
|
65
|
-
# c.find(data["parent"]["id"])
|
66
|
-
# #rescue Mongoid::Errors::DocumentNotFound => e
|
67
|
-
# # logger.error "document not found"
|
68
|
-
# # nil
|
69
|
-
# end
|
70
|
-
# end
|
71
|
-
# end
|
72
|
-
#end
|
73
|
-
|
74
42
|
def model
|
75
43
|
@model ||= begin
|
76
44
|
if data && data["model"] && data["model"]["id"] && data["model"]["class"]
|
data/lib/qujo/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qujo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- app/helpers/qujo/application_helper.rb
|
79
79
|
- app/assets/javascripts/qujo/application.js
|
80
80
|
- app/assets/javascripts/qujo/qujo.js
|
81
|
+
- app/assets/stylesheets/qujo/qujo.css.scss
|
81
82
|
- app/assets/stylesheets/qujo/application.css
|
82
83
|
- config/routes.rb
|
83
84
|
- lib/qujo/queue/resque.rb
|
@@ -95,7 +96,7 @@ files:
|
|
95
96
|
- lib/tasks/qujo_tasks.rake
|
96
97
|
- MIT-LICENSE
|
97
98
|
- Rakefile
|
98
|
-
- README.
|
99
|
+
- README.md
|
99
100
|
- test/qujo_test.rb
|
100
101
|
- test/dummy/config/locales/en.yml
|
101
102
|
- test/dummy/config/boot.rb
|
@@ -140,7 +141,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
140
141
|
version: '0'
|
141
142
|
segments:
|
142
143
|
- 0
|
143
|
-
hash:
|
144
|
+
hash: 3602785166820259427
|
144
145
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
146
|
none: false
|
146
147
|
requirements:
|
@@ -149,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
150
|
version: '0'
|
150
151
|
segments:
|
151
152
|
- 0
|
152
|
-
hash:
|
153
|
+
hash: 3602785166820259427
|
153
154
|
requirements: []
|
154
155
|
rubyforge_project:
|
155
156
|
rubygems_version: 1.8.25
|
data/README.rdoc
DELETED