queue-bus 0.6.0 → 0.9.1

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +21 -0
  3. data/.rubocop.yml +35 -0
  4. data/CHANGELOG.md +38 -0
  5. data/Gemfile +4 -2
  6. data/README.mdown +16 -0
  7. data/Rakefile +2 -0
  8. data/lib/queue-bus.rb +15 -13
  9. data/lib/queue_bus/adapters/base.rb +4 -2
  10. data/lib/queue_bus/adapters/data.rb +12 -11
  11. data/lib/queue_bus/application.rb +13 -15
  12. data/lib/queue_bus/config.rb +64 -66
  13. data/lib/queue_bus/dispatch.rb +14 -12
  14. data/lib/queue_bus/dispatchers.rb +12 -5
  15. data/lib/queue_bus/driver.rb +15 -10
  16. data/lib/queue_bus/heartbeat.rb +32 -30
  17. data/lib/queue_bus/local.rb +9 -9
  18. data/lib/queue_bus/matcher.rb +36 -27
  19. data/lib/queue_bus/publisher.rb +7 -5
  20. data/lib/queue_bus/publishing.rb +31 -24
  21. data/lib/queue_bus/rider.rb +26 -22
  22. data/lib/queue_bus/subscriber.rb +20 -14
  23. data/lib/queue_bus/subscription.rb +25 -15
  24. data/lib/queue_bus/subscription_list.rb +30 -12
  25. data/lib/queue_bus/task_manager.rb +18 -16
  26. data/lib/queue_bus/tasks.rb +27 -11
  27. data/lib/queue_bus/util.rb +11 -8
  28. data/lib/queue_bus/version.rb +3 -1
  29. data/lib/queue_bus/worker.rb +3 -2
  30. data/queue-bus.gemspec +19 -17
  31. data/spec/adapter/publish_at_spec.rb +28 -25
  32. data/spec/adapter/support.rb +7 -1
  33. data/spec/adapter_spec.rb +4 -2
  34. data/spec/application_spec.rb +97 -97
  35. data/spec/config_spec.rb +116 -40
  36. data/spec/dispatch_spec.rb +48 -51
  37. data/spec/driver_spec.rb +60 -58
  38. data/spec/heartbeat_spec.rb +26 -24
  39. data/spec/integration_spec.rb +41 -40
  40. data/spec/matcher_spec.rb +104 -102
  41. data/spec/publish_spec.rb +46 -46
  42. data/spec/publisher_spec.rb +3 -1
  43. data/spec/rider_spec.rb +16 -14
  44. data/spec/spec_helper.rb +12 -7
  45. data/spec/subscriber_spec.rb +227 -227
  46. data/spec/subscription_list_spec.rb +31 -31
  47. data/spec/subscription_spec.rb +37 -36
  48. data/spec/worker_spec.rb +17 -15
  49. metadata +21 -6
@@ -1,23 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Creates a DSL for apps to define their blocks to run for event_types
2
4
 
3
5
  module QueueBus
6
+ # A Dispatch object can be used to declare an application along with it's various subscriptions.
4
7
  class Dispatch
5
-
6
8
  attr_reader :app_key, :subscriptions
7
-
9
+
8
10
  def initialize(app_key)
9
11
  @app_key = Application.normalize(app_key)
10
12
  @subscriptions = SubscriptionList.new
11
13
  end
12
-
14
+
13
15
  def size
14
16
  @subscriptions.size
15
17
  end
16
-
18
+
17
19
  def subscribe(key, matcher_hash = nil, &block)
18
- dispatch_event("default", key, matcher_hash, block)
20
+ dispatch_event('default', key, matcher_hash, block)
19
21
  end
20
-
22
+
21
23
  # allows definitions of other queues
22
24
  def method_missing(method_name, *args, &block)
23
25
  if args.size == 1 && block
@@ -28,7 +30,7 @@ module QueueBus
28
30
  super
29
31
  end
30
32
  end
31
-
33
+
32
34
  def execute(key, attributes)
33
35
  sub = subscriptions.key(key)
34
36
  if sub
@@ -41,17 +43,17 @@ module QueueBus
41
43
  def subscription_matches(attributes)
42
44
  out = subscriptions.matches(attributes)
43
45
  out.each do |sub|
44
- sub.app_key = self.app_key
46
+ sub.app_key = app_key
45
47
  end
46
48
  out
47
49
  end
48
-
50
+
49
51
  def dispatch_event(queue, key, matcher_hash, block)
50
52
  # 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
+ matcher_hash ||= { 'bus_event_type' => key }
54
+ add_subscription("#{app_key}_#{queue}", key, '::QueueBus::Rider', matcher_hash, block)
53
55
  end
54
-
56
+
55
57
  def add_subscription(queue_name, key, class_name, matcher_hash = nil, block)
56
58
  sub = Subscription.register(queue_name, key, class_name, matcher_hash, block)
57
59
  subscriptions.add(sub)
@@ -1,26 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module QueueBus
4
+ # A collection of Dispatches
5
+ #
6
+ # Each Dispatch is an application with it's own set of subscriptions. This is a master object
7
+ # that provides some basic controls over the set of applications.
2
8
  class Dispatchers
3
- def dispatch(app_key=nil, &block)
9
+ # Fetches a dispatch for the application key and binds the provided block to it.
10
+ def dispatch(app_key = nil, &block)
4
11
  dispatcher = dispatcher_by_key(app_key)
5
12
  dispatcher.instance_eval(&block)
6
13
  dispatcher
7
14
  end
8
-
15
+
9
16
  def dispatchers
10
17
  @dispatchers ||= {}
11
18
  @dispatchers.values
12
19
  end
13
-
20
+
14
21
  def dispatcher_by_key(app_key)
15
22
  app_key = Application.normalize(app_key || ::QueueBus.default_app_key)
16
23
  @dispatchers ||= {}
17
24
  @dispatchers[app_key] ||= Dispatch.new(app_key)
18
25
  end
19
-
26
+
20
27
  def dispatcher_execute(app_key, key, attributes)
21
28
  @dispatchers ||= {}
22
29
  dispatcher = @dispatchers[app_key]
23
- dispatcher.execute(key, attributes) if dispatcher
30
+ dispatcher&.execute(key, attributes)
24
31
  end
25
32
  end
26
33
  end
@@ -1,7 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module QueueBus
2
- # fans out an event to multiple queues
4
+ # Fans out an event to multiple queues
5
+ #
6
+ # When a single event is broadcast, it may have zero to many subscriptions attached to it.
7
+ # The Driver is what is run in order to look up the subscription matches and enqueue each
8
+ # of the jobs. It uses the class_name supplied by the subscription to know which class will
9
+ # be performed.
3
10
  class Driver
4
-
5
11
  class << self
6
12
  def subscription_matches(attributes)
7
13
  out = []
@@ -12,24 +18,23 @@ module QueueBus
12
18
  out
13
19
  end
14
20
 
15
- def perform(attributes={})
16
- raise "No attributes passed" if attributes.empty?
21
+ def perform(attributes = {})
22
+ raise 'No attributes passed' if attributes.empty?
17
23
 
18
24
  ::QueueBus.log_worker("Driver running: #{attributes.inspect}")
19
25
 
20
26
  subscription_matches(attributes).each do |sub|
21
27
  ::QueueBus.log_worker(" ...sending to #{sub.queue_name} queue with class #{sub.class_name} for app #{sub.app_key} because of subscription: #{sub.key}")
22
28
 
23
- bus_attr = { "bus_driven_at" => Time.now.to_i,
24
- "bus_rider_queue" => sub.queue_name,
25
- "bus_rider_app_key" => sub.app_key,
26
- "bus_rider_sub_key" => sub.key,
27
- "bus_rider_class_name" => sub.class_name}
29
+ bus_attr = { 'bus_driven_at' => Time.now.to_i,
30
+ 'bus_rider_queue' => sub.queue_name,
31
+ 'bus_rider_app_key' => sub.app_key,
32
+ 'bus_rider_sub_key' => sub.key,
33
+ 'bus_rider_class_name' => sub.class_name }
28
34
  bus_attr = bus_attr.merge(attributes || {})
29
35
  ::QueueBus.enqueue_to(sub.queue_name, sub.class_name, bus_attr)
30
36
  end
31
37
  end
32
38
  end
33
-
34
39
  end
35
40
  end
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module QueueBus
2
- # publishes event about the current time
4
+ # When run, will calculate all of the heartbeats that need to be sent and then broadcasts
5
+ # those events out for execution. By always backfilling it ensures that no heartbeat is
6
+ # ever missed.
3
7
  class Heartbeat
4
-
5
8
  class << self
6
-
7
9
  def lock_key
8
- "bus:heartbeat:lock"
10
+ 'bus:heartbeat:lock'
9
11
  end
10
12
 
11
13
  def lock_seconds
@@ -38,71 +40,71 @@ module QueueBus
38
40
  ::QueueBus.redis { |redis| redis.del(lock_key) }
39
41
  end
40
42
 
41
-
42
43
  def redis_key
43
- "bus:heartbeat:timestamp"
44
+ 'bus:heartbeat:timestamp'
44
45
  end
45
46
 
46
47
  def environment_name
47
- ENV["RAILS_ENV"] || ENV["RACK_ENV"] || ENV["BUS_ENV"]
48
+ ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['BUS_ENV']
48
49
  end
49
50
 
50
51
  def get_saved_minute!
51
52
  key = ::QueueBus.redis { |redis| redis.get(redis_key) }
52
53
  return nil if key.nil?
54
+
53
55
  case environment_name
54
56
  when 'development', 'test'
55
57
  # only 3 minutes in development; otherwise, TONS of events if not run in a while
56
- three_ago = Time.now.to_i - 3*60*60
58
+ three_ago = Time.now.to_i / 60 - 3
57
59
  key = three_ago if key.to_i < three_ago
58
60
  end
59
- return key.to_i
61
+ key.to_i
60
62
  end
61
63
 
62
64
  def set_saved_minute!(epoch_minute)
63
65
  ::QueueBus.redis { |redis| redis.set(redis_key, epoch_minute) }
64
66
  end
65
67
 
66
- def perform(*args)
68
+ def perform(*_args)
67
69
  real_now = Time.now.to_i
68
70
  run_until = lock! - 2
69
71
  return if run_until < real_now
70
72
 
71
- while((real_now = Time.now.to_i) < run_until)
73
+ while (real_now = Time.now.to_i) < run_until
72
74
  minutes = real_now.to_i / 60
73
75
  last = get_saved_minute!
74
76
  if last
75
77
  break if minutes <= last
78
+
76
79
  minutes = last + 1
77
80
  end
78
81
 
79
- seconds = minutes * (60)
80
- hours = minutes / (60)
81
- days = minutes / (60*24)
82
+ seconds = minutes * 60
83
+ hours = minutes / 60
84
+ days = minutes / (60 * 24)
82
85
 
83
- now = Time.at(seconds)
86
+ now = Time.at(seconds)
84
87
 
85
88
  attributes = {}
86
- attributes["epoch_seconds"] = seconds
87
- attributes["epoch_minutes"] = minutes
88
- attributes["epoch_hours"] = hours
89
- attributes["epoch_days"] = days
90
-
91
- attributes["minute"] = now.min
92
- attributes["hour"] = now.hour
93
- attributes["day"] = now.day
94
- attributes["month"] = now.month
95
- attributes["year"] = now.year
96
- attributes["yday"] = now.yday
97
- attributes["wday"] = now.wday
98
-
99
- ::QueueBus.publish("heartbeat_minutes", attributes)
89
+ attributes['epoch_seconds'] = seconds
90
+ attributes['epoch_minutes'] = minutes
91
+ attributes['epoch_hours'] = hours
92
+ attributes['epoch_days'] = days
93
+
94
+ attributes['minute'] = now.min
95
+ attributes['hour'] = now.hour
96
+ attributes['day'] = now.day
97
+ attributes['month'] = now.month
98
+ attributes['year'] = now.year
99
+ attributes['yday'] = now.yday
100
+ attributes['wday'] = now.wday
101
+
102
+ ::QueueBus.publish('heartbeat_minutes', attributes)
100
103
  set_saved_minute!(minutes)
101
104
  end
102
105
 
103
106
  unlock!
104
107
  end
105
108
  end
106
-
107
109
  end
108
110
  end
@@ -1,12 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module QueueBus
2
4
  # only process local queues
3
5
  class Local
4
-
5
6
  class << self
6
7
  def publish(attributes = {})
7
8
  if ::QueueBus.local_mode == :suppress
8
9
  ::QueueBus.log_worker("Suppressed: #{attributes.inspect}")
9
- return # not doing anything
10
+ return # not doing anything
10
11
  end
11
12
 
12
13
  # To json and back to simlulate enqueueing
@@ -17,15 +18,15 @@ module QueueBus
17
18
 
18
19
  # looking for subscriptions, not queues
19
20
  subscription_matches(attributes).each do |sub|
20
- bus_attr = { "bus_driven_at" => Time.now.to_i,
21
- "bus_rider_queue" => sub.queue_name,
22
- "bus_rider_app_key" => sub.app_key,
23
- "bus_rider_sub_key" => sub.key,
24
- "bus_rider_class_name" => sub.class_name}
21
+ bus_attr = { 'bus_driven_at' => Time.now.to_i,
22
+ 'bus_rider_queue' => sub.queue_name,
23
+ 'bus_rider_app_key' => sub.app_key,
24
+ 'bus_rider_sub_key' => sub.key,
25
+ 'bus_rider_class_name' => sub.class_name }
25
26
  to_publish = bus_attr.merge(attributes || {})
26
27
  if ::QueueBus.local_mode == :standalone
27
28
  ::QueueBus.enqueue_to(sub.queue_name, sub.class_name, bus_attr.merge(attributes || {}))
28
- else # defaults to inline mode
29
+ else # defaults to inline mode
29
30
  sub.execute!(to_publish)
30
31
  end
31
32
  end
@@ -41,6 +42,5 @@ module QueueBus
41
42
  out
42
43
  end
43
44
  end
44
-
45
45
  end
46
46
  end
@@ -1,70 +1,80 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module QueueBus
4
+ # Tests whether a field on a bus event matches a filter.
2
5
  class Matcher
3
- SPECIAL_PREPEND = "bus_special_value_"
6
+ SPECIAL_PREPEND = 'bus_special_value_'
4
7
  attr_reader :filters
5
8
  def initialize(hash)
6
9
  @filters = encode(hash)
7
10
  end
8
-
11
+
9
12
  def to_redis
10
13
  @filters
11
14
  end
12
-
15
+
13
16
  def match?(attribute_name, attributes)
14
17
  mine = filters[attribute_name].to_s
15
- return false if mine.size == 0
16
-
18
+ return false if mine.empty?
19
+
17
20
  given = attributes[attribute_name]
18
21
  case mine
19
22
  when "#{SPECIAL_PREPEND}key"
20
- return true if attributes.has_key?(attribute_name)
23
+ return true if attributes.key?(attribute_name)
24
+
21
25
  return false
22
26
  when "#{SPECIAL_PREPEND}blank"
23
- return true if given.to_s.strip.size == 0
27
+ return true if given.to_s.strip.empty?
28
+
24
29
  return false
25
30
  when "#{SPECIAL_PREPEND}empty"
26
- return false if given == nil
27
- return true if given.to_s.size == 0
31
+ return false if given.nil?
32
+ return true if given.to_s.empty?
33
+
28
34
  return false
29
35
  when "#{SPECIAL_PREPEND}nil"
30
- return true if given == nil
36
+ return true if given.nil?
37
+
31
38
  return false
32
39
  when "#{SPECIAL_PREPEND}value"
33
- return false if given == nil
40
+ return false if given.nil?
41
+
34
42
  return true
35
43
  when "#{SPECIAL_PREPEND}present"
36
- return true if given.to_s.strip.size > 0
44
+ return true unless given.to_s.strip.empty?
45
+
37
46
  return false
38
47
  end
39
-
48
+
40
49
  given = given.to_s
41
-
50
+
42
51
  return true if mine == given
52
+
43
53
  begin
44
54
  # if it's already a regex, don't mess with it
45
55
  # 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
56
+ regex = if mine[0..6] == '(?-mix:'
57
+ Regexp.new(mine)
58
+ else
59
+ Regexp.new("^#{mine}$")
60
+ end
51
61
  return !!regex.match(given)
52
- rescue
62
+ rescue StandardError
53
63
  return false
54
64
  end
55
65
  end
56
-
66
+
57
67
  def matches?(attributes)
58
68
  return false if filters.empty?
59
- return false if attributes == nil
60
-
69
+ return false if attributes.nil?
70
+
61
71
  filters.keys.each do |key|
62
72
  return false unless match?(key, attributes)
63
73
  end
64
-
74
+
65
75
  true
66
76
  end
67
-
77
+
68
78
  def encode(hash)
69
79
  out = {}
70
80
  hash.each do |key, value|
@@ -73,9 +83,8 @@ module QueueBus
73
83
  value = "#{SPECIAL_PREPEND}#{value}"
74
84
  end
75
85
  out[key.to_s] = value.to_s
76
- end
86
+ end
77
87
  out
78
88
  end
79
89
  end
80
90
  end
81
-
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module QueueBus
2
- # publishes on a delay
4
+ # A simple publishing worker for QueueBus. Makes publishing asynchronously possible since
5
+ # it may be enqueued to the background worker with a delay. This will allow the event to
6
+ # be published at a later time.
3
7
  class Publisher
4
-
5
8
  class << self
6
9
  def perform(attributes)
7
- event_type = attributes["bus_event_type"]
10
+ event_type = attributes['bus_event_type']
8
11
  ::QueueBus.log_worker("Publisher running: #{event_type} - #{attributes.inspect}")
9
12
  ::QueueBus.publish(event_type, attributes)
10
13
  end
11
14
  end
12
-
13
15
  end
14
- end
16
+ end