amqp-client 1.0.0 → 1.0.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.
@@ -0,0 +1,78 @@
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
15
+ # @param body [String] The message body
16
+ # @param properties [Properties]
17
+ # @option properties [String] content_type Content type of the message body
18
+ # @option properties [String] content_encoding Content encoding of the body
19
+ # @option properties [Hash<String, Object>] headers Custom headers
20
+ # @option properties [Integer] delivery_mode 2 for persisted message, transient messages for all other values
21
+ # @option properties [Integer] priority A priority of the message (between 0 and 255)
22
+ # @option properties [Integer] correlation_id A correlation id, most often used used for RPC communication
23
+ # @option properties [String] reply_to Queue to reply RPC responses to
24
+ # @option properties [Integer, String] expiration Number of seconds the message will stay in the queue
25
+ # @option properties [String] message_id Can be used to uniquely identify the message, e.g. for deduplication
26
+ # @option properties [Date] timestamp Often used for the time the message was originally generated
27
+ # @option properties [String] type Can indicate what kind of message this is
28
+ # @option properties [String] user_id Can be used to verify that this is the user that published the message
29
+ # @option properties [String] app_id Can be used to indicates which app that generated the message
30
+ # @return [Queue] self
31
+ def publish(body, **properties)
32
+ @client.publish(body, "", @name, **properties)
33
+ self
34
+ end
35
+
36
+ # Subscribe/consume from the queue
37
+ # @return [Queue] self
38
+ def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
39
+ @client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
40
+ self
41
+ end
42
+
43
+ # Bind the queue to an exchange
44
+ # @param exchange [String] Name of the exchange to bind to
45
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
46
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
47
+ # @return [Queue] self
48
+ def bind(exchange, binding_key, arguments: {})
49
+ @client.bind(@name, exchange, binding_key, arguments: arguments)
50
+ self
51
+ end
52
+
53
+ # Unbind the queue from an exchange
54
+ # @param exchange [String] Name of the exchange to unbind from
55
+ # @param binding_key [String] Binding key which the queue is bound to the exchange with
56
+ # @param arguments [Hash] Arguments matching the binding that's being removed
57
+ # @return [Queue] self
58
+ def unbind(exchange, binding_key, arguments: {})
59
+ @client.unbind(@name, exchange, binding_key, arguments: arguments)
60
+ self
61
+ end
62
+
63
+ # Purge/empty the queue
64
+ # @return [Queue] self
65
+ def purge
66
+ @client.purge(@name)
67
+ self
68
+ end
69
+
70
+ # Delete the queue
71
+ # @return [nil]
72
+ def delete
73
+ @client.delete_queue(@name)
74
+ nil
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,122 +1,133 @@
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
+ module_function
7
9
 
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*")
10
+ # Encodes a hash into a byte array
11
+ # @return [String] Byte array
12
+ def encode(hash)
13
+ tbl = StringIO.new
14
+ hash.each do |k, v|
15
+ key = k.to_s
16
+ tbl.write [key.bytesize, key].pack("Ca*")
17
+ tbl.write encode_field(v)
18
+ end
19
+ tbl.string
13
20
  end
14
- tbl
15
- end
16
-
17
- def decode(bytes)
18
- h = {}
19
- pos = 0
20
21
 
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
22
+ # Decodes an AMQP table into a hash
23
+ # @return [Hash<String, Object>]
24
+ def decode(bytes)
25
+ hash = {}
26
+ pos = 0
27
+ while pos < bytes.bytesize
28
+ key_len = bytes[pos].ord
29
+ pos += 1
30
+ key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
31
+ pos += key_len
32
+ rest = bytes.byteslice(pos, bytes.bytesize - pos)
33
+ len, value = decode_field(rest)
34
+ pos += len + 1
35
+ hash[key] = value
36
+ end
37
+ hash
30
38
  end
31
- h
32
- end
33
39
 
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>")
40
+ # Encoding a single value in a table
41
+ # @api private
42
+ def encode_field(value)
43
+ case value
44
+ when Integer
45
+ if value > 2**31
46
+ ["l", value].pack("a q>")
47
+ else
48
+ ["I", value].pack("a l>")
49
+ end
50
+ when Float
51
+ ["d", value].pack("a G")
52
+ when String
53
+ ["S", value.bytesize, value].pack("a L> a*")
54
+ when Time
55
+ ["T", value.to_i].pack("a Q>")
56
+ when Array
57
+ bytes = value.map { |e| encode_field(e) }.join
58
+ ["A", bytes.bytesize, bytes].pack("a L> a*")
59
+ when Hash
60
+ bytes = Table.encode(value)
61
+ ["F", bytes.bytesize, bytes].pack("a L> a*")
62
+ when true
63
+ ["t", 1].pack("a C")
64
+ when false
65
+ ["t", 0].pack("a C")
66
+ when nil
67
+ ["V"].pack("a")
68
+ else raise ArgumentError, "unsupported table field type: #{value.class}"
41
69
  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}"
61
70
  end
62
- end
63
71
 
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
72
+ # Decodes a single value
73
+ # @return [Array<Integer, Object>] Bytes read and the parsed object
74
+ # @api private
75
+ def decode_field(bytes)
76
+ type = bytes[0]
77
+ pos = 1
78
+ case type
79
+ when "S"
80
+ len = bytes.byteslice(pos, 4).unpack1("L>")
81
+ pos += 4
82
+ [4 + len, bytes.byteslice(pos, len).force_encoding("utf-8")]
83
+ when "F"
84
+ len = bytes.byteslice(pos, 4).unpack1("L>")
85
+ pos += 4
86
+ [4 + len, decode(bytes.byteslice(pos, len))]
87
+ when "A"
88
+ len = bytes.byteslice(pos, 4).unpack1("L>")
89
+ a = []
90
+ while pos < len
91
+ length, value = decode_field(bytes.byteslice(pos, -1))
92
+ pos += length + 1
93
+ a << value
94
+ end
95
+ [4 + len, a]
96
+ when "t"
97
+ [1, bytes[pos].ord == 1]
98
+ when "b"
99
+ [1, bytes.byteslice(pos, 1).unpack1("c")]
100
+ when "B"
101
+ [1, bytes.byteslice(pos, 1).unpack1("C")]
102
+ when "s"
103
+ [2, bytes.byteslice(pos, 2).unpack1("s")]
104
+ when "u"
105
+ [2, bytes.byteslice(pos, 2).unpack1("S")]
106
+ when "I"
107
+ [4, bytes.byteslice(pos, 4).unpack1("l>")]
108
+ when "i"
109
+ [4, bytes.byteslice(pos, 4).unpack1("L>")]
110
+ when "l"
111
+ [8, bytes.byteslice(pos, 8).unpack1("q>")]
112
+ when "f"
113
+ [4, bytes.byteslice(pos, 4).unpack1("g")]
114
+ when "d"
115
+ [8, bytes.byteslice(pos, 8).unpack1("G")]
116
+ when "D"
117
+ scale = bytes[pos].ord
118
+ pos += 1
119
+ value = bytes.byteslice(pos, 4).unpack1("L>")
120
+ d = value / 10**scale
121
+ [5, d]
122
+ when "x"
123
+ len = bytes.byteslice(pos, 4).unpack1("L>")
124
+ [4 + len, bytes.byteslice(pos, len)]
125
+ when "T"
126
+ [8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))]
127
+ when "V"
128
+ [0, nil]
129
+ else raise ArgumentError, "unsupported table field type: #{type}"
84
130
  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
108
- 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}"
120
131
  end
121
132
  end
122
133
  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.0.1"
6
7
  end
7
8
  end
data/lib/amqp/client.rb CHANGED
@@ -3,10 +3,26 @@
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
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.
10
26
  def initialize(uri, **options)
11
27
  @uri = uri
12
28
  @options = options
@@ -17,12 +33,17 @@ 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 a new AMQP connection, does not try to reconnect
39
+ # @see Connection.connect
40
+ # @return [Connection]
21
41
  def connect(read_loop_thread: true)
22
42
  Connection.connect(@uri, read_loop_thread: read_loop_thread, **@options)
23
43
  end
24
44
 
25
- # Opens an AMQP connection using the high level API, will try to reconnect
45
+ # Opens an AMQP connection using the high level API, will try to reconnect if successfully connected at first
46
+ # @return [self]
26
47
  def start
27
48
  @stopped = false
28
49
  Thread.new(connect(read_loop_thread: false)) do |conn|
@@ -42,7 +63,7 @@ module AMQP
42
63
  @connq << conn
43
64
  end
44
65
  conn.read_loop # blocks until connection is closed, then reconnect
45
- rescue AMQP::Client::Error => e
66
+ rescue Error => e
46
67
  warn "AMQP-Client reconnect error: #{e.inspect}"
47
68
  sleep @options[:reconnect_interval] || 1
48
69
  ensure
@@ -52,76 +73,56 @@ module AMQP
52
73
  self
53
74
  end
54
75
 
76
+ # Close the currently open connection
77
+ # @return [nil]
55
78
  def stop
79
+ return if @stopped
80
+
56
81
  @stopped = true
57
82
  conn = @connq.pop
58
83
  conn.close
59
84
  nil
60
85
  end
61
86
 
62
- def queue(name, durable: true, exclusive: false, auto_delete: false, arguments: {})
87
+ # @!endgroup
88
+ # @!group High level objects
89
+
90
+ # Declare a queue
91
+ # @param name [String] Name of the queue
92
+ # @param durable [Boolean] If true the queue will survive broker restarts,
93
+ # messages in the queue will only survive if they are published as persistent
94
+ # @param auto_delete [Boolean] If true the queue will be deleted when the last consumer stops consuming
95
+ # (it won't be deleted until at least one consumer has consumed from it)
96
+ # @param arguments [Hash] Custom arguments, such as queue-ttl etc.
97
+ # @return [Queue]
98
+ def queue(name, durable: true, auto_delete: false, arguments: {})
63
99
  raise ArgumentError, "Currently only supports named, durable queues" if name.empty?
64
100
 
65
101
  @queues.fetch(name) do
66
102
  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
103
+ conn.channel(1).queue_declare(name, durable: durable, auto_delete: auto_delete, arguments: arguments)
70
104
  end
71
105
  @queues[name] = Queue.new(self, name)
72
106
  end
73
107
  end
74
108
 
109
+ # Declare an exchange and return a high level Exchange object
110
+ # @return [Exchange]
75
111
  def exchange(name, type, durable: true, auto_delete: false, internal: false, arguments: {})
76
112
  @exchanges.fetch(name) do
77
113
  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
114
+ conn.channel(1).exchange_declare(name, type, durable: durable, auto_delete: auto_delete,
115
+ internal: internal, arguments: arguments)
81
116
  end
82
117
  @exchanges[name] = Exchange.new(self, name)
83
118
  end
84
119
  end
85
120
 
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
121
+ # @!endgroup
122
+ # @!group Publish
123
123
 
124
124
  # Publish a (persistent) message and wait for confirmation
125
+ # @return [nil]
125
126
  def publish(body, exchange, routing_key, **properties)
126
127
  with_connection do |conn|
127
128
  properties = { delivery_mode: 2 }.merge!(properties)
@@ -130,6 +131,7 @@ module AMQP
130
131
  end
131
132
 
132
133
  # Publish a (persistent) message but don't wait for a confirmation
134
+ # @return [nil]
133
135
  def publish_and_forget(body, exchange, routing_key, **properties)
134
136
  with_connection do |conn|
135
137
  properties = { delivery_mode: 2 }.merge!(properties)
@@ -137,94 +139,125 @@ module AMQP
137
139
  end
138
140
  end
139
141
 
142
+ # Wait for unconfirmed publishes
143
+ # @return [Boolean] True if successful, false if any message negatively acknowledged
140
144
  def wait_for_confirms
141
145
  with_connection do |conn|
142
146
  conn.channel(1).wait_for_confirms
143
147
  end
144
148
  end
145
149
 
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
150
+ # @!endgroup
151
+ # @!group Queue actions
152
+
153
+ # Consume messages from a queue
154
+ # @param queue [String] Name of the queue to subscribe to
155
+ # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
156
+ # @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false
157
+ # @param worker_threads [Integer] Number of threads processing messages,
158
+ # 0 means that the thread calling this method will be blocked
159
+ # @param arguments [Hash] Custom arguments to the consumer
160
+ # @yield [Message] Delivered message from the queue
161
+ # @return [Array<(String, Array<Thread>)>] Returns consumer_tag and an array of worker threads
162
+ # @return [nil] When `worker_threads` is 0 the method will return when the consumer is cancelled
163
+ def subscribe(queue, no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
164
+ @subscriptions.add? [queue, no_ack, prefetch, worker_threads, arguments, blk]
151
165
 
152
- def unbind(queue, exchange, routing_key, arguments: {})
153
166
  with_connection do |conn|
154
- conn.channel(1).queue_unbind(queue, exchange, routing_key, arguments: arguments)
167
+ ch = conn.channel
168
+ ch.basic_qos(prefetch)
169
+ ch.basic_consume(queue, no_ack: no_ack, worker_threads: worker_threads, arguments: arguments) do |msg|
170
+ blk.call(msg)
171
+ end
155
172
  end
156
173
  end
157
174
 
158
- def exchange_bind(destination, source, routing_key, arguments: {})
175
+ # Bind a queue to an exchange
176
+ # @param queue [String] Name of the queue to bind
177
+ # @param exchange [String] Name of the exchange to bind to
178
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
179
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
180
+ # @return [nil]
181
+ def bind(queue, exchange, binding_key, arguments: {})
159
182
  with_connection do |conn|
160
- conn.channel(1).exchange_bind(destination, source, routing_key, arguments: arguments)
183
+ conn.channel(1).queue_bind(queue, exchange, binding_key, arguments: arguments)
161
184
  end
162
185
  end
163
186
 
164
- def exchange_unbind(destination, source, routing_key, arguments: {})
187
+ # Unbind a queue from an exchange
188
+ # @param queue [String] Name of the queue to unbind
189
+ # @param exchange [String] Name of the exchange to unbind from
190
+ # @param binding_key [String] Binding key which the queue is bound to the exchange with
191
+ # @param arguments [Hash] Arguments matching the binding that's being removed
192
+ # @return [nil]
193
+ def unbind(queue, exchange, binding_key, arguments: {})
165
194
  with_connection do |conn|
166
- conn.channel(1).exchange_unbind(destination, source, routing_key, arguments: arguments)
195
+ conn.channel(1).queue_unbind(queue, exchange, binding_key, arguments: arguments)
167
196
  end
168
197
  end
169
198
 
199
+ # Purge a queue
200
+ # @param queue [String] Name of the queue
201
+ # @return [nil]
170
202
  def purge(queue)
171
203
  with_connection do |conn|
172
204
  conn.channel(1).queue_purge(queue)
173
205
  end
174
206
  end
175
207
 
176
- def delete_queue(name)
208
+ # Delete a queue
209
+ # @param name [String] Name of the queue
210
+ # @param if_unused [Boolean] Only delete if the queue doesn't have consumers, raises a ChannelClosed error otherwise
211
+ # @param if_empty [Boolean] Only delete if the queue is empty, raises a ChannelClosed error otherwise
212
+ # @return [Integer] Number of messages in the queue when deleted
213
+ def delete_queue(name, if_unused: false, if_empty: false)
177
214
  with_connection do |conn|
178
- conn.channel(1).queue_delete(name)
215
+ msgs = conn.channel(1).queue_delete(name, if_unused: if_unused, if_empty: if_empty)
179
216
  @queues.delete(name)
217
+ msgs
180
218
  end
181
219
  end
182
220
 
183
- def delete_exchange(name)
221
+ # @!endgroup
222
+ # @!group Exchange actions
223
+
224
+ # Bind an exchange to an exchange
225
+ # @param destination [String] Name of the exchange to bind
226
+ # @param source [String] Name of the exchange to bind to
227
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
228
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
229
+ # @return [nil]
230
+ def exchange_bind(destination, source, binding_key, arguments: {})
184
231
  with_connection do |conn|
185
- conn.channel(1).exchange_delete(name)
186
- @exchanges.delete(name)
232
+ conn.channel(1).exchange_bind(destination, source, binding_key, arguments: arguments)
187
233
  end
188
234
  end
189
235
 
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
236
+ # Unbind an exchange from an exchange
237
+ # @param destination [String] Name of the exchange to unbind
238
+ # @param source [String] Name of the exchange to unbind from
239
+ # @param binding_key [String] Binding key which the exchange is bound to the exchange with
240
+ # @param arguments [Hash] Arguments matching the binding that's being removed
241
+ # @return [nil]
242
+ def exchange_unbind(destination, source, binding_key, arguments: {})
243
+ with_connection do |conn|
244
+ conn.channel(1).exchange_unbind(destination, source, binding_key, arguments: arguments)
220
245
  end
246
+ end
221
247
 
222
- def delete
223
- @client.delete_queue(@name)
248
+ # Delete an exchange
249
+ # @param name [String] Name of the exchange
250
+ # @return [nil]
251
+ def delete_exchange(name)
252
+ with_connection do |conn|
253
+ conn.channel(1).exchange_delete(name)
254
+ @exchanges.delete(name)
224
255
  nil
225
256
  end
226
257
  end
227
258
 
259
+ # @!endgroup
260
+
228
261
  private
229
262
 
230
263
  def with_connection