ears 0.3.0 → 0.4.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c122db057142136f9cbd1a20760711e0391494df1fa4d57eec4182ad5f748903
4
- data.tar.gz: 92301abc303c0623cdcf33a24b20923a86514c3c3cbf0fff6515bdaecd66fd47
3
+ metadata.gz: 6b46a87dfab0c7135fbb74534070d44ef48b693f7dfd848b3df9d68ceb915b29
4
+ data.tar.gz: 3880d424b10a408d49a51dfb1f6e22650ede251d23b642ee5983e42cfa4c0b82
5
5
  SHA512:
6
- metadata.gz: b32705e3c3a137514451440dc8e151ab2181f938f7e772a47d698f7bec115165b8a7c867a163cbd1f6507f0b200fba070babd1d31ece9a3571f764eab6b27830
7
- data.tar.gz: 606693da62fc02b53efcde24be5b92ed0eafd10a83795cc8fd08c56cfa0f496bdbacad8b7350fddfa3080e87982307567c994bae5c3aab04b8ebc4766b3b592d
6
+ metadata.gz: d7c3368085535d3c385778d0711b89755dbe4f5e2fd8f91fb986c573bf4beeb8a86253c00e8238be5cc38c78e5fbe727dc403500c16752985ac7e04e208d9075
7
+ data.tar.gz: ba1ee1434321eb492948027c31d7180d1254f89bcd756cf3edd11f64abdaae9c553841bacc8fef1607939ac6aa8341cb5c9751441c4b48f807bf9ba73e4a8cf4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.3 (2021-07-26)
4
+
5
+ ### Changes
6
+
7
+ - Ears will not exit gracefully on unhandled errors anymore. You have to take care of proper flushing and cleanup yourself (see README for example).
8
+
9
+ ## 0.3.3 (2021-06-07)
10
+
11
+ ### Changes
12
+
13
+ - Ears will now exit gracefully on unhandled errors to give the application a chance to do flushing and cleanup work
14
+
15
+ ## 0.3.2 (2021-05-21)
16
+
17
+ ### Changes
18
+
19
+ - added YARD documentation and usage instructions to README
20
+ - introduced `Ears::Middleware` as an abstract base class for middlewares
21
+
22
+ ## 0.3.1 (2021-05-07)
23
+
24
+ ### Changes
25
+
26
+ - internally, user-defined `Ears::Consumer` instances are now wrapped by `Ears::ConsumerWrapper` to make the user-defined class smaller and easier to test
27
+
3
28
  ## 0.3.0 (2021-05-07)
4
29
 
5
30
  ### Breaking
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ears (0.3.0)
4
+ ears (0.4.3)
5
5
  bunny
6
6
  multi_json
7
7
 
@@ -19,6 +19,7 @@ GEM
19
19
  ast (~> 2.4.1)
20
20
  rainbow (3.0.0)
21
21
  rake (13.0.3)
22
+ redcarpet (3.5.1)
22
23
  regexp_parser (2.1.1)
23
24
  rexml (3.2.4)
24
25
  rspec (3.10.0)
@@ -52,6 +53,7 @@ GEM
52
53
  rubocop-ast (>= 1.1.0)
53
54
  ruby-progressbar (1.11.0)
54
55
  unicode-display_width (2.0.0)
56
+ yard (0.9.26)
55
57
 
56
58
  PLATFORMS
57
59
  x86_64-darwin-20
@@ -60,10 +62,12 @@ PLATFORMS
60
62
  DEPENDENCIES
61
63
  ears!
62
64
  rake
65
+ redcarpet
63
66
  rspec
64
67
  rubocop
65
68
  rubocop-rake
66
69
  rubocop-rspec
70
+ yard
67
71
 
68
72
  BUNDLED WITH
69
73
  2.2.3
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ears
2
2
 
3
- TODO: Write description here
3
+ `Ears` is a small, simple library for writing RabbitMQ consumers.
4
4
 
5
5
  ## Installation
6
6
 
@@ -20,16 +20,151 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- TODO: Write usage instructions here
23
+ ### Basic usage
24
+
25
+ First, you should configure where `Ears` should connect to.
26
+
27
+ ```ruby
28
+ require 'ears'
29
+
30
+ Ears.configure do |config|
31
+ config.rabbitmq_url = 'amqp://user:password@myrmq:5672'
32
+ end
33
+ ```
34
+
35
+ Next, define your exchanges, queues, and consumers by calling `Ears.setup`.
36
+
37
+ ```ruby
38
+ Ears.setup do
39
+ # define a durable topic exchange
40
+ my_exchange = exchange('my_exchange', :topic, durable: true)
41
+
42
+ # define a queue
43
+ my_queue = queue('my_queue', durable: true)
44
+
45
+ # bind your queue to the exchange
46
+ my_queue.bind(my_exchange, routing_key: 'my.routing.key')
47
+
48
+ # define a consumer that handles messages for that queue
49
+ consumer(my_queue, MyConsumer)
50
+ end
51
+ ```
52
+
53
+ Finally, you need to implement `MyConsumer` by subclassing `Ears::Consumer`.
54
+
55
+ ```ruby
56
+ class MyConsumer < Ears::Consumer
57
+ def work(delivery_info, metadata, payload)
58
+ message = JSON.parse(payload)
59
+ do_stuff(message)
60
+
61
+ ack
62
+ end
63
+ end
64
+ ```
65
+
66
+ And, do not forget to run it. Be prepared that unhandled errors will be reraised. So, take care of cleanup work.
67
+
68
+ ```ruby
69
+ begin
70
+ Ears.run!
71
+ ensure
72
+ # all your cleanup work goes here...
73
+ end
74
+ ```
75
+
76
+ At the end of the `#work` method, you must always return `ack`, `reject`, or `requeue` to signal what should be done with the message.
77
+
78
+ ### Middlewares
79
+
80
+ `Ears` supports middlewares that you can use for recurring tasks that you don't always want to reimplement. It comes with some built-in middlewares:
81
+
82
+ - `Ears::JSON` for automatically parsing JSON payloads
83
+ - `Ears::Appsignal` for automatically wrapping `#work` in an Appsignal transaction
84
+
85
+ You can use a middleware by just calling `use` with the middleware you want to register in your consumer.
86
+
87
+ ```ruby
88
+ require 'ears/middlewares/json'
89
+
90
+ class MyConsumer < Ears::Consumer
91
+ # register the JSON middleware and don't symbolize keys (this can be omitted, the default is true)
92
+ use Ears::Middlewares::JSON, symbolize_keys: false
93
+
94
+ def work(delivery_info, metadata, payload)
95
+ return ack unless payload['data'].nil? # this now just works
96
+ end
97
+ end
98
+ ```
99
+
100
+ If you want to implement your own middleware, just subclass `Ears::Middleware` and implement `#call` (and if you need it `#initialize`).
101
+
102
+ ```ruby
103
+ class MyMiddleware < Ears::Middleware
104
+ def initialize(opts = {})
105
+ @my_option = opts.fetch(:my_option, nil)
106
+ end
107
+
108
+ def call(delivery_info, metadata, payload, app)
109
+ do_stuff
110
+
111
+ # always call the next middleware in the chain or your consumer will never be called
112
+ app.call(delivery_info, metadata, payload)
113
+ end
114
+ end
115
+ ```
116
+
117
+ ### Multiple threads
118
+
119
+ If you need to handle a lot of messages, you might want to have multiple instances of the same consumer all working on a dedicated thread. This is supported out of the box. You just have to define how many consumers you want when calling `consumer` in `Ears.setup`.
120
+
121
+ ```ruby
122
+ Ears.setup do
123
+ my_exchange = exchange('my_exchange', :topic, durable: true)
124
+ my_queue = queue('my_queue', durable: true)
125
+ my_queue.bind(my_exchange, routing_key: 'my.routing.key')
126
+
127
+ # this will instantiate MyConsumer 10 times and run every instance on a dedicated thread
128
+ consumer(my_queue, MyConsumer, 10)
129
+ end
130
+ ```
131
+
132
+ It may also be interesting for you to increase the prefetch amount. The default prefetch amount is 1, but if you have a lot of very small, fast to process messages, a higher prefetch is a good idea. Just set it when defining your consumer.
133
+
134
+ ```ruby
135
+ Ears.setup do
136
+ my_exchange = exchange('my_exchange', :topic, durable: true)
137
+ my_queue = queue('my_queue', durable: true)
138
+ my_queue.bind(my_exchange, routing_key: 'my.routing.key')
139
+
140
+ # this will instantiate one consumer but with a prefetch value of 10
141
+ consumer(my_queue, MyConsumer, 1, prefetch: 10)
142
+ end
143
+ ```
144
+
145
+ ### Setting arbitrary exchange/queue parameters
146
+
147
+ If you need some custom arguments on your exchange or queue, you can just pass these to `queue` or `exchange` inside `Ears.setup`. These are then just passed on to `Bunny::Queue` and `Bunny::Exchange`.
148
+
149
+ ```ruby
150
+ Ears.setup do
151
+ my_queue =
152
+ queue('my_queue', durable: true, arguments: { 'x-message-ttl' => 10_000 })
153
+ end
154
+ ```
155
+
156
+ ## Documentation
157
+
158
+ If you need more in-depth information, look at [our API documentation](https://www.rubydoc.info/gems/ears).
24
159
 
25
160
  ## Contributing
26
161
 
27
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ears. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/ears/blob/master/CODE_OF_CONDUCT.md).
162
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ivx/ears. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/ivx/ears/blob/master/CODE_OF_CONDUCT.md).
28
163
 
29
164
  ## License
30
165
 
31
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
166
+ The gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
32
167
 
33
168
  ## Code of Conduct
34
169
 
35
- Everyone interacting in the Ears project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/ears/blob/master/CODE_OF_CONDUCT.md).
170
+ Everyone interacting in the Ears project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/ivx/ears/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
+ require 'yard'
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
5
6
 
7
+ YARD::Rake::YardocTask.new { |t| t.files = ['lib/**/*.rb'] }
8
+
6
9
  task default: :spec
data/ears.gemspec CHANGED
@@ -19,6 +19,12 @@ Gem::Specification.new do |spec|
19
19
  spec.metadata['changelog_uri'] =
20
20
  'https://github.com/ivx/ears/blob/master/CHANGELOG.md'
21
21
 
22
+ spec.post_install_message =
23
+ '
24
+ Ears: the new version changed the exit behaviour in case of uncaught exceptions.
25
+ You may want to have a look into the CHANGELOG!
26
+ '
27
+
22
28
  spec.files =
23
29
  Dir.chdir(File.expand_path('..', __FILE__)) do
24
30
  `git ls-files -z`.split("\x0").reject do |f|
@@ -33,8 +39,10 @@ Gem::Specification.new do |spec|
33
39
  spec.add_dependency 'multi_json'
34
40
 
35
41
  spec.add_development_dependency 'rake'
42
+ spec.add_development_dependency 'redcarpet'
36
43
  spec.add_development_dependency 'rspec'
37
44
  spec.add_development_dependency 'rubocop'
38
45
  spec.add_development_dependency 'rubocop-rake'
39
46
  spec.add_development_dependency 'rubocop-rspec'
47
+ spec.add_development_dependency 'yard'
40
48
  end
data/lib/ears.rb CHANGED
@@ -1,47 +1,70 @@
1
1
  require 'bunny'
2
2
  require 'ears/configuration'
3
3
  require 'ears/consumer'
4
+ require 'ears/middleware'
4
5
  require 'ears/setup'
5
6
  require 'ears/version'
6
7
 
7
8
  module Ears
8
- class Error < StandardError
9
- end
10
-
11
9
  class << self
10
+ # The global configuration for Ears.
11
+ #
12
+ # @return [Ears::Configuration]
12
13
  def configuration
13
14
  @configuration ||= Ears::Configuration.new
14
15
  end
15
16
 
17
+ # Yields the global configuration instance so you can modify it.
18
+ # @yieldparam configuration [Ears::Configuration] The global configuration instance.
16
19
  def configure
17
20
  yield(configuration)
18
21
  end
19
22
 
23
+ # The global RabbitMQ connection used by Ears.
24
+ #
25
+ # @return [Bunny::Session]
20
26
  def connection
21
27
  @connection ||= Bunny.new.tap { |conn| conn.start }
22
28
  end
23
29
 
30
+ # The channel for the current thread.
31
+ #
32
+ # @return [Bunny::Channel]
24
33
  def channel
25
34
  Thread.current[:ears_channel] ||=
26
35
  connection
27
36
  .create_channel(nil, 1, true)
28
37
  .tap do |channel|
29
38
  channel.prefetch(1)
30
- channel.on_uncaught_exception { |error| Thread.main.raise(error) }
39
+ channel.on_uncaught_exception { |error| Ears.error!(error) }
31
40
  end
32
41
  end
33
42
 
43
+ # Used to set up your exchanges, queues and consumers. See {Ears::Setup} for implementation details.
34
44
  def setup(&block)
35
45
  Ears::Setup.new.instance_eval(&block)
36
46
  end
37
47
 
48
+ # Blocks the calling thread until +SIGTERM+ or +SIGINT+ is received.
49
+ # Used to keep the process alive while processing messages.
38
50
  def run!
39
51
  running = true
40
52
  Signal.trap('INT') { running = false }
41
53
  Signal.trap('TERM') { running = false }
42
- sleep 1 while running
54
+ sleep 1 while running && !@uncaught_error_occurred
55
+ raise @error if @uncaught_error_occurred
56
+ end
57
+
58
+ # Signals that an uncaught error has occurred and the process should be stopped.
59
+ #
60
+ # @param [Exception] error The unhandled error that occurred.
61
+ def error!(error)
62
+ puts(error.full_message)
63
+ @uncaught_error_occurred = true
64
+ @error = error
43
65
  end
44
66
 
67
+ # Used internally for testing.
45
68
  def reset!
46
69
  @connection = nil
47
70
  Thread.current[:ears_channel] = nil
@@ -1,7 +1,9 @@
1
1
  module Ears
2
+ # The class representing the global {Ears} configuration.
2
3
  class Configuration
3
4
  DEFAULT_RABBITMQ_URL = 'amqp://guest:guest@localhost:5672'
4
5
 
6
+ # @return [String] the connection string for RabbitMQ.
5
7
  attr_accessor :rabbitmq_url
6
8
 
7
9
  def initialize
data/lib/ears/consumer.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  require 'bunny'
2
2
 
3
3
  module Ears
4
- class Consumer < Bunny::Consumer
4
+ # The abstract base class for consumers processing messages from queues.
5
+ # @abstract Subclass and override {#work} to implement.
6
+ class Consumer
7
+ # Error that is raised when an invalid value is returned from {#work}
5
8
  class InvalidReturnError < StandardError
6
9
  def initialize(value)
7
10
  super(
@@ -10,18 +13,38 @@ module Ears
10
13
  end
11
14
  end
12
15
 
16
+ # List of registered middlewares. Register new middlewares with {.use}.
17
+ # @return [Array<Ears::Middleware>]
13
18
  def self.middlewares
14
19
  @middlewares ||= []
15
20
  end
16
21
 
22
+ # Registers a new middleware by instantiating +middleware+ and passing it +opts+.
23
+ #
24
+ # @param [Class<Ears::Middleware>] middleware The middleware class to instantiate and register.
25
+ # @param [Hash] opts The options for instantiating the middleware.
17
26
  def self.use(middleware, opts = {})
18
27
  middlewares << middleware.new(opts)
19
28
  end
20
29
 
30
+ # The method that is called when a message from the queue is received.
31
+ # Keep in mind that the parameters received can be altered by middlewares!
32
+ #
33
+ # @param [Bunny::DeliveryInfo] delivery_info The delivery info of the message.
34
+ # @param [Bunny::MessageProperties] metadata The metadata of the message.
35
+ # @param [String] payload The payload of the message.
36
+ #
37
+ # @return [:ack, :reject, :requeue] A symbol denoting what should be done with the message.
21
38
  def work(delivery_info, metadata, payload)
22
39
  raise NotImplementedError
23
40
  end
24
41
 
42
+ # Wraps #work to add middlewares. This is being called by Ears when a message is received for the consumer.
43
+ #
44
+ # @param [Bunny::DeliveryInfo] delivery_info The delivery info of the received message.
45
+ # @param [Bunny::MessageProperties] metadata The metadata of the received message.
46
+ # @param [String] payload The payload of the received message.
47
+ # @raise [InvalidReturnError] if you return something other than +:ack+, +:reject+ or +:requeue+ from {#work}.
25
48
  def process_delivery(delivery_info, metadata, payload)
26
49
  self.class.middlewares.reverse.reduce(
27
50
  work_proc,
@@ -32,14 +55,24 @@ module Ears
32
55
 
33
56
  protected
34
57
 
58
+ # Helper method to ack a message.
59
+ #
60
+ # @return [:ack]
35
61
  def ack
36
62
  :ack
37
63
  end
38
64
 
65
+ # Helper method to reject a message.
66
+ #
67
+ # @return [:reject]
68
+ #
39
69
  def reject
40
70
  :reject
41
71
  end
42
72
 
73
+ # Helper method to requeue a message.
74
+ #
75
+ # @return [:requeue]
43
76
  def requeue
44
77
  :requeue
45
78
  end
@@ -49,7 +82,7 @@ module Ears
49
82
  def work_proc
50
83
  ->(delivery_info, metadata, payload) do
51
84
  work(delivery_info, metadata, payload).tap do |result|
52
- process_result(result, delivery_info.delivery_tag)
85
+ verify_result(result)
53
86
  end
54
87
  end
55
88
  end
@@ -60,15 +93,8 @@ module Ears
60
93
  end
61
94
  end
62
95
 
63
- def process_result(result, delivery_tag)
64
- case result
65
- when :ack
66
- channel.ack(delivery_tag, false)
67
- when :reject
68
- channel.reject(delivery_tag)
69
- when :requeue
70
- channel.reject(delivery_tag, true)
71
- else
96
+ def verify_result(result)
97
+ unless %i[ack reject requeue].include?(result)
72
98
  raise InvalidReturnError, result
73
99
  end
74
100
  end
@@ -0,0 +1,44 @@
1
+ require 'bunny'
2
+
3
+ module Ears
4
+ # Wraps the user-defined consumer to provide the expected interface to Bunny.
5
+ class ConsumerWrapper < Bunny::Consumer
6
+ # @param [Ears::Consumer] consumer The user-defined consumer implementation derived from {Ears::Consumer}.
7
+ # @param [Bunny::Channel] channel The channel used for the consumer.
8
+ # @param [Bunny::Queue] queue The queue the consumer is subscribed to.
9
+ # @param [String] consumer_tag A string identifying the consumer instance.
10
+ # @param [Hash] arguments Arguments that are passed on to +Bunny::Consumer.new+.
11
+ def initialize(consumer, channel, queue, consumer_tag, arguments = {})
12
+ @consumer = consumer
13
+ super(channel, queue, consumer_tag, false, false, arguments)
14
+ end
15
+
16
+ # Called when a message is received from the subscribed queue.
17
+ #
18
+ # @param [Bunny::DeliveryInfo] delivery_info The delivery info of the received message.
19
+ # @param [Bunny::MessageProperties] metadata The metadata of the received message.
20
+ # @param [String] payload The payload of the received message.
21
+ def process_delivery(delivery_info, metadata, payload)
22
+ consumer
23
+ .process_delivery(delivery_info, metadata, payload)
24
+ .tap { |result| process_result(result, delivery_info.delivery_tag) }
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :consumer
30
+
31
+ def process_result(result, delivery_tag)
32
+ case result
33
+ when :ack
34
+ channel.ack(delivery_tag, false)
35
+ when :reject
36
+ channel.reject(delivery_tag)
37
+ when :requeue
38
+ channel.reject(delivery_tag, true)
39
+ else
40
+ raise InvalidReturnError, result
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ module Ears
2
+ # The abstract base class for middlewares.
3
+ # @abstract Subclass and override {#call} (and maybe +#initialize+) to implement.
4
+ class Middleware
5
+ # Invokes the middleware.
6
+ #
7
+ # @param [Bunny::DeliveryInfo] delivery_info The delivery info of the received message.
8
+ # @param [Bunny::MessageProperties] metadata The metadata of the received message.
9
+ # @param [String] payload The payload of the received message.
10
+ # @param app The next middleware to call or the actual consumer instance.
11
+ def call(delivery_info, metadata, payload, app)
12
+ raise NotImplementedError
13
+ end
14
+ end
15
+ end
@@ -1,9 +1,14 @@
1
+ require 'ears/middleware'
2
+
1
3
  module Ears
2
4
  module Middlewares
3
- class Appsignal
4
- attr_reader :transaction_name, :class_name, :method
5
-
5
+ # A middleware that automatically wraps {Ears::Consumer#work} in an Appsignal transaction.
6
+ class Appsignal < Middleware
7
+ # @param [Hash] opts The options for the middleware.
8
+ # @option opts [String] :transaction_name The name of the Appsignal transaction.
9
+ # @option opts [String] :class_name The name of the class you want to monitor.
6
10
  def initialize(opts)
11
+ super()
7
12
  @transaction_name = opts.fetch(:transaction_name)
8
13
  @class_name = opts.fetch(:class_name)
9
14
  end
@@ -16,6 +21,10 @@ module Ears
16
21
  queue_start: Time.now.utc,
17
22
  ) { app.call(delivery_info, metadata, payload) }
18
23
  end
24
+
25
+ private
26
+
27
+ attr_reader :transaction_name, :class_name
19
28
  end
20
29
  end
21
30
  end
@@ -1,11 +1,14 @@
1
+ require 'ears/middleware'
1
2
  require 'multi_json'
2
3
 
3
4
  module Ears
4
5
  module Middlewares
5
- class JSON
6
- attr_reader :symbolize_keys
7
-
6
+ # A middleware that automatically parses your JSON payload.
7
+ class JSON < Middleware
8
+ # @param [Hash] opts The options for the middleware.
9
+ # @option opts [Boolean] :symbolize_keys Whether to symbolize the keys of your payload.
8
10
  def initialize(opts = {})
11
+ super()
9
12
  @symbolize_keys = opts.fetch(:symbolize_keys, true)
10
13
  end
11
14
 
@@ -13,6 +16,10 @@ module Ears
13
16
  parsed_payload = MultiJson.load(payload, symbolize_keys: symbolize_keys)
14
17
  app.call(delivery_info, metadata, parsed_payload)
15
18
  end
19
+
20
+ private
21
+
22
+ attr_reader :symbolize_keys
16
23
  end
17
24
  end
18
25
  end
data/lib/ears/setup.rb CHANGED
@@ -1,16 +1,36 @@
1
1
  require 'bunny'
2
2
  require 'ears/consumer'
3
+ require 'ears/consumer_wrapper'
3
4
 
4
5
  module Ears
6
+ # Contains methods used in {Ears.setup} to set up your exchanges, queues and consumers.
5
7
  class Setup
8
+ # Creates a new exchange if it does not already exist.
9
+ #
10
+ # @param [String] name The name of the exchange.
11
+ # @param [Symbol] type The type of the exchange (:direct, :fanout, :topic or :headers).
12
+ # @param [Hash] opts The options for the exchange. These are passed on to +Bunny::Exchange.new+.
13
+ # @return [Bunny::Exchange] The exchange that was either newly created or was already there.
6
14
  def exchange(name, type, opts = {})
7
15
  Bunny::Exchange.new(Ears.channel, type, name, opts)
8
16
  end
9
17
 
18
+ # Creates a new queue if it does not already exist.
19
+ #
20
+ # @param [String] name The name of the queue.
21
+ # @param [Hash] opts The options for the queue. These are passed on to +Bunny::Exchange.new+.
22
+ # @return [Bunny::Queue] The queue that was either newly created or was already there.
10
23
  def queue(name, opts = {})
11
24
  Bunny::Queue.new(Ears.channel, name, opts)
12
25
  end
13
26
 
27
+ # Creates and starts one or many consumers bound to the given queue.
28
+ #
29
+ # @param [Bunny::Queue] queue The queue the consumers should be subscribed to.
30
+ # @param [Class<Ears::Consumer>] consumer_class A class implementing {Ears::Consumer} that holds the consumer behavior.
31
+ # @param [Integer] threads The number of threads that should be used to process messages from the queue.
32
+ # @param [Hash] args The arguments for the consumer. These are passed on to +Bunny::Consumer.new+.
33
+ # @option args [Integer] :prefetch (1) The prefetch count used for this consumer.
14
34
  def consumer(queue, consumer_class, threads = 1, args = {})
15
35
  threads.times do |n|
16
36
  consumer_queue = create_consumer_queue(queue, args)
@@ -27,12 +47,11 @@ module Ears
27
47
  private
28
48
 
29
49
  def create_consumer(queue, consumer_class, args, number)
30
- consumer_class.new(
50
+ ConsumerWrapper.new(
51
+ consumer_class.new,
31
52
  queue.channel,
32
53
  queue,
33
54
  "#{consumer_class.name}-#{number}",
34
- false,
35
- false,
36
55
  args,
37
56
  )
38
57
  end
@@ -43,7 +62,7 @@ module Ears
43
62
  .create_channel(nil, 1, true)
44
63
  .tap do |channel|
45
64
  channel.prefetch(args.fetch(:prefetch, 1))
46
- channel.on_uncaught_exception { |error| Thread.main.raise(error) }
65
+ channel.on_uncaught_exception { |error| Ears.error!(error) }
47
66
  end
48
67
  end
49
68
 
data/lib/ears/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ears
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.3'
3
3
  end
data/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "on-prem-importer",
2
+ "name": "ears",
3
3
  "private": true,
4
4
  "scripts": {
5
5
  "prettify": "prettier \"**/*.{ru,rb,yml,yaml,md,gemspec,json}\" --ignore-path=\".gitignore\"",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ears
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mario Mainz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-07 00:00:00.000000000 Z
11
+ date: 2021-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: redcarpet
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +122,20 @@ dependencies:
108
122
  - - ">="
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: yard
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
111
139
  description: A gem for building RabbitMQ consumers.
112
140
  email:
113
141
  - mario.mainz@invision.de
@@ -131,6 +159,8 @@ files:
131
159
  - lib/ears.rb
132
160
  - lib/ears/configuration.rb
133
161
  - lib/ears/consumer.rb
162
+ - lib/ears/consumer_wrapper.rb
163
+ - lib/ears/middleware.rb
134
164
  - lib/ears/middlewares/appsignal.rb
135
165
  - lib/ears/middlewares/json.rb
136
166
  - lib/ears/setup.rb
@@ -145,7 +175,10 @@ metadata:
145
175
  homepage_uri: https://github.com/ivx/ears
146
176
  source_code_uri: https://github.com/ivx/ears
147
177
  changelog_uri: https://github.com/ivx/ears/blob/master/CHANGELOG.md
148
- post_install_message:
178
+ post_install_message: |2
179
+
180
+ Ears: the new version changed the exit behaviour in case of uncaught exceptions.
181
+ You may want to have a look into the CHANGELOG!
149
182
  rdoc_options: []
150
183
  require_paths:
151
184
  - lib