queues-rabbit 0.1.0.beta → 0.1.0.beta.1

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