queue-bus 0.5.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/.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
|