amqp-client 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AMQP
4
+ class Client
5
+ # Queue abstraction
6
+ class Queue
7
+ # Should only be initialized from the Client
8
+ # @api private
9
+ def initialize(client, name)
10
+ @client = client
11
+ @name = name
12
+ end
13
+
14
+ # Publish to the queue, wait for confirm
15
+ # @param (see Client#publish)
16
+ # @option (see Client#publish)
17
+ # @raise (see Client#publish)
18
+ # @return [self]
19
+ def publish(body, **properties)
20
+ @client.publish(body, "", @name, **properties)
21
+ self
22
+ end
23
+
24
+ # Subscribe/consume from the queue
25
+ # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
26
+ # @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false
27
+ # @param worker_threads [Integer] Number of threads processing messages,
28
+ # 0 means that the thread calling this method will be blocked
29
+ # @param arguments [Hash] Custom arguments to the consumer
30
+ # @yield [Message] Delivered message from the queue
31
+ # @return [self]
32
+ def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
33
+ @client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
34
+ self
35
+ end
36
+
37
+ # Bind the queue to an exchange
38
+ # @param exchange [String] Name of the exchange to bind to
39
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
40
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
41
+ # @return [self]
42
+ def bind(exchange, binding_key, arguments: {})
43
+ @client.bind(@name, exchange, binding_key, arguments: arguments)
44
+ self
45
+ end
46
+
47
+ # Unbind the queue from an exchange
48
+ # @param exchange [String] Name of the exchange to unbind from
49
+ # @param binding_key [String] Binding key which the queue is bound to the exchange with
50
+ # @param arguments [Hash] Arguments matching the binding that's being removed
51
+ # @return [self]
52
+ def unbind(exchange, binding_key, arguments: {})
53
+ @client.unbind(@name, exchange, binding_key, arguments: arguments)
54
+ self
55
+ end
56
+
57
+ # Purge/empty the queue
58
+ # @return [self]
59
+ def purge
60
+ @client.purge(@name)
61
+ self
62
+ end
63
+
64
+ # Delete the queue
65
+ # @return [nil]
66
+ def delete
67
+ @client.delete_queue(@name)
68
+ nil
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,122 +1,152 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AMQP
4
- # Encode/decode AMQP Tables
5
- module Table
6
- module_function
4
+ class Client
5
+ # Encode and decode an AMQP table to/from hash, only used internally
6
+ # @api private
7
+ module Table
8
+ # Encodes a hash into a byte array
9
+ # @param hash [Hash]
10
+ # @return [String] Byte array
11
+ def self.encode(hash)
12
+ return "" if hash.empty?
7
13
 
8
- def encode(hash)
9
- tbl = ""
10
- hash.each do |k, v|
11
- key = k.to_s
12
- tbl += [key.bytesize, key, encode_field(v)].pack("C a* a*")
14
+ arr = []
15
+ fmt = String.new
16
+ hash.each do |k, value|
17
+ key = k.to_s
18
+ arr.push(key.bytesize, key)
19
+ fmt << "Ca*"
20
+ encode_field(value, arr, fmt)
21
+ end
22
+ arr.pack(fmt)
13
23
  end
14
- tbl
15
- end
16
-
17
- def decode(bytes)
18
- h = {}
19
- pos = 0
20
24
 
21
- while pos < bytes.bytesize
22
- key_len = bytes[pos].ord
23
- pos += 1
24
- key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
25
- pos += key_len
26
- rest = bytes.byteslice(pos, bytes.bytesize - pos)
27
- len, value = decode_field(rest)
28
- pos += len + 1
29
- h[key] = value
25
+ # Decodes an AMQP table into a hash
26
+ # @return [Hash<String, Object>]
27
+ def self.decode(bytes)
28
+ hash = {}
29
+ pos = 0
30
+ while pos < bytes.bytesize
31
+ key_len = bytes.getbyte(pos)
32
+ pos += 1
33
+ key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
34
+ pos += key_len
35
+ len, value = decode_field(bytes, pos)
36
+ pos += len + 1
37
+ hash[key] = value
38
+ end
39
+ hash
30
40
  end
31
- h
32
- end
33
41
 
34
- def encode_field(value)
35
- case value
36
- when Integer
37
- if value > 2**31
38
- ["l", value].pack("a q>")
39
- else
40
- ["I", value].pack("a l>")
42
+ # Encoding a single value in a table
43
+ # @return [nil]
44
+ # @api private
45
+ def self.encode_field(value, arr, fmt)
46
+ case value
47
+ when Integer
48
+ if value > 2**31
49
+ arr.push("l", value)
50
+ fmt << "aq>"
51
+ else
52
+ arr.push("I", value)
53
+ fmt << "al>"
54
+ end
55
+ when Float
56
+ arr.push("d", value)
57
+ fmt << "aG"
58
+ when String
59
+ arr.push("S", value.bytesize, value)
60
+ fmt << "aL>a*"
61
+ when Time
62
+ arr.push("T", value.to_i)
63
+ fmt << "aQ>"
64
+ when Array
65
+ value_arr = []
66
+ value_fmt = String.new
67
+ value.each { |e| encode_field(e, value_arr, value_fmt) }
68
+ bytes = value_arr.pack(value_fmt)
69
+ arr.push("A", bytes.bytesize, bytes)
70
+ fmt << "aL>a*"
71
+ when Hash
72
+ bytes = Table.encode(value)
73
+ arr.push("F", bytes.bytesize, bytes)
74
+ fmt << "aL>a*"
75
+ when true
76
+ arr.push("t", 1)
77
+ fmt << "aC"
78
+ when false
79
+ arr.push("t", 0)
80
+ fmt << "aC"
81
+ when nil
82
+ arr << "V"
83
+ fmt << "a"
84
+ else raise ArgumentError, "unsupported table field type: #{value.class}"
41
85
  end
42
- when Float
43
- ["d", value].pack("a G")
44
- when String
45
- ["S", value.bytesize, value].pack("a L> a*")
46
- when Time
47
- ["T", value.to_i].pack("a Q>")
48
- when Array
49
- bytes = value.map { |e| encode_field(e) }.join
50
- ["A", bytes.bytesize, bytes].pack("a L> a*")
51
- when Hash
52
- bytes = Table.encode(value)
53
- ["F", bytes.bytesize, bytes].pack("a L> a*")
54
- when true
55
- ["t", 1].pack("a C")
56
- when false
57
- ["t", 0].pack("a C")
58
- when nil
59
- ["V"].pack("a")
60
- else raise "unsupported table field type: #{value.class}"
86
+ nil
61
87
  end
62
- end
63
88
 
64
- # returns [length of field including type, value of field]
65
- def decode_field(bytes)
66
- type = bytes[0]
67
- pos = 1
68
- case type
69
- when "S"
70
- len = bytes.byteslice(pos, 4).unpack1("L>")
71
- pos += 4
72
- [4 + len, bytes.byteslice(pos, len).force_encoding("utf-8")]
73
- when "F"
74
- len = bytes.byteslice(pos, 4).unpack1("L>")
75
- pos += 4
76
- [4 + len, decode(bytes.byteslice(pos, len))]
77
- when "A"
78
- len = bytes.byteslice(pos, 4).unpack1("L>")
79
- a = []
80
- while pos < len
81
- length, value = decode_field(bytes.byteslice(pos, -1))
82
- pos += length + 1
83
- a << value
84
- end
85
- [4 + len, a]
86
- when "t"
87
- [1, bytes[pos].ord == 1]
88
- when "b"
89
- [1, bytes.byteslice(pos, 1).unpack1("c")]
90
- when "B"
91
- [1, bytes.byteslice(pos, 1).unpack1("C")]
92
- when "s"
93
- [2, bytes.byteslice(pos, 2).unpack1("s")]
94
- when "u"
95
- [2, bytes.byteslice(pos, 2).unpack1("S")]
96
- when "I"
97
- [4, bytes.byteslice(pos, 4).unpack1("l>")]
98
- when "i"
99
- [4, bytes.byteslice(pos, 4).unpack1("L>")]
100
- when "l"
101
- [8, bytes.byteslice(pos, 8).unpack1("q>")]
102
- when "f"
103
- [4, bytes.byteslice(pos, 4).unpack1("g")]
104
- when "d"
105
- [8, bytes.byteslice(pos, 8).unpack1("G")]
106
- when "D"
107
- scale = bytes[pos].ord
89
+ # Decodes a single value
90
+ # @return [Array<Integer, Object>] Bytes read and the parsed value
91
+ # @api private
92
+ def self.decode_field(bytes, pos)
93
+ type = bytes[pos]
108
94
  pos += 1
109
- value = bytes.byteslice(pos, 4).unpack1("L>")
110
- d = value / 10**scale
111
- [5, d]
112
- when "x"
113
- len = bytes.byteslice(pos, 4).unpack1("L>")
114
- [4 + len, bytes.byteslice(pos, len)]
115
- when "T"
116
- [8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))]
117
- when "V"
118
- [0, nil]
119
- else raise "unsupported table field type: #{type}"
95
+ case type
96
+ when "S"
97
+ len = bytes.byteslice(pos, 4).unpack1("L>")
98
+ pos += 4
99
+ [4 + len, bytes.byteslice(pos, len).force_encoding("utf-8")]
100
+ when "F"
101
+ len = bytes.byteslice(pos, 4).unpack1("L>")
102
+ pos += 4
103
+ [4 + len, decode(bytes.byteslice(pos, len))]
104
+ when "A"
105
+ len = bytes.byteslice(pos, 4).unpack1("L>")
106
+ pos += 4
107
+ array_end = pos + len
108
+ a = []
109
+ while pos < array_end
110
+ length, value = decode_field(bytes, pos)
111
+ pos += length + 1
112
+ a << value
113
+ end
114
+ [4 + len, a]
115
+ when "t"
116
+ [1, bytes.getbyte(pos) == 1]
117
+ when "b"
118
+ [1, bytes.byteslice(pos, 1).unpack1("c")]
119
+ when "B"
120
+ [1, bytes.byteslice(pos, 1).unpack1("C")]
121
+ when "s"
122
+ [2, bytes.byteslice(pos, 2).unpack1("s")]
123
+ when "u"
124
+ [2, bytes.byteslice(pos, 2).unpack1("S")]
125
+ when "I"
126
+ [4, bytes.byteslice(pos, 4).unpack1("l>")]
127
+ when "i"
128
+ [4, bytes.byteslice(pos, 4).unpack1("L>")]
129
+ when "l"
130
+ [8, bytes.byteslice(pos, 8).unpack1("q>")]
131
+ when "f"
132
+ [4, bytes.byteslice(pos, 4).unpack1("g")]
133
+ when "d"
134
+ [8, bytes.byteslice(pos, 8).unpack1("G")]
135
+ when "D"
136
+ scale = bytes.getbyte(pos)
137
+ pos += 1
138
+ value = bytes.byteslice(pos, 4).unpack1("L>")
139
+ d = value / 10**scale
140
+ [5, d]
141
+ when "x"
142
+ len = bytes.byteslice(pos, 4).unpack1("L>")
143
+ [4 + len, bytes.byteslice(pos, len)]
144
+ when "T"
145
+ [8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))]
146
+ when "V"
147
+ [0, nil]
148
+ else raise ArgumentError, "unsupported table field type: #{type}"
149
+ end
120
150
  end
121
151
  end
122
152
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module AMQP
4
4
  class Client
5
- VERSION = "1.0.0"
5
+ # Version of the client library
6
+ VERSION = "1.1.1"
6
7
  end
7
8
  end
data/lib/amqp/client.rb CHANGED
@@ -3,11 +3,27 @@
3
3
  require "set"
4
4
  require_relative "client/version"
5
5
  require_relative "client/connection"
6
+ require_relative "client/exchange"
7
+ require_relative "client/queue"
6
8
 
9
+ # AMQP 0-9-1 Protocol, this library only implements the Client
10
+ # @see Client
7
11
  module AMQP
8
12
  # AMQP 0-9-1 Client
13
+ # @see Connection
9
14
  class Client
10
- def initialize(uri, **options)
15
+ # Create a new Client object, this won't establish a connection yet, use {#connect} or {#start} for that
16
+ # @param uri [String] URL on the format amqp://username:password@hostname/vhost,
17
+ # use amqps:// for encrypted connection
18
+ # @option options [Boolean] connection_name (PROGRAM_NAME) Set a name for the connection to be able to identify
19
+ # the client from the broker
20
+ # @option options [Boolean] verify_peer (true) Verify broker's TLS certificate, set to false for self-signed certs
21
+ # @option options [Integer] heartbeat (0) Heartbeat timeout, defaults to 0 and relies on TCP keepalive instead
22
+ # @option options [Integer] frame_max (131_072) Maximum frame size,
23
+ # the smallest of the client's and the broker's values will be used
24
+ # @option options [Integer] channel_max (2048) Maxium number of channels the client will be allowed to have open.
25
+ # Maxium allowed is 65_536. The smallest of the client's and the broker's value will be used.
26
+ def initialize(uri = "", **options)
11
27
  @uri = uri
12
28
  @options = options
13
29
 
@@ -17,12 +33,23 @@ module AMQP
17
33
  @connq = SizedQueue.new(1)
18
34
  end
19
35
 
20
- # Opens an AMQP connection, does not try to reconnect
36
+ # @!group Connect and disconnect
37
+
38
+ # Establishes and returns a new AMQP connection
39
+ # @see Connection#initialize
40
+ # @return [Connection]
41
+ # @example
42
+ # connection = AMQP::Client.new("amqps://server.rmq.cloudamqp.com", connection_name: "My connection").connect
21
43
  def connect(read_loop_thread: true)
22
- Connection.connect(@uri, read_loop_thread: read_loop_thread, **@options)
44
+ Connection.new(@uri, read_loop_thread: read_loop_thread, **@options)
23
45
  end
24
46
 
25
- # Opens an AMQP connection using the high level API, will try to reconnect
47
+ # Opens an AMQP connection using the high level API, will try to reconnect if successfully connected at first
48
+ # @return [self]
49
+ # @example
50
+ # amqp = AMQP::Client.new("amqps://server.rmq.cloudamqp.com")
51
+ # amqp.start
52
+ # amqp.queue("foobar")
26
53
  def start
27
54
  @stopped = false
28
55
  Thread.new(connect(read_loop_thread: false)) do |conn|
@@ -42,7 +69,7 @@ module AMQP
42
69
  @connq << conn
43
70
  end
44
71
  conn.read_loop # blocks until connection is closed, then reconnect
45
- rescue AMQP::Client::Error => e
72
+ rescue Error => e
46
73
  warn "AMQP-Client reconnect error: #{e.inspect}"
47
74
  sleep @options[:reconnect_interval] || 1
48
75
  ensure
@@ -52,76 +79,67 @@ module AMQP
52
79
  self
53
80
  end
54
81
 
82
+ # Close the currently open connection
83
+ # @return [nil]
55
84
  def stop
85
+ return if @stopped
86
+
56
87
  @stopped = true
57
88
  conn = @connq.pop
58
89
  conn.close
59
90
  nil
60
91
  end
61
92
 
62
- def queue(name, durable: true, exclusive: false, auto_delete: false, arguments: {})
93
+ # @!endgroup
94
+ # @!group High level objects
95
+
96
+ # Declare a queue
97
+ # @param name [String] Name of the queue
98
+ # @param durable [Boolean] If true the queue will survive broker restarts,
99
+ # messages in the queue will only survive if they are published as persistent
100
+ # @param auto_delete [Boolean] If true the queue will be deleted when the last consumer stops consuming
101
+ # (it won't be deleted until at least one consumer has consumed from it)
102
+ # @param arguments [Hash] Custom arguments, such as queue-ttl etc.
103
+ # @return [Queue]
104
+ # @example
105
+ # amqp = AMQP::Client.new.start
106
+ # q = amqp.queue("foobar")
107
+ # q.publish("body")
108
+ def queue(name, durable: true, auto_delete: false, arguments: {})
63
109
  raise ArgumentError, "Currently only supports named, durable queues" if name.empty?
64
110
 
65
111
  @queues.fetch(name) do
66
112
  with_connection do |conn|
67
- conn.with_channel do |ch| # use a temp channel in case the declaration fails
68
- ch.queue_declare(name, durable: durable, exclusive: exclusive, auto_delete: auto_delete, arguments: arguments)
69
- end
113
+ conn.channel(1).queue_declare(name, durable: durable, auto_delete: auto_delete, arguments: arguments)
70
114
  end
71
115
  @queues[name] = Queue.new(self, name)
72
116
  end
73
117
  end
74
118
 
119
+ # Declare an exchange and return a high level Exchange object
120
+ # @return [Exchange]
121
+ # @example
122
+ # amqp = AMQP::Client.new.start
123
+ # x = amqp.exchange("my.hash.exchange", "x-consistent-hash")
124
+ # x.publish("body", "routing-key")
75
125
  def exchange(name, type, durable: true, auto_delete: false, internal: false, arguments: {})
76
126
  @exchanges.fetch(name) do
77
127
  with_connection do |conn|
78
- conn.with_channel do |ch|
79
- ch.exchange_declare(name, type, durable: durable, auto_delete: auto_delete, internal: internal, arguments: arguments)
80
- end
128
+ conn.channel(1).exchange_declare(name, type, durable: durable, auto_delete: auto_delete,
129
+ internal: internal, arguments: arguments)
81
130
  end
82
131
  @exchanges[name] = Exchange.new(self, name)
83
132
  end
84
133
  end
85
134
 
86
- # High level representation of an exchange
87
- class Exchange
88
- def initialize(client, name)
89
- @client = client
90
- @name = name
91
- end
92
-
93
- def publish(body, routing_key, arguments: {})
94
- @client.publish(body, @name, routing_key, arguments: arguments)
95
- end
96
-
97
- # Bind to another exchange
98
- def bind(exchange, routing_key, arguments: {})
99
- @client.exchange_bind(@name, exchange, routing_key, arguments: arguments)
100
- end
101
-
102
- # Unbind from another exchange
103
- def unbind(exchange, routing_key, arguments: {})
104
- @client.exchange_unbind(@name, exchange, routing_key, arguments: arguments)
105
- end
106
-
107
- def delete
108
- @client.delete_exchange(@name)
109
- end
110
- end
111
-
112
- def subscribe(queue_name, no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
113
- @subscriptions.add? [queue_name, no_ack, prefetch, worker_threads, arguments, blk]
114
-
115
- with_connection do |conn|
116
- ch = conn.channel
117
- ch.basic_qos(prefetch)
118
- ch.basic_consume(queue_name, no_ack: no_ack, worker_threads: worker_threads, arguments: arguments) do |msg|
119
- blk.call(msg)
120
- end
121
- end
122
- end
135
+ # @!endgroup
136
+ # @!group Publish
123
137
 
124
138
  # Publish a (persistent) message and wait for confirmation
139
+ # @param (see Connection::Channel#basic_publish_confirm)
140
+ # @option (see Connection::Channel#basic_publish_confirm)
141
+ # @return (see Connection::Channel#basic_publish_confirm)
142
+ # @raise (see Connection::Channel#basic_publish_confirm)
125
143
  def publish(body, exchange, routing_key, **properties)
126
144
  with_connection do |conn|
127
145
  properties = { delivery_mode: 2 }.merge!(properties)
@@ -130,6 +148,10 @@ module AMQP
130
148
  end
131
149
 
132
150
  # Publish a (persistent) message but don't wait for a confirmation
151
+ # @param (see Connection::Channel#basic_publish)
152
+ # @option (see Connection::Channel#basic_publish)
153
+ # @return (see Connection::Channel#basic_publish)
154
+ # @raise (see Connection::Channel#basic_publish)
133
155
  def publish_and_forget(body, exchange, routing_key, **properties)
134
156
  with_connection do |conn|
135
157
  properties = { delivery_mode: 2 }.merge!(properties)
@@ -137,94 +159,123 @@ module AMQP
137
159
  end
138
160
  end
139
161
 
162
+ # Wait for unconfirmed publishes
163
+ # @return [Boolean] True if successful, false if any message negatively acknowledged
140
164
  def wait_for_confirms
141
165
  with_connection do |conn|
142
166
  conn.channel(1).wait_for_confirms
143
167
  end
144
168
  end
145
169
 
146
- def bind(queue, exchange, routing_key, arguments: {})
147
- with_connection do |conn|
148
- conn.channel(1).queue_bind(queue, exchange, routing_key, arguments: arguments)
149
- end
150
- end
170
+ # @!endgroup
171
+ # @!group Queue actions
172
+
173
+ # Consume messages from a queue
174
+ # @param queue [String] Name of the queue to subscribe to
175
+ # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
176
+ # @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false
177
+ # @param worker_threads [Integer] Number of threads processing messages,
178
+ # 0 means that the thread calling this method will be blocked
179
+ # @param arguments [Hash] Custom arguments to the consumer
180
+ # @yield [Message] Delivered message from the queue
181
+ # @return [Array<(String, Array<Thread>)>] Returns consumer_tag and an array of worker threads
182
+ # @return [nil] When `worker_threads` is 0 the method will return when the consumer is cancelled
183
+ def subscribe(queue, no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
184
+ @subscriptions.add? [queue, no_ack, prefetch, worker_threads, arguments, blk]
151
185
 
152
- def unbind(queue, exchange, routing_key, arguments: {})
153
186
  with_connection do |conn|
154
- conn.channel(1).queue_unbind(queue, exchange, routing_key, arguments: arguments)
187
+ ch = conn.channel
188
+ ch.basic_qos(prefetch)
189
+ ch.basic_consume(queue, no_ack: no_ack, worker_threads: worker_threads, arguments: arguments, &blk)
155
190
  end
156
191
  end
157
192
 
158
- def exchange_bind(destination, source, routing_key, arguments: {})
193
+ # Bind a queue to an exchange
194
+ # @param queue [String] Name of the queue to bind
195
+ # @param exchange [String] Name of the exchange to bind to
196
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
197
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
198
+ # @return [nil]
199
+ def bind(queue, exchange, binding_key, arguments: {})
159
200
  with_connection do |conn|
160
- conn.channel(1).exchange_bind(destination, source, routing_key, arguments: arguments)
201
+ conn.channel(1).queue_bind(queue, exchange, binding_key, arguments: arguments)
161
202
  end
162
203
  end
163
204
 
164
- def exchange_unbind(destination, source, routing_key, arguments: {})
205
+ # Unbind a queue from an exchange
206
+ # @param queue [String] Name of the queue to unbind
207
+ # @param exchange [String] Name of the exchange to unbind from
208
+ # @param binding_key [String] Binding key which the queue is bound to the exchange with
209
+ # @param arguments [Hash] Arguments matching the binding that's being removed
210
+ # @return [nil]
211
+ def unbind(queue, exchange, binding_key, arguments: {})
165
212
  with_connection do |conn|
166
- conn.channel(1).exchange_unbind(destination, source, routing_key, arguments: arguments)
213
+ conn.channel(1).queue_unbind(queue, exchange, binding_key, arguments: arguments)
167
214
  end
168
215
  end
169
216
 
217
+ # Purge a queue
218
+ # @param queue [String] Name of the queue
219
+ # @return [nil]
170
220
  def purge(queue)
171
221
  with_connection do |conn|
172
222
  conn.channel(1).queue_purge(queue)
173
223
  end
174
224
  end
175
225
 
176
- def delete_queue(name)
226
+ # Delete a queue
227
+ # @param name [String] Name of the queue
228
+ # @param if_unused [Boolean] Only delete if the queue doesn't have consumers, raises a ChannelClosed error otherwise
229
+ # @param if_empty [Boolean] Only delete if the queue is empty, raises a ChannelClosed error otherwise
230
+ # @return [Integer] Number of messages in the queue when deleted
231
+ def delete_queue(name, if_unused: false, if_empty: false)
177
232
  with_connection do |conn|
178
- conn.channel(1).queue_delete(name)
233
+ msgs = conn.channel(1).queue_delete(name, if_unused: if_unused, if_empty: if_empty)
179
234
  @queues.delete(name)
235
+ msgs
180
236
  end
181
237
  end
182
238
 
183
- def delete_exchange(name)
239
+ # @!endgroup
240
+ # @!group Exchange actions
241
+
242
+ # Bind an exchange to an exchange
243
+ # @param destination [String] Name of the exchange to bind
244
+ # @param source [String] Name of the exchange to bind to
245
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
246
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
247
+ # @return [nil]
248
+ def exchange_bind(destination, source, binding_key, arguments: {})
184
249
  with_connection do |conn|
185
- conn.channel(1).exchange_delete(name)
186
- @exchanges.delete(name)
250
+ conn.channel(1).exchange_bind(destination, source, binding_key, arguments: arguments)
187
251
  end
188
252
  end
189
253
 
190
- # Queue abstraction
191
- class Queue
192
- def initialize(client, name)
193
- @client = client
194
- @name = name
195
- end
196
-
197
- def publish(body, **properties)
198
- @client.publish(body, "", @name, **properties)
199
- self
200
- end
201
-
202
- def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
203
- @client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
204
- self
205
- end
206
-
207
- def bind(exchange, routing_key, **headers)
208
- @client.bind(@name, exchange, routing_key, **headers)
209
- self
210
- end
211
-
212
- def unbind(exchange, routing_key, **headers)
213
- @client.unbind(@name, exchange, routing_key, **headers)
214
- self
215
- end
216
-
217
- def purge
218
- @client.purge(@name)
219
- self
254
+ # Unbind an exchange from an exchange
255
+ # @param destination [String] Name of the exchange to unbind
256
+ # @param source [String] Name of the exchange to unbind from
257
+ # @param binding_key [String] Binding key which the exchange is bound to the exchange with
258
+ # @param arguments [Hash] Arguments matching the binding that's being removed
259
+ # @return [nil]
260
+ def exchange_unbind(destination, source, binding_key, arguments: {})
261
+ with_connection do |conn|
262
+ conn.channel(1).exchange_unbind(destination, source, binding_key, arguments: arguments)
220
263
  end
264
+ end
221
265
 
222
- def delete
223
- @client.delete_queue(@name)
266
+ # Delete an exchange
267
+ # @param name [String] Name of the exchange
268
+ # @return [nil]
269
+ def delete_exchange(name)
270
+ with_connection do |conn|
271
+ conn.channel(1).exchange_delete(name)
272
+ @exchanges.delete(name)
224
273
  nil
225
274
  end
226
275
  end
227
276
 
277
+ # @!endgroup
278
+
228
279
  private
229
280
 
230
281
  def with_connection