queuel 0.0.1 → 0.1.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.
Files changed (40) hide show
  1. data/.cane +3 -0
  2. data/.gitignore +1 -1
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile.lock +112 -0
  6. data/Guardfile +22 -0
  7. data/README.md +17 -11
  8. data/Rakefile +7 -0
  9. data/lib/queuel.rb +50 -27
  10. data/lib/queuel/base/engine.rb +35 -0
  11. data/lib/queuel/base/message.rb +32 -0
  12. data/lib/queuel/base/poller.rb +161 -0
  13. data/lib/queuel/base/queue.rb +59 -0
  14. data/lib/queuel/client.rb +15 -3
  15. data/lib/queuel/configurator.rb +65 -0
  16. data/lib/queuel/introspect.rb +11 -0
  17. data/lib/queuel/iron_mq/engine.rb +11 -17
  18. data/lib/queuel/iron_mq/message.rb +5 -26
  19. data/lib/queuel/iron_mq/poller.rb +8 -111
  20. data/lib/queuel/iron_mq/queue.rb +13 -24
  21. data/lib/queuel/null/engine.rb +3 -7
  22. data/lib/queuel/null/message.rb +1 -16
  23. data/lib/queuel/null/poller.rb +2 -114
  24. data/lib/queuel/null/queue.rb +6 -9
  25. data/lib/queuel/version.rb +1 -1
  26. data/queuel.gemspec +10 -0
  27. data/spec/lib/queuel/base/queue_spec.rb +23 -0
  28. data/spec/lib/queuel/client_spec.rb +30 -0
  29. data/spec/lib/queuel/configurator_spec.rb +32 -0
  30. data/spec/lib/queuel/iron_mq/engine_spec.rb +28 -0
  31. data/spec/lib/queuel/iron_mq/message_spec.rb +2 -2
  32. data/spec/lib/queuel/iron_mq/poller_spec.rb +15 -1
  33. data/spec/lib/queuel/iron_mq/queue_spec.rb +2 -2
  34. data/spec/lib/queuel_spec.rb +12 -7
  35. data/spec/spec_helper.rb +4 -0
  36. data/spec/support/engine_shared_example.rb +4 -2
  37. data/spec/support/message_shared_example.rb +2 -11
  38. data/spec/support/poller_shared_example.rb +23 -57
  39. data/spec/support/queue_shared_example.rb +14 -1
  40. metadata +163 -4
@@ -0,0 +1,59 @@
1
+ module Queuel
2
+ module Base
3
+ class Queue
4
+ extend Introspect
5
+
6
+ def initialize(client, queue_name)
7
+ self.client = client
8
+ self.name = queue_name
9
+ end
10
+
11
+ def peek(options = {})
12
+ raise NotImplementedError, "must implement #peek"
13
+ end
14
+
15
+ def push(message)
16
+ raise NotImplementedError, "must implement #push"
17
+ end
18
+
19
+ def pop(options = {}, &block)
20
+ bare_message = pop_bare_message(options)
21
+ unless bare_message.nil?
22
+ build_new_message(bare_message).tap { |message|
23
+ if block_given? && message.present?
24
+ message.delete if yield(message)
25
+ end
26
+ }
27
+ end
28
+ end
29
+
30
+ def receive(options = {}, &block)
31
+ poller_klass.new(self, block, options, thread_count).poll
32
+ end
33
+
34
+ private
35
+ attr_accessor :client
36
+ attr_accessor :name
37
+
38
+ def thread_count
39
+ Queuel.receiver_threads || 1
40
+ end
41
+
42
+ def pop_bare_message(options = {})
43
+ raise NotImplementedError, "must implement bare Message getter"
44
+ end
45
+
46
+ def build_new_message(bare_message)
47
+ message_klass.new(bare_message)
48
+ end
49
+
50
+ def message_klass
51
+ self.class.const_with_nesting("Message")
52
+ end
53
+
54
+ def poller_klass
55
+ self.class.const_with_nesting("Poller")
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/queuel/client.rb CHANGED
@@ -1,14 +1,18 @@
1
1
  module Queuel
2
+ NoQueueGivenError = Class.new StandardError
2
3
  class Client
3
- extend Forwardable
4
- def_delegators :queue_connection, :push, :pop, :receive
5
-
6
4
  def initialize(engine, credentials, init_queue = nil)
7
5
  self.engine = engine
8
6
  self.credentials = credentials
9
7
  self.given_queue = init_queue
10
8
  end
11
9
 
10
+ [:push, :pop, :receive].each do |operation|
11
+ define_method(operation) do |*args, &block|
12
+ with_queue { queue_connection.public_send(operation, *args, &block) }
13
+ end
14
+ end
15
+
12
16
  def with(change_queue = nil)
13
17
  self.clone.tap { |client| client.given_queue = change_queue }
14
18
  end
@@ -23,6 +27,14 @@ module Queuel
23
27
 
24
28
  private
25
29
 
30
+ def with_queue
31
+ if queue.nil? || queue.to_s.strip.empty?
32
+ raise NoQueueGivenError, "Must select a queue with #with or set a default_queue"
33
+ else
34
+ yield
35
+ end
36
+ end
37
+
26
38
  def queue_connection
27
39
  engine_client.queue queue
28
40
  end
@@ -0,0 +1,65 @@
1
+ require "mono_logger"
2
+ module Queuel
3
+ class Configurator
4
+ InvalidConfigurationError = Class.new StandardError
5
+ private
6
+ attr_accessor :option_values
7
+
8
+ def self.option_values
9
+ @option_values ||= {}
10
+ end
11
+
12
+ def self.define_param_accessors(param_name)
13
+ define_method param_name do |*values|
14
+ value = values.first
15
+ value ? self.send("#{param_name}=", value) : retrieve(param_name)
16
+ end
17
+ define_method "#{param_name}=" do |value|
18
+ validate!(param_name, value) &&
19
+ instance_variable_set("@#{param_name}", value)
20
+ end
21
+ end
22
+
23
+ def validate(param_name, value)
24
+ validator = self.class.option_values[param_name].fetch(:validate) { {} }[:validator] || ->(val) { true }
25
+ validator.call value
26
+ end
27
+
28
+ def validate!(param_name, value)
29
+ message = self.class.option_values[param_name].fetch(:validate) { {} }[:message]
30
+ message ||= "#{value} is not a valid value for #{param_name}"
31
+ validate(param_name, value) || raise(InvalidConfigurationError, message)
32
+ end
33
+
34
+ def retrieve(param)
35
+ if instance_variable_defined?("@#{param}")
36
+ instance_variable_get("@#{param}")
37
+ else
38
+ self.class.option_values[param][:default]
39
+ end
40
+ end
41
+
42
+ def self.param(param_name, options = {})
43
+ attr_accessor param_name
44
+ self.option_values[param_name] = options
45
+ define_param_accessors param_name
46
+ public param_name
47
+ public "#{param_name}="
48
+ end
49
+
50
+ public
51
+
52
+ param :credentials
53
+ param :engine
54
+ param :default_queue
55
+ param :receiver_threads, default: 1
56
+ param :logger, default: MonoLogger.new(STDOUT), validate: {
57
+ message: "Logger must respond to #{%w[info warn debug level level]}",
58
+ validator: ->(logger) {
59
+ %w[info warn debug level].all? { |msg| logger.respond_to? msg } &&
60
+ logger.respond_to?(:level)
61
+ }
62
+ }
63
+ param :log_level, default: MonoLogger::ERROR
64
+ end
65
+ end
@@ -0,0 +1,11 @@
1
+ module Queuel
2
+ module Introspect
3
+ def module_names
4
+ self.to_s.split("::")[0..-2].join("::")
5
+ end
6
+
7
+ def const_with_nesting(other_name)
8
+ Object.module_eval("#{module_names}::#{other_name}", __FILE__, __LINE__)
9
+ end
10
+ end
11
+ end
@@ -1,40 +1,34 @@
1
+ require "forwardable"
1
2
  module Queuel
2
3
  module IronMq
3
- class Engine
4
- IronMqMissingError = Class.new(StandardError)
5
-
6
- def initialize(credentials = {})
7
- self.credentials = credentials
8
- self.memoized_queues = {}
9
- end
4
+ class Engine < Base::Engine
5
+ extend Forwardable
6
+ def_delegators :Queuel, :logger
10
7
 
11
- def queue(which_queue)
12
- memoized_queues[which_queue.to_s] ||= Queue.new(client, which_queue)
13
- end
8
+ IronMqMissingError = Class.new(StandardError)
14
9
 
15
10
  private
16
- attr_accessor :credentials
17
- attr_accessor :memoized_queues
18
-
19
- def client
20
- @client ||= client_proper.new credentials
21
- end
22
11
 
23
12
  def try_typhoeus
24
13
  require 'typhoeus'
14
+ true
25
15
  rescue LoadError
16
+ logger.warn "Typhoeus not found..."
17
+ logger.warn "Typhoeus is recommended for IronMQ"
26
18
  false
27
19
  end
28
20
 
29
- def client_proper
21
+ def client_klass
30
22
  if defined?(::IronMQ::Client)
31
23
  try_typhoeus
32
24
  ::IronMQ::Client
33
25
  else
34
26
  begin
27
+ logger.info "Loading IronMQ..."
35
28
  require 'iron_mq'
36
29
  ::IronMQ::Client
37
30
  rescue LoadError
31
+ logger.error "Couldn't find iron_mq gem"
38
32
  raise(IronMqMissingError)
39
33
  end
40
34
  end
@@ -1,43 +1,22 @@
1
1
  require 'forwardable'
2
2
  module Queuel
3
3
  module IronMq
4
- class Message
4
+ class Message < Base::Message
5
5
  extend Forwardable
6
- def_delegators :message_object, :delete
7
6
 
8
- def self.new_from_iron_mq_object(message_object)
9
- allocate.tap { |instance|
10
- instance.send :initialize_from_iron_mq_object, message_object
11
- }
12
- end
13
-
14
- def initialize_from_iron_mq_object(message_object)
15
- self.message_object = message_object
16
- end
17
- private :initialize_from_iron_mq_object
18
-
19
- def initialize(id, body, queue = nil)
20
- self.id = id
21
- self.body = body
22
- self.queue = queue
7
+ def body
8
+ @body || message_object && message_object.body
23
9
  end
24
10
 
25
- def body
26
- @body || message_object && message_object.msg
11
+ def delete
12
+ message_object.delete
27
13
  end
28
14
 
29
15
  [:id, :queue].each do |delegate|
30
16
  define_method(delegate) do
31
17
  instance_variable_get("@#{delegate}") || message_object && message_object.public_send(delegate)
32
18
  end
33
-
34
- private
35
- attr_writer delegate
36
19
  end
37
-
38
- private
39
- attr_accessor :message_object
40
- attr_writer :body
41
20
  end
42
21
  end
43
22
  end
@@ -1,123 +1,20 @@
1
- require 'timeout'
2
1
  module Queuel
3
2
  module IronMq
4
- class Poller
5
- def initialize(queue, options, block)
6
- self.queue = queue
7
- self.options = options || {}
8
- self.block = block
9
- self.tries = 0
10
- self.continue_looping = true
11
- end
12
-
13
- def poll
14
- choose_looper do |msg|
15
- if msg.nil?
16
- tried
17
- quit_looping! if break_if_nil? || maxed_tried?
18
- sleep(sleep_time)
19
- else
20
- reset_tries
21
- block.call msg
22
- msg.delete
23
- end
24
- !msg.nil?
25
- end
26
- end
27
-
28
- protected
29
- attr_accessor :tries
30
-
3
+ class Poller < Base::Poller
4
+ # Public: poll
31
5
  private
32
- attr_accessor :queue
33
- attr_accessor :args
34
- attr_accessor :options
35
- attr_accessor :block
36
- attr_accessor :continue_looping
37
-
38
- def choose_looper(&loop_block)
39
- timeout? ? timeout_looper(loop_block) : looper(loop_block)
40
- end
41
-
42
- def timeout_looper(loop_block)
43
- Timeout.timeout(timeout) { looper(loop_block) }
44
- rescue Timeout::Error
45
- false
46
- end
47
-
48
- def looper(loop_block)
49
- while continue_looping? do
50
- loop_block.call(pop_new_message)
51
- end
52
- end
53
-
54
- def continue_looping?
55
- !!continue_looping
56
- end
57
-
58
- def quit_looping!
59
- self.continue_looping = false
60
- end
61
-
62
- def timeout
63
- options[:poll_timeout].to_i
64
- end
65
-
66
- def timeout?
67
- timeout > 0
68
- end
69
-
70
- def pop_new_message
71
- queue.pop built_options
72
- end
73
-
74
- def start_sleep_time
75
- 0.1
76
- end
77
-
78
- def sleep_time
79
- tries < 30 ? (start_sleep_time * tries) : 3
80
- end
81
-
82
- def reset_tries
83
- self.tries = 0
84
- end
85
-
86
- def maxed_tried?
87
- tries >= max_fails if max_fails_given?
88
- end
89
-
90
- def max_fails_given?
91
- max_fails > 0
92
- end
93
-
94
- def max_fails
95
- options[:max_consecutive_fails].to_i
96
- end
97
-
98
- def tried
99
- self.tries += 1
100
- end
101
-
102
- def break_if_nil?
103
- !!options.fetch(:break_if_nil, false)
104
- end
105
-
106
- def option_keys
107
- %w[break_if_nil poll_timeout max_consecutive_fails]
108
- end
109
-
110
- def my_options
111
- options.select { |key,_| option_keys.include? key.to_s }
112
- end
113
6
 
114
7
  def built_options
115
- options.merge default_args # intentional direction, force defaults
8
+ options.merge default_options # intentional direction, force defaults
116
9
  end
117
10
 
118
- def default_args
11
+ def default_options
119
12
  { n: 1 }
120
13
  end
14
+
15
+ def peek_options
16
+ { n: self.workers }
17
+ end
121
18
  end
122
19
  end
123
20
  end
@@ -1,36 +1,25 @@
1
1
  require 'queuel/iron_mq/poller'
2
+ require 'queuel/base/queue'
3
+ require 'forwardable'
2
4
  module Queuel
3
5
  module IronMq
4
- class Queue
5
- def initialize(client, queue_name)
6
- self.client = client
7
- self.name = queue_name
8
- end
9
-
10
- # For IronMQ it should just be (message)
11
- def push(*args)
12
- queue_connection.post *args
13
- end
6
+ class Queue < Base::Queue
7
+ extend Forwardable
8
+ def_delegators :queue_connection, :peek
14
9
 
15
- def pop(*args, &block)
16
- bare_message = queue_connection.get(*args)
17
- unless bare_message.nil?
18
- Message.new_from_iron_mq_object(bare_message).tap { |message|
19
- if block_given? && !message.nil?
20
- yield message
21
- message.delete
22
- end
23
- }
24
- end
10
+ def peek(options = {})
11
+ Array(queue_connection.peek(options))
25
12
  end
26
13
 
27
- def receive(options = {}, &block)
28
- Poller.new(self, options, block).poll
14
+ # For IronMQ it should just be (message)
15
+ def push(message)
16
+ queue_connection.post message
29
17
  end
30
18
 
31
19
  private
32
- attr_accessor :client
33
- attr_accessor :name
20
+ def pop_bare_message(options = {})
21
+ queue_connection.get options
22
+ end
34
23
 
35
24
  def queue_connection
36
25
  @queue_connection ||= client.queue(name)