queues-rabbit 0.1.0.beta → 0.1.0.beta.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c58e40e8dbe25821bf98ecb4991f7a60cc1eb6407a2d7c7c31aacd3a195d4235
4
- data.tar.gz: 3d847b462ae575169df2060898d9b70eb9fb27f5413ee655d6a8e982d8ebf30c
3
+ metadata.gz: 4ecb7575f981fcc564af562d2938d6cbf15d1b02f382b87aea516fb5e78b1c0f
4
+ data.tar.gz: 7a22c34478ecc86039a727085af54e71944f241caf032450ce8f593ffa20ce8b
5
5
  SHA512:
6
- metadata.gz: 2c4f8024b9af1b1996a71336a33e74dbb7936b5d5d59cb90aeb2cf97f80cb1584eef8d020cede67c1a41537eb61ba3de22443f5558fd2544e08585889051d109
7
- data.tar.gz: 7194a44edf4b2e0168fb346bd8803419e1ad647c824cde2b097a199eeef16e67f8f7e8b2c646cc1c8fffb755ed1bbbd7b76d3f1c7d36d5be8cf4f1631871383a
6
+ metadata.gz: 890ac13fa7258b55eb6a20a8d4eb64c6dffa42b847772ebe141ce77c1c1560f4086e8aa23f22250b0e72529d18cb99b25b2ed7f590bbeb6e4849fb8ea6a2b43a
7
+ data.tar.gz: afb3f5467d0af7f99ed3336e62ec7eaac6b500573605fa0b1a26e2b0f7db8930feedc66436c1b4c08cb8369b3b8fa18e2b80d9d51419200660b83f39a43649f1
data/README.md CHANGED
@@ -108,7 +108,7 @@ module Rabbits
108
108
  class Schema < ::Queues::Rabbit::Schema
109
109
  queue Rabbits::Queues::MyQueueOne
110
110
  exchange Rabbits::Queues::MyExchangeOne
111
-
111
+
112
112
  queue Rabbits::Queues::MyQueueTwo
113
113
  exchange Rabbits::Queues::MyExchangeTwo
114
114
  end
@@ -131,7 +131,7 @@ module Rabbits
131
131
  durable: true,
132
132
  prefetch: 1,
133
133
  arguments: {}
134
-
134
+
135
135
  # ...
136
136
  end
137
137
  end
@@ -144,12 +144,14 @@ The `queue` method allows you to define the RabbitMQ queue parameters.
144
144
  - **auto_ack:** When false messages have to be manually acknowledged (or rejected)
145
145
  - **auto_delete:** If true, the queue will be deleted when the last consumer stops consuming.
146
146
  - **durable:** If true, the queue will survive broker restarts, messages in the queue will only survive if they are published as persistent.
147
- - **prefetch:** Specify how many messages to prefetch for consumers (with no_ack is false)
147
+ - **prefetch:** Specify how many messages to prefetch.
148
148
  - **arguments:** Custom arguments, such as queue-ttl etc.
149
149
 
150
- Params **durable**, **auto_delete** and **arguments** are optional, default values are:
151
- - **durable:** true
150
+ Params **auto_ack**, **auto_delete**, **durable**, **prefetch** and **arguments** are optional, default values are:
151
+ - **auto_ack:** true
152
152
  - **auto_delete:** false
153
+ - **durable:** true
154
+ - **prefetch:** 1
153
155
  - **arguments:** {}
154
156
 
155
157
  (Remember to register the queue class to the Schema, more details [here](#schema))
@@ -168,7 +170,7 @@ module Rabbits
168
170
  durable: true,
169
171
  prefetch: 1,
170
172
  arguments: {}
171
-
173
+
172
174
  def consume(message)
173
175
  # do something with the message
174
176
  end
@@ -195,7 +197,7 @@ module Rabbits
195
197
  class MyQueue < ::Queues::Rabbit::Queue
196
198
  queue 'my.queue',
197
199
  auto_ack: false,
198
-
200
+
199
201
  def consume(message)
200
202
  puts message.body
201
203
  message.ack
@@ -315,9 +317,9 @@ module Rabbits
315
317
  durable: true,
316
318
  prefetch: 1,
317
319
  arguments: {}
318
-
320
+
319
321
  bind Rabbits::Exchanges::MyExchange, 'my.binding.key', arguments: {}
320
-
322
+
321
323
  # ...
322
324
  end
323
325
  end
@@ -363,7 +365,7 @@ module Rabbits
363
365
  durable: true,
364
366
  internal: false,
365
367
  arguments: {}
366
-
368
+
367
369
  bind Rabbits::Exchanges::MyExchangeTwo, 'my.binding.key', arguments: {}
368
370
  end
369
371
  end
@@ -443,6 +445,15 @@ A good way to implement queues subscription is to use [Rails runners](https://gu
443
445
  rails runner -e production "Rabbits::Queues::MyQueue.subscribe"
444
446
  ```
445
447
 
448
+ If you are running the queues in development mode, there could be some problems due to the Rails lazy loading. To solve this issue, you can add the following lines to `config/environments/development.rb`:
449
+
450
+ ```Ruby
451
+ config.eager_load_paths += Dir["app/queues/**/*.rb"]
452
+ ActiveSupport::Reloader.to_prepare do
453
+ Dir["app/queues/**/*.rb"].each { |f| require_dependency("#{Dir.pwd}/#{f}") }
454
+ end
455
+ ```
456
+
446
457
  ## Contributing
447
458
 
448
459
  Bug reports and pull requests are welcome on GitHub at https://github.com/LapoElisacci/queues-rabbit.
@@ -7,7 +7,7 @@ module Rabbits
7
7
  auto_ack: false, # Optional
8
8
  auto_delete: false, # Optional
9
9
  durable: true, # Optional
10
- prefetch: 1, # Optional
10
+ prefetch: 1, # Optional (it must be >= batch_size if batch_subscribe is called)
11
11
  arguments: {} # Optional
12
12
 
13
13
  def consume(message)
@@ -16,6 +16,15 @@ module Rabbits
16
16
  rescue
17
17
  message.reject(requeue: false)
18
18
  end
19
+
20
+ def batch_consume(messages)
21
+ puts "Received #{messages.size} messages"
22
+ # do something with the messages
23
+ messages.each(&:ack)
24
+ puts "Acked #{messages.size} messages"
25
+ rescue
26
+ messages.each { |msg| msg.reject(requeue: false) }
27
+ end
19
28
  end
20
29
  end
21
30
  end
@@ -2,6 +2,9 @@
2
2
 
3
3
  module Queues
4
4
  module Rabbit
5
+ #
6
+ # AMQP::Client wrapper class
7
+ #
5
8
  class Client < AMQP::Client; end
6
9
  end
7
10
  end
@@ -6,6 +6,15 @@ module Queues
6
6
  class << self
7
7
  attr_accessor :arguments, :auto_delete, :durable, :internal, :name, :schema, :type
8
8
 
9
+ #
10
+ # Bind an Exchange to another Exchange
11
+ #
12
+ # @param [String] exchange Exchange name
13
+ # @param [String] binding_key Exchange binding key
14
+ # @param [Hash] arguments Message headers to match on (only relevant for header exchanges)
15
+ #
16
+ # @return [Boolean] True if bounded, false otherwise
17
+ #
9
18
  def bind(exchange, binding_key, arguments: {})
10
19
  exchange = exchange < Queues::Rabbit::Exchange ? exchange.name : exchange
11
20
  exchange_instance.bind(exchange, binding_key, arguments: arguments)
@@ -15,6 +24,11 @@ module Queues
15
24
  false
16
25
  end
17
26
 
27
+ #
28
+ # Delete an Exchange from RabbitMQ
29
+ #
30
+ # @return [Boolean] True if deleted, false otherwise
31
+ #
18
32
  def delete
19
33
  exchange_instance.delete
20
34
  true
@@ -23,6 +37,18 @@ module Queues
23
37
  false
24
38
  end
25
39
 
40
+ #
41
+ # Declare an Exchange
42
+ #
43
+ # @param [String] name Exchange name
44
+ # @param [String] type Exchange type
45
+ # @param [Hash] arguments Exchange custom arguments
46
+ # @param [Boolean] auto_delete If true, the exchange will be deleted when the last queue/exchange gets unbounded.
47
+ # @param [Boolean] durable If true, the exchange will persist between broker restarts, also a required for persistent messages.
48
+ # @param [Boolean] internal If true, the messages can't be pushed directly to the exchange.
49
+ #
50
+ # @return [Queues::Rabbit::Exchange] Exchange class
51
+ #
26
52
  def exchange(name, type, arguments: {}, auto_delete: false, durable: true, internal: false)
27
53
  self.arguments = arguments
28
54
  self.auto_delete = auto_delete
@@ -34,16 +60,8 @@ module Queues
34
60
  self
35
61
  end
36
62
 
37
- def exchange_instance
38
- @@exchange_instance ||= schema.client_instance.exchange(name, type, arguments: arguments, auto_delete: auto_delete, durable: durable, internal: internal)
39
- end
40
-
41
- def logger
42
- @@logger ||= Queues::Rabbit::Logger.new(name, Queues::Rabbit.log_level)
43
- end
44
-
45
63
  #
46
- # <Description>
64
+ # Publish a message to the Exchange
47
65
  #
48
66
  # @param [String] body The message body, can be a string or either a byte array
49
67
  # @param [String] routing_key The routing key to route the message to bounded queues
@@ -74,6 +92,15 @@ module Queues
74
92
  false
75
93
  end
76
94
 
95
+ #
96
+ # Unbind the Exchange from another Exchange
97
+ #
98
+ # @param [String] exchange The exchange name to unbind
99
+ # @param [String] binding_key The exchange binding key
100
+ # @param [Hash] arguments Message headers to match on (only relevant for header exchanges)
101
+ #
102
+ # @return [Boolean] True if unbound, false otherwise
103
+ #
77
104
  def unbind(exchange, binding_key, arguments: {})
78
105
  exchange = exchange < Queues::Rabbit::Exchange ? exchange.name : exchange
79
106
  exchange_instance.unbind(exchange, binding_key, arguments: arguments)
@@ -82,6 +109,26 @@ module Queues
82
109
  logger.error_with_report "Unable to unbind '#{name}' to '#{exchange}' with key '#{binding_key}' and arguments: '#{arguments}': #{e.message}."
83
110
  false
84
111
  end
112
+
113
+ private
114
+
115
+ #
116
+ # Return the Exchange instance
117
+ #
118
+ # @return [AMQP::Client::Exchange] Exchange instance
119
+ #
120
+ def exchange_instance
121
+ @@exchange_instance ||= schema.client_instance.exchange(name, type, arguments: arguments, auto_delete: auto_delete, durable: durable, internal: internal)
122
+ end
123
+
124
+ #
125
+ # Return the logger instance
126
+ #
127
+ # @return [Queues::Rabbit::Logger] Logger instance
128
+ #
129
+ def logger
130
+ @@logger ||= Queues::Rabbit::Logger.new(name, Queues::Rabbit.log_level)
131
+ end
85
132
  end
86
133
  end
87
134
  end
@@ -12,11 +12,22 @@ module Queues
12
12
  @std.level = ::Logger::INFO
13
13
  end
14
14
 
15
+ #
16
+ # Log an error with attached report string.
17
+ #
18
+ # @param [String] message Message to log
19
+ #
15
20
  def error_with_report(message)
16
21
  @logger.error { message }
17
22
  @logger.error { 'Please report to https://github.com/LapoElisacci/queues-rabbit if needed.' }
18
23
  end
19
24
 
25
+ #
26
+ # Log the passed message to STDOUT
27
+ #
28
+ # @param [String] message Message to log
29
+ # @param [Symbol] level Log level
30
+ #
20
31
  def stdout(message, level = :info)
21
32
  @std.send(level, message)
22
33
  end
@@ -2,6 +2,9 @@
2
2
 
3
3
  module Queues
4
4
  module Rabbit
5
+ #
6
+ # AMQP::Client::Message wrapper class
7
+ #
5
8
  class Message < AMQP::Client::Message
6
9
  def initialize(message)
7
10
  message.instance_variables.each do |variable|
@@ -6,6 +6,15 @@ module Queues
6
6
  class << self
7
7
  attr_accessor :arguments, :auto_delete, :durable, :name, :no_ack, :prefetch, :schema
8
8
 
9
+ #
10
+ # Bind a Queue to an Exchange
11
+ #
12
+ # @param [String] exchange Exchange name
13
+ # @param [String] binding_key Exchange binding key
14
+ # @param [Hash] arguments Message headers to match on (only relevant for header exchanges)
15
+ #
16
+ # @return [Boolean] True if bounded, false otherwise
17
+ #
9
18
  def bind(exchange, binding_key, arguments: {})
10
19
  exchange = exchange < Queues::Rabbit::Exchange ? exchange.name : exchange
11
20
  queue_instance.bind(exchange, binding_key, arguments: arguments)
@@ -15,6 +24,19 @@ module Queues
15
24
  false
16
25
  end
17
26
 
27
+ def consume(_message)
28
+ raise NoMethodError.new("Method #{__method__} must be defined to subscribe a queue!")
29
+ end
30
+
31
+ def batch_consume(_messages)
32
+ raise NoMethodError.new("Method #{__method__} must be defined to batch-subscribe a queue!")
33
+ end
34
+
35
+ #
36
+ # Delete a Queue from RabbitMQ
37
+ #
38
+ # @return [Boolean] True if delete, false otherwise
39
+ #
18
40
  def delete
19
41
  queue_instance.delete
20
42
  true
@@ -23,10 +45,18 @@ module Queues
23
45
  false
24
46
  end
25
47
 
26
- def logger
27
- @@logger ||= Queues::Rabbit::Logger.new(name, Queues::Rabbit.log_level)
28
- end
29
-
48
+ #
49
+ # Declare a Queue
50
+ #
51
+ # @param [String] name Queue name
52
+ # @param [Hash] arguments Custom arguments, such as queue-ttl etc.
53
+ # @param [Boolean] auto_ack When false messages have to be manually acknowledged (or rejected)
54
+ # @param [Boolean] auto_delete If true, the queue will be deleted when the last consumer stops consuming.
55
+ # @param [Boolean] durable If true, the queue will survive broker restarts, messages in the queue will only survive if they are published as persistent.
56
+ # @param [Integer] prefetch Specify how many messages to prefetch
57
+ #
58
+ # @return [Queues::Rabbit::Queue] Queue class
59
+ #
30
60
  def queue(name, arguments: {}, auto_ack: true, auto_delete: false, durable: true, prefetch: 1)
31
61
  self.arguments = arguments
32
62
  self.auto_delete = auto_delete
@@ -37,12 +67,8 @@ module Queues
37
67
  self
38
68
  end
39
69
 
40
- def queue_instance
41
- @@queue_instance ||= schema.client_instance.queue(name, arguments: arguments, auto_delete: auto_delete, durable: durable)
42
- end
43
-
44
70
  #
45
- # <Description>
71
+ # Publish a message to the Queue
46
72
  #
47
73
  # @param [String] body The message body, can be a string or either a byte array
48
74
  # @param [Hash] properties Request properties
@@ -62,7 +88,7 @@ module Queues
62
88
  # @option properties [String] :type Can indicate what kind of message this is
63
89
  # @option properties [String] :user_id Used to identify the user that published the message
64
90
  #
65
- # @return [Boolean] true if published, false otherwise
91
+ # @return [Boolean] True if published, false otherwise
66
92
  #
67
93
  def publish(body, **properties)
68
94
  queue_instance.publish(body, **properties)
@@ -72,6 +98,11 @@ module Queues
72
98
  false
73
99
  end
74
100
 
101
+ #
102
+ # Purge / Empty a queue from RabbitMQ
103
+ #
104
+ # @return [Boolean] True if purged, false otherwise
105
+ #
75
106
  def purge
76
107
  queue_instance.purge
77
108
  true
@@ -80,6 +111,9 @@ module Queues
80
111
  false
81
112
  end
82
113
 
114
+ #
115
+ # Subscribe to a Queue
116
+ #
83
117
  def subscribe
84
118
  logger.info { "Subscribing to queue #{name}" }
85
119
  consumer = new
@@ -99,6 +133,61 @@ module Queues
99
133
  false
100
134
  end
101
135
 
136
+ #
137
+ # Subscribe to the queue with a batch reading.
138
+ # This method will block.
139
+ #
140
+ # @param [Integer] batch_size Batch size
141
+ # @param [ActiveSupport::Duration] batch_timeout Batch timeout, that the time interval in which the batch will be emptied even if not full.
142
+ #
143
+ # @return [FalseClass] if errors
144
+ #
145
+ def batch_subscribe(batch_size:, batch_timeout:)
146
+ raise StandardError.new('Batch size must be a positive integer') if batch_size.to_i <= 0
147
+ raise StandardError.new("Batch size must be less or equal than prefetch: got batch_size=#{batch_size} and prefetch=#{prefetch}") if batch_size > prefetch
148
+
149
+ logger.info "Subscribing to queue #{name} with a batch size #{batch_size}"
150
+ consumer = new
151
+ batch = []
152
+ # Batched subscribe must be performed only by one thread
153
+ # Auto-acking is done manually, otherwise batching is not possible
154
+ queue_instance.subscribe(worker_threads: 1, no_ack: false, prefetch: prefetch) do |message|
155
+ if message.properties.type == 'timeout'
156
+ message.ack # Remove the timeout message from the queue
157
+ min_batch_size = 0
158
+ else
159
+ batch << Queues::Rabbit::Message.new(message)
160
+ min_batch_size = batch_size
161
+ end
162
+ if batch.size > 0 && batch.size >= min_batch_size
163
+ batch.each(&:ack) if no_ack
164
+ consumer.batch_consume(batch)
165
+ batch = []
166
+ end
167
+ rescue Exception => e
168
+ logger.error { e.message }
169
+ logger.stdout e.message, :error
170
+ end
171
+
172
+ loop do
173
+ logger.stdout "Connection to #{name} alive."
174
+ sleep batch_timeout
175
+ queue_instance.publish('', type: 'timeout', persistent: false, espiration: 3.seconds)
176
+ end
177
+ rescue Exception => e
178
+ logger.error_with_report "Unable to connect to #{name}: #{e.message}."
179
+ false
180
+ end
181
+
182
+ #
183
+ # Unbind a Queue from an Exchange
184
+ #
185
+ # @param [String] exchange Exchange name
186
+ # @param [String] binding_key Exchange binding key
187
+ # @param [Hash] arguments Message headers to match on (only relevant for header exchanges)
188
+ #
189
+ # @return [Boolean] True if unbounded, false otherwise.
190
+ #
102
191
  def unbind(exchange, binding_key, arguments: {})
103
192
  exchange = exchange < Queues::Rabbit::Exchange ? exchange.name : exchange
104
193
  queue_instance.unbind(exhange, binding_key, arguments: arguments)
@@ -107,10 +196,26 @@ module Queues
107
196
  logger.error_with_report "Unable to unbind '#{name}' to '#{exchange}' with key '#{binding_key}' and arguments: '#{arguments}': #{e.message}."
108
197
  false
109
198
  end
110
- end
111
199
 
112
- def consume(_message)
113
- raise NoMethodError.new("Method #{__method__} must be defined to subscribe a queue!")
200
+ private
201
+
202
+ #
203
+ # Return the logger instance
204
+ #
205
+ # @return [Queues::Rabbit::Logger] Logger instance
206
+ #
207
+ def logger
208
+ @@logger ||= Queues::Rabbit::Logger.new(name, Queues::Rabbit.log_level)
209
+ end
210
+
211
+ #
212
+ # Return the Queue instance
213
+ #
214
+ # @return [AMQP::Client::Client] Queue instance
215
+ #
216
+ def queue_instance
217
+ @@queue_instance ||= schema.client_instance.queue(name, arguments: arguments, auto_delete: auto_delete, durable: durable)
218
+ end
114
219
  end
115
220
  end
116
221
  end
@@ -3,20 +3,34 @@
3
3
  module Queues
4
4
  module Rabbit
5
5
  class Schema
6
- include ActiveModel::Model
7
6
  class << self
8
7
  attr_accessor :client, :exchanges, :queues
9
8
 
9
+ #
10
+ # Return the client instance
11
+ #
12
+ # @return [AMQP::Client] Client instance
13
+ #
10
14
  def client_instance
11
15
  @@client_instance ||= client.start
12
16
  end
13
17
 
18
+ #
19
+ # Register an Exchange
20
+ #
21
+ # @param [Queues::Rabbit::Exchange] klass Exchange class to register
22
+ #
14
23
  def exchange(klass)
15
24
  self.exchanges ||= []
16
25
  self.exchanges << klass
17
26
  klass.schema = self
18
27
  end
19
28
 
29
+ #
30
+ # Register a Queue
31
+ #
32
+ # @param [Queues::Rabbit::Queue] klass Queue class to register
33
+ #
20
34
  def queue(klass)
21
35
  self.queues ||= []
22
36
  self.queues << klass
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Queues
4
4
  module Rabbit
5
- VERSION = '0.1.0.beta'
5
+ VERSION = '0.1.0.beta.1'
6
6
  end
7
7
  end
data/lib/queues/rabbit.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_model'
4
3
  require 'active_support'
5
4
  require 'amqp-client'
6
5
  require 'logger'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: queues-rabbit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.beta
4
+ version: 0.1.0.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lapo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-01 00:00:00.000000000 Z
11
+ date: 2022-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: amqp-client
@@ -38,7 +38,8 @@ dependencies:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.1.0.beta
41
- description: ''
41
+ description: The gem allows you to integrate RabbitMQ into a Rails application in
42
+ a Ruby Style fashion.
42
43
  email:
43
44
  - lapoelisacci@gmail.com
44
45
  executables: []
@@ -91,5 +92,5 @@ requirements: []
91
92
  rubygems_version: 3.2.22
92
93
  signing_key:
93
94
  specification_version: 4
94
- summary: RabbitMQ Driver for Rails Queues
95
+ summary: A Rails implementation of RabbitMQ
95
96
  test_files: []