queue-bus 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rbenv-version +1 -0
- data/.rspec +1 -0
- data/.rvmrc +2 -0
- data/Gemfile +6 -0
- data/MIT-LICENSE +20 -0
- data/README.mdown +264 -0
- data/Rakefile +1 -0
- data/lib/queue-bus.rb +62 -0
- data/lib/queue_bus/adapters/base.rb +41 -0
- data/lib/queue_bus/adapters/data.rb +65 -0
- data/lib/queue_bus/application.rb +121 -0
- data/lib/queue_bus/config.rb +98 -0
- data/lib/queue_bus/dispatch.rb +61 -0
- data/lib/queue_bus/dispatchers.rb +26 -0
- data/lib/queue_bus/driver.rb +31 -0
- data/lib/queue_bus/heartbeat.rb +109 -0
- data/lib/queue_bus/local.rb +38 -0
- data/lib/queue_bus/matcher.rb +81 -0
- data/lib/queue_bus/publisher.rb +23 -0
- data/lib/queue_bus/publishing.rb +80 -0
- data/lib/queue_bus/rider.rb +28 -0
- data/lib/queue_bus/subscriber.rb +65 -0
- data/lib/queue_bus/subscription.rb +55 -0
- data/lib/queue_bus/subscription_list.rb +53 -0
- data/lib/queue_bus/task_manager.rb +52 -0
- data/lib/queue_bus/util.rb +87 -0
- data/lib/queue_bus/version.rb +3 -0
- data/lib/queue_bus/worker.rb +14 -0
- data/lib/tasks/resquebus.rake +2 -0
- data/queue-bus.gemspec +32 -0
- data/spec/adapter/publish_at_spec.rb +48 -0
- data/spec/adapter/support.rb +15 -0
- data/spec/adapter_spec.rb +14 -0
- data/spec/application_spec.rb +152 -0
- data/spec/config_spec.rb +83 -0
- data/spec/dispatch_spec.rb +76 -0
- data/spec/driver_spec.rb +100 -0
- data/spec/heartbeat_spec.rb +44 -0
- data/spec/integration_spec.rb +53 -0
- data/spec/matcher_spec.rb +143 -0
- data/spec/publish_spec.rb +95 -0
- data/spec/publisher_spec.rb +7 -0
- data/spec/rider_spec.rb +39 -0
- data/spec/spec_helper.rb +69 -0
- data/spec/subscriber_spec.rb +268 -0
- data/spec/subscription_list_spec.rb +43 -0
- data/spec/subscription_spec.rb +53 -0
- metadata +192 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
# Creates a DSL for apps to define their blocks to run for event_types
|
2
|
+
|
3
|
+
module QueueBus
|
4
|
+
class Dispatch
|
5
|
+
|
6
|
+
attr_reader :app_key, :subscriptions
|
7
|
+
|
8
|
+
def initialize(app_key)
|
9
|
+
@app_key = Application.normalize(app_key)
|
10
|
+
@subscriptions = SubscriptionList.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def size
|
14
|
+
@subscriptions.size
|
15
|
+
end
|
16
|
+
|
17
|
+
def subscribe(key, matcher_hash = nil, &block)
|
18
|
+
dispatch_event("default", key, matcher_hash, block)
|
19
|
+
end
|
20
|
+
|
21
|
+
# allows definitions of other queues
|
22
|
+
def method_missing(method_name, *args, &block)
|
23
|
+
if args.size == 1 && block
|
24
|
+
dispatch_event(method_name, args[0], nil, block)
|
25
|
+
elsif args.size == 2 && block
|
26
|
+
dispatch_event(method_name, args[0], args[1], block)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute(key, attributes)
|
33
|
+
sub = subscriptions.key(key)
|
34
|
+
if sub
|
35
|
+
sub.execute!(attributes)
|
36
|
+
else
|
37
|
+
# TODO: log that it's not there
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def subscription_matches(attributes)
|
42
|
+
out = subscriptions.matches(attributes)
|
43
|
+
out.each do |sub|
|
44
|
+
sub.app_key = self.app_key
|
45
|
+
end
|
46
|
+
out
|
47
|
+
end
|
48
|
+
|
49
|
+
def dispatch_event(queue, key, matcher_hash, block)
|
50
|
+
# if not matcher_hash, assume key is a event_type regex
|
51
|
+
matcher_hash ||= { "bus_event_type" => key }
|
52
|
+
add_subscription("#{app_key}_#{queue}", key, "::QueueBus::Rider", matcher_hash, block)
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_subscription(queue_name, key, class_name, matcher_hash = nil, block)
|
56
|
+
sub = Subscription.register(queue_name, key, class_name, matcher_hash, block)
|
57
|
+
subscriptions.add(sub)
|
58
|
+
sub
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module QueueBus
|
2
|
+
class Dispatchers
|
3
|
+
def dispatch(app_key=nil, &block)
|
4
|
+
dispatcher = dispatcher_by_key(app_key)
|
5
|
+
dispatcher.instance_eval(&block)
|
6
|
+
dispatcher
|
7
|
+
end
|
8
|
+
|
9
|
+
def dispatchers
|
10
|
+
@dispatchers ||= {}
|
11
|
+
@dispatchers.values
|
12
|
+
end
|
13
|
+
|
14
|
+
def dispatcher_by_key(app_key)
|
15
|
+
app_key = Application.normalize(app_key || ::QueueBus.default_app_key)
|
16
|
+
@dispatchers ||= {}
|
17
|
+
@dispatchers[app_key] ||= Dispatch.new(app_key)
|
18
|
+
end
|
19
|
+
|
20
|
+
def dispatcher_execute(app_key, key, attributes)
|
21
|
+
@dispatchers ||= {}
|
22
|
+
dispatcher = @dispatchers[app_key]
|
23
|
+
dispatcher.execute(key, attributes) if dispatcher
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module QueueBus
|
2
|
+
# fans out an event to multiple queues
|
3
|
+
class Driver
|
4
|
+
include ::QueueBus::Worker
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def subscription_matches(attributes)
|
8
|
+
out = []
|
9
|
+
Application.all.each do |app|
|
10
|
+
subs = app.subscription_matches(attributes)
|
11
|
+
out.concat(subs)
|
12
|
+
end
|
13
|
+
out
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform(attributes={})
|
17
|
+
raise "No attributes passed" if attributes.empty?
|
18
|
+
|
19
|
+
::QueueBus.log_worker("Driver running: #{attributes.inspect}")
|
20
|
+
|
21
|
+
subscription_matches(attributes).each do |sub|
|
22
|
+
::QueueBus.log_worker(" ...sending to #{sub.queue_name} queue with class #{sub.class_name} for app #{sub.app_key} because of subscription: #{sub.key}")
|
23
|
+
|
24
|
+
bus_attr = {"bus_driven_at" => Time.now.to_i, "bus_rider_queue" => sub.queue_name, "bus_rider_app_key" => sub.app_key, "bus_rider_sub_key" => sub.key, "bus_rider_class_name" => sub.class_name}
|
25
|
+
::QueueBus.enqueue_to(sub.queue_name, sub.class_name, bus_attr.merge(attributes || {}))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module QueueBus
|
2
|
+
# publishes event about the current time
|
3
|
+
class Heartbeat
|
4
|
+
include ::QueueBus::Worker
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def lock_key
|
9
|
+
"bus:heartbeat:lock"
|
10
|
+
end
|
11
|
+
|
12
|
+
def lock_seconds
|
13
|
+
60
|
14
|
+
end
|
15
|
+
|
16
|
+
def lock!
|
17
|
+
now = Time.now.to_i
|
18
|
+
timeout = now + lock_seconds + 2
|
19
|
+
|
20
|
+
::QueueBus.redis do |redis|
|
21
|
+
# return true if we successfully acquired the lock
|
22
|
+
return timeout if redis.setnx(lock_key, timeout)
|
23
|
+
|
24
|
+
# see if the existing timeout is still valid and return false if it is
|
25
|
+
# (we cannot acquire the lock during the timeout period)
|
26
|
+
return 0 if now <= redis.get(lock_key).to_i
|
27
|
+
|
28
|
+
# otherwise set the timeout and ensure that no other worker has
|
29
|
+
# acquired the lock
|
30
|
+
if now > redis.getset(lock_key, timeout).to_i
|
31
|
+
return timeout
|
32
|
+
else
|
33
|
+
return 0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def unlock!
|
39
|
+
::QueueBus.redis { |redis| redis.del(lock_key) }
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def redis_key
|
44
|
+
"bus:heartbeat:timestamp"
|
45
|
+
end
|
46
|
+
|
47
|
+
def environment_name
|
48
|
+
ENV["RAILS_ENV"] || ENV["RACK_ENV"] || ENV["BUS_ENV"]
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_saved_minute!
|
52
|
+
key = ::QueueBus.redis { |redis| redis.get(redis_key) }
|
53
|
+
return nil if key.nil?
|
54
|
+
case environment_name
|
55
|
+
when 'development', 'test'
|
56
|
+
# only 3 minutes in development; otherwise, TONS of events if not run in a while
|
57
|
+
three_ago = Time.now.to_i - 3*60*60
|
58
|
+
key = three_ago if key.to_i < three_ago
|
59
|
+
end
|
60
|
+
return key.to_i
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_saved_minute!(epoch_minute)
|
64
|
+
::QueueBus.redis { |redis| redis.set(redis_key, epoch_minute) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def perform
|
68
|
+
real_now = Time.now.to_i
|
69
|
+
run_until = lock! - 2
|
70
|
+
return if run_until < real_now
|
71
|
+
|
72
|
+
while((real_now = Time.now.to_i) < run_until)
|
73
|
+
minutes = real_now.to_i / 60
|
74
|
+
last = get_saved_minute!
|
75
|
+
if last
|
76
|
+
break if minutes <= last
|
77
|
+
minutes = last + 1
|
78
|
+
end
|
79
|
+
|
80
|
+
seconds = minutes * (60)
|
81
|
+
hours = minutes / (60)
|
82
|
+
days = minutes / (60*24)
|
83
|
+
|
84
|
+
now = Time.at(seconds)
|
85
|
+
|
86
|
+
attributes = {}
|
87
|
+
attributes["epoch_seconds"] = seconds
|
88
|
+
attributes["epoch_minutes"] = minutes
|
89
|
+
attributes["epoch_hours"] = hours
|
90
|
+
attributes["epoch_days"] = days
|
91
|
+
|
92
|
+
attributes["minute"] = now.min
|
93
|
+
attributes["hour"] = now.hour
|
94
|
+
attributes["day"] = now.day
|
95
|
+
attributes["month"] = now.month
|
96
|
+
attributes["year"] = now.year
|
97
|
+
attributes["yday"] = now.yday
|
98
|
+
attributes["wday"] = now.wday
|
99
|
+
|
100
|
+
::QueueBus.publish("heartbeat_minutes", attributes)
|
101
|
+
set_saved_minute!(minutes)
|
102
|
+
end
|
103
|
+
|
104
|
+
unlock!
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module QueueBus
|
2
|
+
# only process local queues
|
3
|
+
class Local
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def perform(attributes = {})
|
7
|
+
if ::QueueBus.local_mode == :suppress
|
8
|
+
::QueueBus.log_worker("Suppressed: #{attributes.inspect}")
|
9
|
+
return # not doing anything
|
10
|
+
end
|
11
|
+
|
12
|
+
::QueueBus.log_worker("Local running: #{attributes.inspect}")
|
13
|
+
|
14
|
+
# looking for subscriptions, not queues
|
15
|
+
subscription_matches(attributes).each do |sub|
|
16
|
+
bus_attr = {"bus_driven_at" => Time.now.to_i, "bus_rider_queue" => sub.queue_name, "bus_rider_app_key" => sub.app_key, "bus_rider_sub_key" => sub.key, "bus_rider_class_name" => sub.class_name}
|
17
|
+
to_publish = bus_attr.merge(attributes || {})
|
18
|
+
if ::QueueBus.local_mode == :standalone
|
19
|
+
::QueueBus.enqueue_to(sub.queue_name, sub.class_name, bus_attr.merge(attributes || {}))
|
20
|
+
else # defaults to inline mode
|
21
|
+
sub.execute!(to_publish)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# looking directly at subscriptions loaded into dispatcher
|
27
|
+
# so we don't need redis server up
|
28
|
+
def subscription_matches(attributes)
|
29
|
+
out = []
|
30
|
+
::QueueBus.dispatchers.each do |dispatcher|
|
31
|
+
out.concat(dispatcher.subscription_matches(attributes))
|
32
|
+
end
|
33
|
+
out
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module QueueBus
|
2
|
+
class Matcher
|
3
|
+
SPECIAL_PREPEND = "bus_special_value_"
|
4
|
+
attr_reader :filters
|
5
|
+
def initialize(hash)
|
6
|
+
@filters = encode(hash)
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_redis
|
10
|
+
@filters
|
11
|
+
end
|
12
|
+
|
13
|
+
def match?(attribute_name, attributes)
|
14
|
+
mine = filters[attribute_name].to_s
|
15
|
+
return false if mine.size == 0
|
16
|
+
|
17
|
+
given = attributes[attribute_name]
|
18
|
+
case mine
|
19
|
+
when "#{SPECIAL_PREPEND}key"
|
20
|
+
return true if attributes.has_key?(attribute_name)
|
21
|
+
return false
|
22
|
+
when "#{SPECIAL_PREPEND}blank"
|
23
|
+
return true if given.to_s.strip.size == 0
|
24
|
+
return false
|
25
|
+
when "#{SPECIAL_PREPEND}empty"
|
26
|
+
return false if given == nil
|
27
|
+
return true if given.to_s.size == 0
|
28
|
+
return false
|
29
|
+
when "#{SPECIAL_PREPEND}nil"
|
30
|
+
return true if given == nil
|
31
|
+
return false
|
32
|
+
when "#{SPECIAL_PREPEND}value"
|
33
|
+
return false if given == nil
|
34
|
+
return true
|
35
|
+
when "#{SPECIAL_PREPEND}present"
|
36
|
+
return true if given.to_s.strip.size > 0
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
|
40
|
+
given = given.to_s
|
41
|
+
|
42
|
+
return true if mine == given
|
43
|
+
begin
|
44
|
+
# if it's already a regex, don't mess with it
|
45
|
+
# otherwise, it should have start and end line situation
|
46
|
+
if mine[0..6] == "(?-mix:"
|
47
|
+
regex = Regexp.new(mine)
|
48
|
+
else
|
49
|
+
regex = Regexp.new("^#{mine}$")
|
50
|
+
end
|
51
|
+
return !!regex.match(given)
|
52
|
+
rescue
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def matches?(attributes)
|
58
|
+
return false if filters.empty?
|
59
|
+
return false if attributes == nil
|
60
|
+
|
61
|
+
filters.keys.each do |key|
|
62
|
+
return false unless match?(key, attributes)
|
63
|
+
end
|
64
|
+
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
def encode(hash)
|
69
|
+
out = {}
|
70
|
+
hash.each do |key, value|
|
71
|
+
case value
|
72
|
+
when :key, :blank, :nil, :present, :empty, :value
|
73
|
+
value = "#{SPECIAL_PREPEND}#{value}"
|
74
|
+
end
|
75
|
+
out[key.to_s] = value.to_s
|
76
|
+
end
|
77
|
+
out
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module QueueBus
|
2
|
+
# publishes on a delay
|
3
|
+
class Publisher
|
4
|
+
include ::QueueBus::Worker
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def perform(*args)
|
8
|
+
# TODO: move this to just resquebus for fallback
|
9
|
+
if args.size > 1
|
10
|
+
# handles older arguments
|
11
|
+
event_type = args.first
|
12
|
+
attributes = args.last
|
13
|
+
else
|
14
|
+
attributes = args.first
|
15
|
+
event_type = attributes["bus_event_type"]
|
16
|
+
end
|
17
|
+
::QueueBus.log_worker("Publisher running: #{event_type} - #{attributes.inspect}")
|
18
|
+
::QueueBus.publish(event_type, attributes)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module QueueBus
|
2
|
+
module Publishing
|
3
|
+
|
4
|
+
def with_global_attributes(attributes)
|
5
|
+
original_timezone = false
|
6
|
+
original_locale = false
|
7
|
+
|
8
|
+
if attributes["bus_locale"] && defined?(I18n) && I18n.respond_to?(:locale=)
|
9
|
+
original_locale = I18n.locale if I18n.respond_to?(:locale)
|
10
|
+
I18n.locale = attributes["bus_locale"]
|
11
|
+
end
|
12
|
+
|
13
|
+
if attributes["bus_timezone"] && defined?(Time) && Time.respond_to?(:zone=)
|
14
|
+
original_timezone = Time.zone if Time.respond_to?(:zone)
|
15
|
+
Time.zone = attributes["bus_timezone"]
|
16
|
+
end
|
17
|
+
|
18
|
+
yield
|
19
|
+
ensure
|
20
|
+
I18n.locale = original_locale unless original_locale == false
|
21
|
+
Time.zone = original_timezone unless original_timezone == false
|
22
|
+
end
|
23
|
+
|
24
|
+
def publish_metadata(event_type, attributes={})
|
25
|
+
# TODO: "bus_app_key" => application.app_key ?
|
26
|
+
bus_attr = {"bus_published_at" => Time.now.to_i, "bus_event_type" => event_type}
|
27
|
+
bus_attr["bus_id"] = "#{Time.now.to_i}-#{generate_uuid}"
|
28
|
+
bus_attr["bus_app_hostname"] = ::QueueBus.hostname
|
29
|
+
bus_attr["bus_locale"] = I18n.locale.to_s if defined?(I18n) && I18n.respond_to?(:locale) && I18n.locale
|
30
|
+
bus_attr["bus_timezone"] = Time.zone.name if defined?(Time) && Time.respond_to?(:zone) && Time.zone
|
31
|
+
out = bus_attr.merge(attributes || {})
|
32
|
+
::QueueBus.before_publish_callback(out)
|
33
|
+
out
|
34
|
+
end
|
35
|
+
|
36
|
+
def generate_uuid
|
37
|
+
require 'securerandom' unless defined?(SecureRandom)
|
38
|
+
return SecureRandom.uuid
|
39
|
+
|
40
|
+
rescue Exception => e
|
41
|
+
# secure random not there
|
42
|
+
# big random number a few times
|
43
|
+
n_bytes = [42].pack('i').size
|
44
|
+
n_bits = n_bytes * 8
|
45
|
+
max = 2 ** (n_bits - 2) - 1
|
46
|
+
return "#{rand(max)}-#{rand(max)}-#{rand(max)}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def publish(event_type, attributes = {})
|
50
|
+
to_publish = publish_metadata(event_type, attributes)
|
51
|
+
::QueueBus.log_application("Event published: #{event_type} #{to_publish.inspect}")
|
52
|
+
if local_mode
|
53
|
+
::QueueBus::Local.perform(to_publish)
|
54
|
+
else
|
55
|
+
enqueue_to(::QueueBus.incoming_queue, Driver, to_publish)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def publish_at(timestamp_or_epoch, event_type, attributes = {})
|
60
|
+
to_publish = publish_metadata(event_type, attributes)
|
61
|
+
to_publish["bus_delayed_until"] ||= timestamp_or_epoch.to_i
|
62
|
+
to_publish.delete("bus_published_at") unless attributes["bus_published_at"] # will be put on when it actually does it
|
63
|
+
|
64
|
+
::QueueBus.log_application("Event published:#{event_type} #{to_publish.inspect} publish_at: #{timestamp_or_epoch.to_i}")
|
65
|
+
delayed_enqueue_to(timestamp_or_epoch.to_i, incoming_queue, Publisher, to_publish)
|
66
|
+
end
|
67
|
+
|
68
|
+
def enqueue_to(queue_name, klass, hash)
|
69
|
+
::QueueBus.adapter.enqueue(queue_name, klass, hash)
|
70
|
+
end
|
71
|
+
|
72
|
+
def delayed_enqueue_to(epoch_seconds, queue_name, klass, hash)
|
73
|
+
::QueueBus.adapter.enqueue_at(epoch_seconds, queue_name, klass, hash)
|
74
|
+
end
|
75
|
+
|
76
|
+
def heartbeat!
|
77
|
+
::QueueBus.adapter.setup_heartbeat!(incoming_queue)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module QueueBus
|
2
|
+
# queue'd in each
|
3
|
+
class Rider
|
4
|
+
include ::QueueBus::Worker
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def perform(attributes = {})
|
8
|
+
sub_key = attributes["bus_rider_sub_key"]
|
9
|
+
app_key = attributes["bus_rider_app_key"]
|
10
|
+
raise "No application key passed" if app_key.to_s == ""
|
11
|
+
raise "No subcription key passed" if sub_key.to_s == ""
|
12
|
+
|
13
|
+
attributes ||= {}
|
14
|
+
|
15
|
+
::QueueBus.log_worker("Rider received: #{app_key} #{sub_key} #{attributes.inspect}")
|
16
|
+
|
17
|
+
# attributes that should be available
|
18
|
+
# attributes["bus_event_type"]
|
19
|
+
# attributes["bus_app_key"]
|
20
|
+
# attributes["bus_published_at"]
|
21
|
+
# attributes["bus_driven_at"]
|
22
|
+
|
23
|
+
# (now running with the real app that subscribed)
|
24
|
+
::QueueBus.dispatcher_execute(app_key, sub_key, attributes.merge("bus_executed_at" => Time.now.to_i))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module QueueBus
|
2
|
+
module Subscriber
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send(:include, ::QueueBus::Worker)
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def application(app_key)
|
12
|
+
@app_key = ::QueueBus::Application.normalize(app_key)
|
13
|
+
end
|
14
|
+
|
15
|
+
def app_key
|
16
|
+
return @app_key if @app_key
|
17
|
+
@app_key = ::QueueBus.default_app_key
|
18
|
+
return @app_key if @app_key
|
19
|
+
# module or class_name
|
20
|
+
val = self.name.to_s.split("::").first
|
21
|
+
@app_key = ::QueueBus::Util.underscore(val)
|
22
|
+
end
|
23
|
+
|
24
|
+
def subscribe(method_name, matcher_hash = nil)
|
25
|
+
queue_name = nil
|
26
|
+
queue_name ||= self.instance_variable_get(:@queue) || (self.respond_to?(:queue) && self.queue)
|
27
|
+
queue_name ||= ::QueueBus.default_queue
|
28
|
+
queue_name ||= "#{app_key}_default"
|
29
|
+
subscribe_queue(queue_name, method_name, matcher_hash)
|
30
|
+
end
|
31
|
+
|
32
|
+
def subscribe_queue(queue_name, method_name, matcher_hash = nil)
|
33
|
+
klass = self
|
34
|
+
matcher_hash ||= {"bus_event_type" => method_name}
|
35
|
+
sub_key = "#{self.name}.#{method_name}"
|
36
|
+
dispatcher = ::QueueBus.dispatcher_by_key(app_key)
|
37
|
+
dispatcher.add_subscription(queue_name, sub_key, klass.name.to_s, matcher_hash, lambda{ |att| klass.perform(att) })
|
38
|
+
end
|
39
|
+
|
40
|
+
def transform(method_name)
|
41
|
+
@transform = method_name
|
42
|
+
end
|
43
|
+
|
44
|
+
def perform(attributes)
|
45
|
+
::QueueBus.with_global_attributes(attributes) do
|
46
|
+
sub_key = attributes["bus_rider_sub_key"]
|
47
|
+
meth_key = sub_key.split(".").last
|
48
|
+
queue_bus_execute(meth_key, attributes)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def queue_bus_execute(key, attributes)
|
53
|
+
args = attributes
|
54
|
+
args = send(@transform, attributes) if @transform
|
55
|
+
args = [args] unless args.is_a?(Array)
|
56
|
+
if self.respond_to?(:subscriber_with_attributes)
|
57
|
+
me = self.subscriber_with_attributes(attributes)
|
58
|
+
else
|
59
|
+
me = self.new
|
60
|
+
end
|
61
|
+
me.send(key, *args)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module QueueBus
|
2
|
+
class Subscription
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def register(queue, key, class_name, matcher, block)
|
6
|
+
Subscription.new(queue, key, class_name, matcher, block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def from_redis(hash)
|
10
|
+
queue_name = hash["queue_name"].to_s
|
11
|
+
key = hash["key"].to_s
|
12
|
+
class_name = hash["class"].to_s
|
13
|
+
matcher = hash["matcher"]
|
14
|
+
return nil if key.length == 0 || queue_name.length == 0
|
15
|
+
Subscription.new(queue_name, key, class_name, matcher, nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
def normalize(val)
|
19
|
+
val.to_s.gsub(/\W/, "_").downcase
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :matcher, :executor, :queue_name, :key, :class_name
|
24
|
+
attr_accessor :app_key # dyanmically set on return from subscription_matches
|
25
|
+
|
26
|
+
def initialize(queue_name, key, class_name, filters, executor=nil)
|
27
|
+
@queue_name = self.class.normalize(queue_name)
|
28
|
+
@key = key.to_s
|
29
|
+
@class_name = class_name.to_s
|
30
|
+
@matcher = Matcher.new(filters)
|
31
|
+
@executor = executor
|
32
|
+
end
|
33
|
+
|
34
|
+
def execute!(attributes)
|
35
|
+
attributes = attributes.with_indifferent_access if attributes.respond_to?(:with_indifferent_access)
|
36
|
+
::QueueBus.with_global_attributes(attributes) do
|
37
|
+
executor.call(attributes)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def matches?(attributes)
|
42
|
+
@matcher.matches?(attributes)
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_redis
|
46
|
+
out = {}
|
47
|
+
out["queue_name"] = queue_name
|
48
|
+
out["key"] = key
|
49
|
+
out["class"] = class_name
|
50
|
+
out["matcher"] = matcher.to_redis
|
51
|
+
out
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module QueueBus
|
2
|
+
class SubscriptionList
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def from_redis(redis_hash)
|
6
|
+
out = SubscriptionList.new
|
7
|
+
|
8
|
+
redis_hash.each do |key, value|
|
9
|
+
sub = Subscription.from_redis(value)
|
10
|
+
out.add(sub) if sub
|
11
|
+
end
|
12
|
+
|
13
|
+
out
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_redis
|
18
|
+
out = {}
|
19
|
+
@subscriptions.values.each do |sub|
|
20
|
+
out[sub.key] = sub.to_redis
|
21
|
+
end
|
22
|
+
out
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@subscriptions = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def add(sub)
|
30
|
+
@subscriptions[sub.key] = sub
|
31
|
+
end
|
32
|
+
|
33
|
+
def size
|
34
|
+
@subscriptions.size
|
35
|
+
end
|
36
|
+
|
37
|
+
def key(key)
|
38
|
+
@subscriptions[key.to_s]
|
39
|
+
end
|
40
|
+
|
41
|
+
def all
|
42
|
+
@subscriptions.values
|
43
|
+
end
|
44
|
+
|
45
|
+
def matches(attributes)
|
46
|
+
out = []
|
47
|
+
all.each do |sub|
|
48
|
+
out << sub if sub.matches?(attributes)
|
49
|
+
end
|
50
|
+
out
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|