resque-bus 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p194
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm use 1.9.3-p194@resque-bus --install --create
2
+ export PATH=./bin:$PATH
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "debugger"
6
+ gem "rake"
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Brian Leonard
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.mdown ADDED
@@ -0,0 +1,152 @@
1
+ ## Resque Bus
2
+
3
+ This gem uses Redis and Resque to allow simple asynchronous communication between apps.
4
+
5
+ ### Example
6
+
7
+ Application A can publish an event
8
+
9
+ # config
10
+ ResqueBus.redis = "192.168.1.1:6379"
11
+
12
+ # business logic
13
+ ResqueBus.publish("user_created", "id" => 42, "first_name" => "John", "last_name" => "Smith")
14
+
15
+ # or do it later
16
+ ResqueBus.publish_at(1.hour.from_now, "user_created", "id" => 42, "first_name" => "John", "last_name" => "Smith")
17
+
18
+ Application B is subscribed to events
19
+
20
+ # config
21
+ ResqueBus.redis = "192.168.1.1:6379"
22
+
23
+ # initializer
24
+ ResqueBus.dispatch("app_b") do
25
+ # processes event on app_b_default queue
26
+ # subscribe is short-hand to subscribe to your 'default' queue and this block with process events with the name "user_created"
27
+ subscribe "user_created" do |attributes|
28
+ NameCount.find_or_create_by_name(attributes["last_name"]).increment!
29
+ end
30
+
31
+ # processes event on app_b_critical queue
32
+ # critical is short-hand to subscribe to your 'critical' queue and this block with process events with the name "user_paid"
33
+ critical "user_paid" do |attributes|
34
+ CreditCard.charge!(attributes)
35
+ end
36
+
37
+ # you can pass any queue name you would like to process from as well IE: `banana "peeled" do |attributes|`
38
+
39
+ # and regexes work as well. note that with the above configuration along with this regex,
40
+ # the following as well as the corresponding block above would both be executed
41
+ subscribe /^user_/ do |attributes|
42
+ Metrics.record_user_action(attributes["bus_event_type"], attributes["id"])
43
+ end
44
+
45
+ # the above all filter on just the event_type, but you can filter on anything
46
+ # this would be _any_ event that has a user_id and the page value of homepage regardless of bus_event_type
47
+ subscribe "my_key", { "user_id" => :present, "page" => "homepage"} do
48
+ Mixpanel.homepage_action!(attributes["action"])
49
+ end
50
+ end
51
+
52
+ Applications can also subscribe within classes using the provided `Subscriber` module.
53
+
54
+ class SimpleSubscriber
55
+ include ResqueBus::Subscriber
56
+ subscribe :my_method
57
+
58
+ def my_method(attributes)
59
+ # heavy lifting
60
+ end
61
+ end
62
+
63
+ The following is equivalent to the original initializer and shows more options:
64
+
65
+ class OtherSubscriber
66
+ include ResqueBus::Subscriber
67
+ application :app_b
68
+
69
+ subscribe :user_created
70
+ subscribe_queue :app_b_critical, :user_paid
71
+ subscribe_queue :app_b_default, :user_action, :bus_event_type => /^user_/
72
+ subscribe :homepage_method, :user_id => :present, :page => "homepage"
73
+
74
+ def user_created(attributes)
75
+ NameCount.find_or_create_by_name(attributes["last_name"]).increment!
76
+ end
77
+
78
+ def user_paid(attributes)
79
+ CreditCard.charge!(attributes)
80
+ end
81
+
82
+ def user_action(attributes)
83
+ Metrics.record_user_action(attributes["bus_event_type"], attributes["id"])
84
+ end
85
+
86
+ def homepage_method
87
+ Mixpanel.homepage_action!(attributes["action"])
88
+ end
89
+ end
90
+
91
+
92
+ The subscription block is run inside a Resque worker which needs to be started for each app.
93
+
94
+ $ rake resquebus:setup resque:work
95
+
96
+ The incoming queue also needs to be processed on a dedicated or all the app servers.
97
+
98
+ $ rake resquebus:driver resque:work
99
+
100
+ If you want retry to work for subscribing apps, you should run resque-scheduler
101
+
102
+ $ rake resquebus:driver resque:scheduler
103
+
104
+ ### Compatibility
105
+
106
+ ResqueBus can live along side another instance of Resque that points at a different Redis server.
107
+
108
+ # config
109
+ Resque.redis = "192.168.1.0:6379"
110
+ ResqueBus.redis = "192.168.1.1:6379"
111
+
112
+ If no Redis instance is given specifically, ResqueBus will use the Resque one.
113
+
114
+ # config
115
+ Resque.redis = "192.168.1.0:6379"
116
+
117
+ That will use the default (resque) namespace which can be helpful for using the tooling. Conflict with queue names are unlikely. You can change the namespace if you like though.
118
+
119
+ # config
120
+ Resque.redis = "192.168.1.0:6379"
121
+ ResqusBus.redis.namespace = :get_on_the_bus
122
+
123
+
124
+ ### Local Mode
125
+
126
+ For development, a local mode is also provided and is specified in the
127
+ configuration.
128
+
129
+ # config
130
+ ResqueBus.local_mode = :standalone
131
+ or
132
+ ResqueBus.local_mode = :inline
133
+
134
+ Standalone mode does not require a separate resquebus:driver task to be running to process the
135
+ incoming queue. Simply publishing to the bus will distribute the incoming events
136
+ to the appropriate application specific queue. A separate resquebus:work task does
137
+ still need to be run to process these events
138
+
139
+ Inline mode skips queue processing entirely and directly dispatches the
140
+ event to the appropriate code block.
141
+
142
+
143
+ ### TODO
144
+
145
+ * There are a few spots in the code with TODO notes
146
+ * Make this not freak out in development without Redis or when Redis is down
147
+ * We might not actually need to publish in tests
148
+ * Add some rspec helpers for the apps to use: should_ post an event_publish or something along those lines
149
+ * A synchronous version will be needed for several use cases. Make it so that an event can go in real-time to one subscriber and still be async to the rest.
150
+ * Should this use resque-retry or should they jsut go into the failure queue?
151
+
152
+ Copyright (c) 2011 Brian Leonard, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+ require 'resque/tasks'
3
+ require 'resque_bus/tasks'
data/lib/resque-bus.rb ADDED
@@ -0,0 +1,250 @@
1
+ require 'redis/namespace'
2
+ require 'resque'
3
+
4
+ require "resque_bus/version"
5
+ require 'resque_bus/util'
6
+ require 'resque_bus/matcher'
7
+ require 'resque_bus/subscription'
8
+ require 'resque_bus/subscription_list'
9
+ require 'resque_bus/subscriber'
10
+ require 'resque_bus/application'
11
+ require 'resque_bus/publisher'
12
+ require 'resque_bus/driver'
13
+ require 'resque_bus/local'
14
+ require 'resque_bus/rider'
15
+ require 'resque_bus/dispatch'
16
+ require 'resque_bus/task_manager'
17
+
18
+ module ResqueBus
19
+ extend self
20
+
21
+ def default_app_key=val
22
+ @default_app_key = Application.normalize(val)
23
+ end
24
+
25
+ def default_app_key
26
+ @default_app_key
27
+ end
28
+
29
+ def default_queue=val
30
+ @default_queue = val
31
+ end
32
+
33
+ def default_queue
34
+ @default_queue
35
+ end
36
+
37
+ def hostname
38
+ @hostname ||= `hostname 2>&1`.strip.sub(/.local/,'')
39
+ end
40
+
41
+ def dispatch(app_key=nil, &block)
42
+ dispatcher = dispatcher_by_key(app_key)
43
+ dispatcher.instance_eval(&block)
44
+ dispatcher
45
+ end
46
+
47
+ def dispatchers
48
+ @dispatchers ||= {}
49
+ @dispatchers.values
50
+ end
51
+
52
+ def dispatcher_by_key(app_key)
53
+ app_key = Application.normalize(app_key || default_app_key)
54
+ @dispatchers ||= {}
55
+ @dispatchers[app_key] ||= Dispatch.new(app_key)
56
+ end
57
+
58
+ def dispatcher_execute(app_key, key, attributes)
59
+ @dispatchers ||= {}
60
+ dispatcher = @dispatchers[app_key]
61
+ dispatcher.execute(key, attributes) if dispatcher
62
+ end
63
+
64
+ def local_mode=value
65
+ @local_mode = value
66
+ end
67
+
68
+ def local_mode
69
+ @local_mode
70
+ end
71
+
72
+ # Accepts:
73
+ # 1. A 'hostname:port' String
74
+ # 2. A 'hostname:port:db' String (to select the Redis db)
75
+ # 3. A 'hostname:port/namespace' String (to set the Redis namespace)
76
+ # 4. A Redis URL String 'redis://host:port'
77
+ # 5. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`,
78
+ # or `Redis::Namespace`.
79
+ def redis=(server)
80
+ case server
81
+ when String
82
+ if server =~ /redis\:\/\//
83
+ redis = Redis.connect(:url => server, :thread_safe => true)
84
+ else
85
+ server, namespace = server.split('/', 2)
86
+ host, port, db = server.split(':')
87
+ redis = Redis.new(:host => host, :port => port,
88
+ :thread_safe => true, :db => db)
89
+ end
90
+ namespace ||= default_namespace
91
+
92
+ @redis = Redis::Namespace.new(namespace, :redis => redis)
93
+ when Redis::Namespace
94
+ @redis = server
95
+ else
96
+ @redis = Redis::Namespace.new(default_namespace, :redis => server)
97
+ end
98
+ end
99
+
100
+ # Returns the current Redis connection. If none has been created, will
101
+ # create a new one from the Reqsue one (with a different namespace)
102
+ def redis
103
+ return @redis if @redis
104
+ copy = Resque.redis.clone
105
+ copy.namespace = default_namespace
106
+ self.redis = copy
107
+ self.redis
108
+ end
109
+
110
+ def original_redis=(server)
111
+ @original_redis = server
112
+ end
113
+ def original_redis
114
+ @original_redis
115
+ end
116
+
117
+ def publish_metadata(event_type, attributes={})
118
+ # TODO: "bus_app_key" => application.app_key ?
119
+ bus_attr = {"bus_published_at" => Time.now.to_i, "created_at" => Time.now.to_i, "bus_event_type" => event_type}
120
+ bus_attr["bus_id"] ||= "#{Time.now.to_i}-#{generate_uuid}"
121
+ bus_attr["bus_app_hostname"] = hostname
122
+ bus_attr.merge(attributes || {})
123
+ end
124
+
125
+ def generate_uuid
126
+ require 'securerandom' unless defined?(SecureRandom)
127
+ return SecureRandom.uuid
128
+
129
+ rescue Exception => e
130
+ # secure random not there
131
+ # big random number a few times
132
+ n_bytes = [42].pack('i').size
133
+ n_bits = n_bytes * 8
134
+ max = 2 ** (n_bits - 2) - 1
135
+ return "#{rand(max)}-#{rand(max)}-#{rand(max)}"
136
+ end
137
+
138
+ def publish(event_type, attributes = {})
139
+ to_publish = publish_metadata(event_type, attributes)
140
+ ResqueBus.log_application("Event published: #{event_type} #{to_publish.inspect}")
141
+ if local_mode
142
+ ResqueBus::Local.perform(to_publish)
143
+ else
144
+ enqueue_to(incoming_queue, Driver, to_publish)
145
+ end
146
+ end
147
+
148
+ def publish_at(timestamp_or_epoch, event_type, attributes = {})
149
+ to_publish = publish_metadata(event_type, attributes)
150
+ to_publish["bus_delayed_until"] ||= timestamp_or_epoch.to_i
151
+ to_publish.delete("bus_published_at") unless attributes["bus_published_at"] # will be put on when it actually does it
152
+
153
+ ResqueBus.log_application("Event published:#{event_type} #{to_publish.inspect} publish_at: #{timestamp_or_epoch.to_i}")
154
+ item = delayed_job_to_hash_with_queue(incoming_queue, Publisher, [event_type, to_publish])
155
+ delayed_push(timestamp_or_epoch, item)
156
+ end
157
+
158
+ def enqueue_to(queue, klass, *args)
159
+ push(queue, :class => klass.to_s, :args => args)
160
+ end
161
+
162
+ def logger
163
+ @logger
164
+ end
165
+
166
+ def logger=val
167
+ @logger = val
168
+ end
169
+
170
+ def log_application(message)
171
+ if logger
172
+ time = Time.now.strftime('%H:%M:%S %Y-%m-%d')
173
+ logger.info("** [#{time}] #$$: ResqueBus #{message}")
174
+ end
175
+ end
176
+
177
+ def log_worker(message)
178
+ if ENV['LOGGING'] || ENV['VERBOSE'] || ENV['VVERBOSE']
179
+ time = Time.now.strftime('%H:%M:%S %Y-%m-%d')
180
+ puts "** [#{time}] #$$: #{message}"
181
+ end
182
+ end
183
+
184
+ protected
185
+
186
+ def reset
187
+ # used by tests
188
+ @redis = nil # clear instance of redis
189
+ @dispatcher = nil
190
+ @default_app_key = nil
191
+ @default_queue = nil
192
+ end
193
+
194
+ def incoming_queue
195
+ "resquebus_incoming"
196
+ end
197
+
198
+ def default_namespace
199
+ # It might play better on the same server, but overall life is more complicated
200
+ :resque
201
+ end
202
+
203
+ ## From Resque, but using a (possibly) different instance of Redis
204
+
205
+ # Pushes a job onto a queue. Queue name should be a string and the
206
+ # item should be any JSON-able Ruby object.
207
+ #
208
+ # Resque works generally expect the `item` to be a hash with the following
209
+ # keys:
210
+ #
211
+ # class - The String name of the job to run.
212
+ # args - An Array of arguments to pass the job. Usually passed
213
+ # via `class.to_class.perform(*args)`.
214
+ #
215
+ # Example
216
+ #
217
+ # Resque.push('archive', :class => 'Archive', :args => [ 35, 'tar' ])
218
+ #
219
+ # Returns nothing
220
+ def push(queue, item)
221
+ watch_queue(queue)
222
+ redis.rpush "queue:#{queue}", Resque.encode(item)
223
+ end
224
+
225
+ # Used internally to keep track of which queues we've created.
226
+ # Don't call this directly.
227
+ def watch_queue(queue)
228
+ redis.sadd(:queues, queue.to_s)
229
+ end
230
+
231
+ ### From Resque Scheduler
232
+ # Used internally to stuff the item into the schedule sorted list.
233
+ # +timestamp+ can be either in seconds or a datetime object
234
+ # Insertion if O(log(n)).
235
+ # Returns true if it's the first job to be scheduled at that time, else false
236
+ def delayed_push(timestamp, item)
237
+ # First add this item to the list for this timestamp
238
+ redis.rpush("delayed:#{timestamp.to_i}", Resque.encode(item))
239
+
240
+ # Now, add this timestamp to the zsets. The score and the value are
241
+ # the same since we'll be querying by timestamp, and we don't have
242
+ # anything else to store.
243
+ redis.zadd :delayed_queue_schedule, timestamp.to_i, timestamp.to_i
244
+ end
245
+
246
+ def delayed_job_to_hash_with_queue(queue, klass, args)
247
+ {:class => klass.to_s, :args => args, :queue => queue}
248
+ end
249
+
250
+ end
@@ -0,0 +1,110 @@
1
+ module ResqueBus
2
+ class Application
3
+ attr_reader :app_key, :redis_key
4
+
5
+ def self.all
6
+ # note the names arent the same as we started with
7
+ ResqueBus.redis.smembers(app_list_key).collect{ |val| new(val) }
8
+ end
9
+
10
+ def initialize(app_key)
11
+ @app_key = self.class.normalize(app_key)
12
+ @redis_key = "#{self.class.app_single_key}:#{@app_key}"
13
+ # raise error if only other chars
14
+ raise "Invalid application name" if @app_key.gsub("_", "").size == 0
15
+ end
16
+
17
+ def subscribe(subscription_list, log = false)
18
+ @subscriptions = nil
19
+
20
+ if subscription_list == nil || subscription_list.size == 0
21
+ unsubscribe
22
+ return true
23
+ end
24
+
25
+ temp_key = "temp_#{redis_key}:#{rand(999999999)}"
26
+
27
+ redis_hash = subscription_list.to_redis
28
+ redis_hash.each do |key, hash|
29
+ ResqueBus.redis.hset(temp_key, key, Resque.encode(hash))
30
+ end
31
+
32
+ # make it the real one
33
+ ResqueBus.redis.rename(temp_key, redis_key)
34
+ ResqueBus.redis.sadd(self.class.app_list_key, app_key)
35
+
36
+ if log
37
+ puts ResqueBus.redis.hgetall(redis_key).inspect
38
+ end
39
+
40
+ true
41
+ end
42
+
43
+ def unsubscribe
44
+ # TODO: clean up known queues?
45
+ ResqueBus.redis.srem(self.class.app_list_key, app_key)
46
+ ResqueBus.redis.del(redis_key)
47
+ end
48
+
49
+ def no_connect_queue_names_for(subscriptions)
50
+ out = []
51
+ subscriptions.all.each do |sub|
52
+ queue = "#{app_key}_#{sub.queue_name}"
53
+ out << queue
54
+ end
55
+ out << "#{app_key}_default"
56
+ out.uniq
57
+ end
58
+
59
+ def subscription_matches(attributes)
60
+ out = subscriptions.matches(attributes)
61
+ out.each do |sub|
62
+ sub.app_key = self.app_key
63
+ end
64
+ out
65
+ end
66
+
67
+ def event_display_tuples
68
+ out = []
69
+ subscriptions.all.each do |sub|
70
+ out << [sub.event_name, sub.queue_name]
71
+ end
72
+ out
73
+ end
74
+
75
+ protected
76
+
77
+ def self.normalize(val)
78
+ val.to_s.gsub(/\W/, "_").downcase
79
+ end
80
+
81
+ def self.app_list_key
82
+ "resquebus_apps"
83
+ end
84
+
85
+ def self.app_single_key
86
+ "resquebus_app"
87
+ end
88
+
89
+ def event_queues
90
+ ResqueBus.redis.hgetall(redis_key)
91
+ end
92
+
93
+ def subscriptions
94
+ @subscriptions ||= SubscriptionList.from_redis(read_redis_hash)
95
+ end
96
+
97
+ def read_redis_hash
98
+ out = {}
99
+ ResqueBus.redis.hgetall(redis_key).each do |key, val|
100
+ begin
101
+ out[key] = Resque.decode(val)
102
+ rescue Resque::Helpers::DecodeException
103
+ out[key] = val
104
+ end
105
+ end
106
+ out
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,59 @@
1
+ # Creates a DSL for apps to define their blocks to run for event_types
2
+
3
+ module ResqueBus
4
+ class Dispatch
5
+ attr_reader :app_key, :subscriptions
6
+ def initialize(app_key)
7
+ @app_key = Application.normalize(app_key)
8
+ @subscriptions = SubscriptionList.new
9
+ end
10
+
11
+ def size
12
+ @subscriptions.size
13
+ end
14
+
15
+ def subscribe(key, matcher_hash = nil, &block)
16
+ dispatch_event("default", key, matcher_hash, block)
17
+ end
18
+
19
+ # allows definitions of other queues
20
+ def method_missing(method_name, *args, &block)
21
+ if args.size == 1 and block
22
+ dispatch_event(method_name, args[0], nil, block)
23
+ elsif args.size == 2 and block
24
+ dispatch_event(method_name, args[0], args[1], block)
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ def execute(key, attributes)
31
+ sub = subscriptions.key(key)
32
+ if sub
33
+ sub.execute!(attributes)
34
+ else
35
+ # TODO: log that it's not there
36
+ end
37
+ end
38
+
39
+ def subscription_matches(attributes)
40
+ out = subscriptions.matches(attributes)
41
+ out.each do |sub|
42
+ sub.app_key = self.app_key
43
+ end
44
+ out
45
+ end
46
+
47
+ def dispatch_event(queue, key, matcher_hash, block)
48
+ # if not matcher_hash, assume key is a event_type regex
49
+ matcher_hash ||= { "bus_event_type" => key }
50
+ add_subscription("#{app_key}_#{queue}", key, "::ResqueBus::Rider", matcher_hash, block)
51
+ end
52
+
53
+ def add_subscription(queue_name, key, class_name, matcher_hash = nil, block)
54
+ sub = Subscription.register(queue_name, key, class_name, matcher_hash, block)
55
+ subscriptions.add(sub)
56
+ sub
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,28 @@
1
+ module ResqueBus
2
+ # fans out an event to multiple queues
3
+ class Driver
4
+
5
+ def self.subscription_matches(attributes)
6
+ out = []
7
+ Application.all.each do |app|
8
+ subs = app.subscription_matches(attributes)
9
+ out.concat(subs)
10
+ end
11
+ out
12
+ end
13
+
14
+ def self.perform(attributes={})
15
+ raise "No attribiutes passed" if attributes.empty?
16
+
17
+ ResqueBus.log_worker("Driver running: #{attributes.inspect}")
18
+
19
+ subscription_matches(attributes).each do |sub|
20
+ ResqueBus.log_worker(" ...sending to #{sub.queue_name} queue with class #{sub.class_name} for app #{sub.app_key} because of subscription: #{sub.key}")
21
+
22
+ 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}
23
+ ResqueBus.enqueue_to(sub.queue_name, sub.class_name, bus_attr.merge(attributes || {}))
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ module ResqueBus
2
+ # only process local queues
3
+ class Local
4
+
5
+ def self.perform(attributes = {})
6
+ ResqueBus.log_worker("Local running: #{attributes.inspect}")
7
+
8
+ # looking for subscriptions, not queues
9
+ subscription_matches(attributes).each do |sub|
10
+ 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}
11
+ to_publish = bus_attr.merge(attributes || {})
12
+ if ResqueBus.local_mode == :standalone
13
+ ResqueBus.enqueue_to(sub.queue_name, sub.class_name, bus_attr.merge(attributes || {}))
14
+ # defaults to inline mode
15
+ else ResqueBus.local_mode == :inline
16
+ sub.execute!(to_publish)
17
+ end
18
+ end
19
+ end
20
+
21
+ # looking directly at subscriptions loaded into dispatcher
22
+ # so we don't need redis server up
23
+ def self.subscription_matches(attributes)
24
+ out = []
25
+ ResqueBus.dispatchers.each do |dispatcher|
26
+ out.concat(dispatcher.subscription_matches(attributes))
27
+ end
28
+ out
29
+ end
30
+
31
+ end
32
+ end