qs 0.3.0 → 0.4.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/bench/config.qs +4 -27
- data/bench/dispatcher.qs +24 -0
- data/bench/report.rb +80 -10
- data/bench/report.txt +10 -3
- data/bench/setup.rb +55 -0
- data/lib/qs.rb +75 -15
- data/lib/qs/client.rb +73 -22
- data/lib/qs/daemon.rb +21 -21
- data/lib/qs/daemon_data.rb +4 -4
- data/lib/qs/dispatch_job.rb +36 -0
- data/lib/qs/dispatch_job_handler.rb +79 -0
- data/lib/qs/dispatcher_queue.rb +19 -0
- data/lib/qs/error_handler.rb +12 -12
- data/lib/qs/event.rb +82 -0
- data/lib/qs/event_handler.rb +34 -0
- data/lib/qs/event_handler_test_helpers.rb +17 -0
- data/lib/qs/job.rb +19 -31
- data/lib/qs/job_handler.rb +6 -63
- data/lib/qs/{test_helpers.rb → job_handler_test_helpers.rb} +2 -2
- data/lib/qs/message.rb +29 -0
- data/lib/qs/message_handler.rb +84 -0
- data/lib/qs/payload.rb +98 -0
- data/lib/qs/payload_handler.rb +106 -54
- data/lib/qs/queue.rb +39 -6
- data/lib/qs/queue_item.rb +33 -0
- data/lib/qs/route.rb +7 -7
- data/lib/qs/runner.rb +6 -5
- data/lib/qs/test_runner.rb +41 -13
- data/lib/qs/version.rb +1 -1
- data/qs.gemspec +1 -1
- data/test/helper.rb +1 -1
- data/test/support/app_daemon.rb +77 -11
- data/test/support/factory.rb +34 -0
- data/test/system/daemon_tests.rb +146 -77
- data/test/system/queue_tests.rb +87 -0
- data/test/unit/client_tests.rb +184 -45
- data/test/unit/daemon_data_tests.rb +4 -4
- data/test/unit/daemon_tests.rb +32 -32
- data/test/unit/dispatch_job_handler_tests.rb +163 -0
- data/test/unit/dispatch_job_tests.rb +75 -0
- data/test/unit/dispatcher_queue_tests.rb +42 -0
- data/test/unit/error_handler_tests.rb +9 -9
- data/test/unit/event_handler_test_helpers_tests.rb +55 -0
- data/test/unit/event_handler_tests.rb +63 -0
- data/test/unit/event_tests.rb +162 -0
- data/test/unit/{test_helper_tests.rb → job_handler_test_helper_tests.rb} +13 -19
- data/test/unit/job_handler_tests.rb +17 -210
- data/test/unit/job_tests.rb +49 -79
- data/test/unit/message_handler_tests.rb +235 -0
- data/test/unit/message_tests.rb +64 -0
- data/test/unit/payload_handler_tests.rb +285 -86
- data/test/unit/payload_tests.rb +139 -0
- data/test/unit/qs_runner_tests.rb +6 -6
- data/test/unit/qs_tests.rb +167 -28
- data/test/unit/queue_item_tests.rb +51 -0
- data/test/unit/queue_tests.rb +126 -18
- data/test/unit/route_tests.rb +12 -13
- data/test/unit/runner_tests.rb +10 -10
- data/test/unit/test_runner_tests.rb +117 -24
- metadata +51 -21
- data/bench/queue.rb +0 -8
- data/lib/qs/redis_item.rb +0 -33
- data/test/unit/redis_item_tests.rb +0 -49
data/lib/qs/daemon.rb
CHANGED
@@ -9,7 +9,7 @@ require 'qs/daemon_data'
|
|
9
9
|
require 'qs/io_pipe'
|
10
10
|
require 'qs/logger'
|
11
11
|
require 'qs/payload_handler'
|
12
|
-
require 'qs/
|
12
|
+
require 'qs/queue_item'
|
13
13
|
|
14
14
|
module Qs
|
15
15
|
|
@@ -33,7 +33,7 @@ module Qs
|
|
33
33
|
|
34
34
|
# * Set the size of the client to the max workers + 1. This ensures we
|
35
35
|
# have 1 connection for fetching work from redis and at least 1
|
36
|
-
# connection for each worker to requeue its
|
36
|
+
# connection for each worker to requeue its message when hard-shutdown.
|
37
37
|
def initialize
|
38
38
|
self.class.configuration.validate!
|
39
39
|
Qs.init
|
@@ -96,8 +96,8 @@ module Qs
|
|
96
96
|
|
97
97
|
private
|
98
98
|
|
99
|
-
def process(
|
100
|
-
Qs::PayloadHandler.new(self.daemon_data,
|
99
|
+
def process(queue_item)
|
100
|
+
Qs::PayloadHandler.new(self.daemon_data, queue_item).run
|
101
101
|
end
|
102
102
|
|
103
103
|
def work_loop
|
@@ -128,9 +128,9 @@ module Qs
|
|
128
128
|
wp = DatWorkerPool.new(
|
129
129
|
self.daemon_data.min_workers,
|
130
130
|
self.daemon_data.max_workers
|
131
|
-
){ |
|
132
|
-
wp.on_worker_error do |worker, exception,
|
133
|
-
handle_worker_exception(exception,
|
131
|
+
){ |queue_item| process(queue_item) }
|
132
|
+
wp.on_worker_error do |worker, exception, queue_item|
|
133
|
+
handle_worker_exception(exception, queue_item)
|
134
134
|
end
|
135
135
|
wp.on_worker_sleep{ @worker_available_io.write(SIGNAL) }
|
136
136
|
wp.start
|
@@ -138,8 +138,8 @@ module Qs
|
|
138
138
|
end
|
139
139
|
|
140
140
|
# * Shuffle the queue redis keys to avoid queue starvation. Redis will
|
141
|
-
# pull
|
142
|
-
# shuffling we ensure they are randomly ordered so every queue should
|
141
|
+
# pull messages off queues in the order they are passed to the command,
|
142
|
+
# by shuffling we ensure they are randomly ordered so every queue should
|
143
143
|
# get a chance.
|
144
144
|
# * Use 0 for the brpop timeout which means block indefinitely.
|
145
145
|
# * Rescue runtime errors so the daemon thread doesn't fail if redis is
|
@@ -151,9 +151,9 @@ module Qs
|
|
151
151
|
|
152
152
|
begin
|
153
153
|
args = [self.signals_redis_key, self.queue_redis_keys.shuffle, 0].flatten
|
154
|
-
redis_key,
|
154
|
+
redis_key, encoded_payload = @client.block_dequeue(*args)
|
155
155
|
if redis_key != @signals_redis_key
|
156
|
-
@worker_pool.add_work(
|
156
|
+
@worker_pool.add_work(QueueItem.new(redis_key, encoded_payload))
|
157
157
|
end
|
158
158
|
rescue RuntimeError => exception
|
159
159
|
log "Error dequeueing #{exception.message.inspect}", :error
|
@@ -178,9 +178,9 @@ module Qs
|
|
178
178
|
log "Shutting down, waiting for work to finish"
|
179
179
|
end
|
180
180
|
@worker_pool.shutdown(timeout)
|
181
|
-
log "Requeueing #{@worker_pool.work_items.size}
|
181
|
+
log "Requeueing #{@worker_pool.work_items.size} message(s)"
|
182
182
|
@worker_pool.work_items.each do |ri|
|
183
|
-
@client.prepend(ri.queue_redis_key, ri.
|
183
|
+
@client.prepend(ri.queue_redis_key, ri.encoded_payload)
|
184
184
|
end
|
185
185
|
end
|
186
186
|
|
@@ -196,18 +196,18 @@ module Qs
|
|
196
196
|
# * This only catches errors that happen outside of running the payload
|
197
197
|
# handler. The only known use-case for this is dat worker pools
|
198
198
|
# hard-shutdown errors.
|
199
|
-
# * If there isn't a
|
199
|
+
# * If there isn't a queue item (this can happen when an idle worker is
|
200
200
|
# being forced to exit) then we don't need to do anything.
|
201
|
-
# * If we never started processing the
|
201
|
+
# * If we never started processing the queue item, its safe to requeue it.
|
202
202
|
# Otherwise it happened while processing so the payload handler caught
|
203
203
|
# it or it happened after the payload handler which we don't care about.
|
204
|
-
def handle_worker_exception(exception,
|
205
|
-
return if
|
206
|
-
if !
|
207
|
-
log "Worker error, requeueing
|
208
|
-
@client.prepend(
|
204
|
+
def handle_worker_exception(exception, queue_item)
|
205
|
+
return if queue_item.nil?
|
206
|
+
if !queue_item.started
|
207
|
+
log "Worker error, requeueing message because it hasn't started", :error
|
208
|
+
@client.prepend(queue_item.queue_redis_key, queue_item.encoded_payload)
|
209
209
|
else
|
210
|
-
log "Worker error after
|
210
|
+
log "Worker error after message was processed, ignoring", :error
|
211
211
|
end
|
212
212
|
log "#{exception.class}: #{exception.message}", :error
|
213
213
|
log exception.backtrace.join("\n"), :error
|
data/lib/qs/daemon_data.rb
CHANGED
@@ -5,7 +5,7 @@ module Qs
|
|
5
5
|
# The daemon uses this to "compile" its configuration for speed. NsOptions
|
6
6
|
# is relatively slow everytime an option is read. To avoid this, we read the
|
7
7
|
# options one time here and memoize their values. This way, we don't pay the
|
8
|
-
# NsOptions overhead when reading them while handling a
|
8
|
+
# NsOptions overhead when reading them while handling a message.
|
9
9
|
|
10
10
|
attr_reader :name
|
11
11
|
attr_reader :pid_file
|
@@ -29,14 +29,14 @@ module Qs
|
|
29
29
|
@routes = build_routes(args[:routes] || [])
|
30
30
|
end
|
31
31
|
|
32
|
-
def route_for(
|
33
|
-
@routes[
|
32
|
+
def route_for(route_id)
|
33
|
+
@routes[route_id] || raise(NotFoundError, "unknown message '#{route_id}'")
|
34
34
|
end
|
35
35
|
|
36
36
|
private
|
37
37
|
|
38
38
|
def build_routes(routes)
|
39
|
-
routes.inject({}){ |h, route| h.merge(route.
|
39
|
+
routes.inject({}){ |h, route| h.merge(route.id => route) }
|
40
40
|
end
|
41
41
|
|
42
42
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'qs'
|
2
|
+
require 'qs/event'
|
3
|
+
require 'qs/job'
|
4
|
+
|
5
|
+
module Qs
|
6
|
+
|
7
|
+
class DispatchJob < Qs::Job
|
8
|
+
|
9
|
+
def self.event(job)
|
10
|
+
Qs::Event.new(job.params['event_channel'], job.params['event_name'], {
|
11
|
+
:params => job.params['event_params'],
|
12
|
+
:publisher => job.params['event_publisher'],
|
13
|
+
:published_at => job.created_at
|
14
|
+
})
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(event_channel, event_name, options = nil)
|
18
|
+
options ||= {}
|
19
|
+
event_params = options.delete(:event_params) || {}
|
20
|
+
event_publisher = options.delete(:event_publisher) || Qs.event_publisher
|
21
|
+
options[:params] = {
|
22
|
+
'event_channel' => event_channel,
|
23
|
+
'event_name' => event_name,
|
24
|
+
'event_params' => event_params,
|
25
|
+
'event_publisher' => event_publisher
|
26
|
+
}
|
27
|
+
super(Qs.dispatcher_job_name, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def event
|
31
|
+
@event ||= self.class.event(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'qs'
|
2
|
+
require 'qs/dispatch_job'
|
3
|
+
require 'qs/job_handler'
|
4
|
+
|
5
|
+
module Qs
|
6
|
+
|
7
|
+
module DispatchJobHandler
|
8
|
+
|
9
|
+
def self.included(klass)
|
10
|
+
klass.class_eval do
|
11
|
+
include Qs::JobHandler
|
12
|
+
include InstanceMethods
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module InstanceMethods
|
17
|
+
|
18
|
+
attr_reader :event, :subscribed_queue_names
|
19
|
+
|
20
|
+
def init!
|
21
|
+
@event = Qs::DispatchJob.event(job)
|
22
|
+
@subscribed_queue_names = Qs.event_subscribers(@event)
|
23
|
+
@qs_failed_dispatches = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def run!
|
27
|
+
logger.info "Dispatching #{self.event.route_name}"
|
28
|
+
logger.info " params: #{self.event.params.inspect}"
|
29
|
+
logger.info " publisher: #{self.event.publisher}"
|
30
|
+
logger.info " published at: #{self.event.published_at}"
|
31
|
+
logger.info "Found #{self.subscribed_queue_names.size} subscribed queue(s):"
|
32
|
+
self.subscribed_queue_names.each do |queue_name|
|
33
|
+
qs_dispatch(queue_name, self.event)
|
34
|
+
end
|
35
|
+
qs_handle_errors(self.event, @qs_failed_dispatches)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def qs_dispatch(queue_name, event)
|
41
|
+
Qs.push(queue_name, Qs::Payload.event_hash(event))
|
42
|
+
logger.info " => #{queue_name}"
|
43
|
+
rescue StandardError => exception
|
44
|
+
logger.info " => #{queue_name} (failed)"
|
45
|
+
@qs_failed_dispatches << FailedDispatch.new(queue_name, exception)
|
46
|
+
end
|
47
|
+
|
48
|
+
def qs_handle_errors(event, failed_dispatches)
|
49
|
+
return if failed_dispatches.empty?
|
50
|
+
logger.info "Failed to dispatch the event to " \
|
51
|
+
"#{failed_dispatches.size} subscribed queues"
|
52
|
+
descriptions = failed_dispatches.map do |fail|
|
53
|
+
exception_desc = "#{fail.exception.class}: #{fail.exception.message}"
|
54
|
+
logger.info "#{fail.queue_name}"
|
55
|
+
logger.info " #{exception_desc}"
|
56
|
+
logger.info " #{fail.exception.backtrace.first}"
|
57
|
+
"#{fail.queue_name} - #{exception_desc}"
|
58
|
+
end
|
59
|
+
message = "#{event.route_name} event wasn't dispatched to:\n" \
|
60
|
+
" #{descriptions.join("\n ")}"
|
61
|
+
raise DispatchError.new(message, failed_dispatches)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
FailedDispatch = Struct.new(:queue_name, :exception)
|
67
|
+
|
68
|
+
class DispatchError < RuntimeError
|
69
|
+
attr_reader :failed_dispatches
|
70
|
+
|
71
|
+
def initialize(message, failed_dispatches)
|
72
|
+
super message
|
73
|
+
@failed_dispatches = failed_dispatches
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'qs/queue'
|
2
|
+
require 'qs/dispatch_job_handler'
|
3
|
+
|
4
|
+
module Qs
|
5
|
+
|
6
|
+
module DispatcherQueue
|
7
|
+
|
8
|
+
def self.new(options)
|
9
|
+
options[:queue_class].new do
|
10
|
+
name options[:queue_name]
|
11
|
+
job options[:job_name], options[:job_handler_class_name]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
RunDispatchJob = Class.new{ include Qs::DispatchJobHandler }
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/qs/error_handler.rb
CHANGED
@@ -31,24 +31,24 @@ module Qs
|
|
31
31
|
|
32
32
|
class ErrorContext
|
33
33
|
attr_reader :daemon_data
|
34
|
-
attr_reader :queue_name, :
|
35
|
-
attr_reader :
|
34
|
+
attr_reader :queue_name, :encoded_payload
|
35
|
+
attr_reader :message, :handler_class
|
36
36
|
|
37
37
|
def initialize(args)
|
38
|
-
@daemon_data
|
39
|
-
@queue_name
|
40
|
-
@
|
41
|
-
@
|
42
|
-
@handler_class
|
38
|
+
@daemon_data = args[:daemon_data]
|
39
|
+
@queue_name = Queue::RedisKey.parse_name(args[:queue_redis_key].to_s)
|
40
|
+
@encoded_payload = args[:encoded_payload]
|
41
|
+
@message = args[:message]
|
42
|
+
@handler_class = args[:handler_class]
|
43
43
|
end
|
44
44
|
|
45
45
|
def ==(other)
|
46
46
|
if other.kind_of?(self.class)
|
47
|
-
self.daemon_data
|
48
|
-
self.queue_name
|
49
|
-
self.
|
50
|
-
self.
|
51
|
-
self.handler_class
|
47
|
+
self.daemon_data == other.daemon_data &&
|
48
|
+
self.queue_name == other.queue_name &&
|
49
|
+
self.encoded_payload == other.encoded_payload &&
|
50
|
+
self.message == other.message &&
|
51
|
+
self.handler_class == other.handler_class
|
52
52
|
else
|
53
53
|
super
|
54
54
|
end
|
data/lib/qs/event.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'qs/message'
|
2
|
+
|
3
|
+
module Qs
|
4
|
+
|
5
|
+
class Event < Message
|
6
|
+
|
7
|
+
PAYLOAD_TYPE = 'event'
|
8
|
+
|
9
|
+
attr_reader :channel, :name, :publisher, :published_at
|
10
|
+
|
11
|
+
def initialize(channel, name, options = nil)
|
12
|
+
options ||= {}
|
13
|
+
options[:params] ||= {}
|
14
|
+
validate!(channel, name, options[:params])
|
15
|
+
@channel = channel
|
16
|
+
@name = name
|
17
|
+
@publisher = options[:publisher]
|
18
|
+
@published_at = options[:published_at] || Time.now
|
19
|
+
super(PAYLOAD_TYPE, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def route_name
|
23
|
+
@route_name ||= Event::RouteName.new(self.channel, self.name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def subscribers_redis_key
|
27
|
+
@subscribers_redis_key ||= SubscribersRedisKey.new(self.route_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect
|
31
|
+
reference = '0x0%x' % (self.object_id << 1)
|
32
|
+
"#<#{self.class}:#{reference} " \
|
33
|
+
"@channel=#{self.channel.inspect} " \
|
34
|
+
"@name=#{self.name.inspect} " \
|
35
|
+
"@params=#{self.params.inspect} " \
|
36
|
+
"@publisher=#{self.publisher.inspect} " \
|
37
|
+
"@published_at=#{self.published_at.inspect}>"
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(other)
|
41
|
+
if other.kind_of?(self.class)
|
42
|
+
self.payload_type == other.payload_type &&
|
43
|
+
self.channel == other.channel &&
|
44
|
+
self.name == other.name &&
|
45
|
+
self.params == other.params &&
|
46
|
+
self.publisher == other.publisher &&
|
47
|
+
self.published_at == other.published_at
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def validate!(channel, name, params)
|
56
|
+
problem = if channel.to_s.empty?
|
57
|
+
"The event doesn't have a channel."
|
58
|
+
elsif name.to_s.empty?
|
59
|
+
"The event doesn't have a name."
|
60
|
+
elsif !params.kind_of?(::Hash)
|
61
|
+
"The event's params are not valid."
|
62
|
+
end
|
63
|
+
raise(InvalidError, problem) if problem
|
64
|
+
end
|
65
|
+
|
66
|
+
module RouteName
|
67
|
+
def self.new(event_channel, event_name)
|
68
|
+
"#{event_channel}:#{event_name}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
module SubscribersRedisKey
|
73
|
+
def self.new(route_name)
|
74
|
+
"events:#{route_name}:subscribers"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
InvalidError = Class.new(ArgumentError)
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'qs/message_handler'
|
2
|
+
|
3
|
+
module Qs
|
4
|
+
|
5
|
+
module EventHandler
|
6
|
+
|
7
|
+
def self.included(klass)
|
8
|
+
klass.class_eval do
|
9
|
+
include Qs::MessageHandler
|
10
|
+
include InstanceMethods
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module InstanceMethods
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
reference = '0x0%x' % (self.object_id << 1)
|
18
|
+
"#<#{self.class}:#{reference} @event=#{event.inspect}>"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Helpers
|
24
|
+
|
25
|
+
def event; @qs_runner.message; end
|
26
|
+
def event_channel; event.channel; end
|
27
|
+
def event_name; event.name; end
|
28
|
+
def event_published_at; event.published_at; end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'qs/test_runner'
|
2
|
+
|
3
|
+
module Qs::EventHandler
|
4
|
+
|
5
|
+
module TestHelpers
|
6
|
+
|
7
|
+
def test_runner(handler_class, args = nil)
|
8
|
+
Qs::EventTestRunner.new(handler_class, args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_handler(handler_class, args = nil)
|
12
|
+
test_runner(handler_class, args).handler
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/lib/qs/job.rb
CHANGED
@@ -1,26 +1,24 @@
|
|
1
|
+
require 'qs/message'
|
2
|
+
|
1
3
|
module Qs
|
2
4
|
|
3
|
-
class Job
|
5
|
+
class Job < Message
|
4
6
|
|
5
|
-
|
6
|
-
created_at = Time.at(payload['created_at'].to_i)
|
7
|
-
self.new(payload['name'], payload['params'], created_at)
|
8
|
-
end
|
7
|
+
PAYLOAD_TYPE = 'job'
|
9
8
|
|
10
|
-
attr_reader :name, :
|
9
|
+
attr_reader :name, :created_at
|
11
10
|
|
12
|
-
def initialize(name,
|
13
|
-
|
11
|
+
def initialize(name, options = nil)
|
12
|
+
options ||= {}
|
13
|
+
options[:params] ||= {}
|
14
|
+
validate!(name, options[:params])
|
14
15
|
@name = name
|
15
|
-
@
|
16
|
-
|
16
|
+
@created_at = options[:created_at] || Time.now
|
17
|
+
super(PAYLOAD_TYPE, options)
|
17
18
|
end
|
18
19
|
|
19
|
-
def
|
20
|
-
|
21
|
-
'params' => StringifyParams.new(self.params),
|
22
|
-
'created_at' => self.created_at.to_i
|
23
|
-
}
|
20
|
+
def route_name
|
21
|
+
self.name
|
24
22
|
end
|
25
23
|
|
26
24
|
def inspect
|
@@ -33,7 +31,10 @@ module Qs
|
|
33
31
|
|
34
32
|
def ==(other)
|
35
33
|
if other.kind_of?(self.class)
|
36
|
-
self.
|
34
|
+
self.payload_type == other.payload_type &&
|
35
|
+
self.name == other.name &&
|
36
|
+
self.params == other.params &&
|
37
|
+
self.created_at == other.created_at
|
37
38
|
else
|
38
39
|
super
|
39
40
|
end
|
@@ -47,24 +48,11 @@ module Qs
|
|
47
48
|
elsif !params.kind_of?(::Hash)
|
48
49
|
"The job's params are not valid."
|
49
50
|
end
|
50
|
-
raise(
|
51
|
+
raise(InvalidError, problem) if problem
|
51
52
|
end
|
52
53
|
|
53
|
-
|
54
|
-
def self.new(object)
|
55
|
-
case(object)
|
56
|
-
when Hash
|
57
|
-
object.inject({}){ |h, (k, v)| h.merge(k.to_s => self.new(v)) }
|
58
|
-
when Array
|
59
|
-
object.map{ |item| self.new(item) }
|
60
|
-
else
|
61
|
-
object
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
54
|
+
InvalidError = Class.new(ArgumentError)
|
65
55
|
|
66
56
|
end
|
67
57
|
|
68
|
-
BadJobError = Class.new(ArgumentError)
|
69
|
-
|
70
58
|
end
|