queue-bus 0.6.0 → 0.9.1

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