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