amqp-client 0.3.0 → 1.1.0

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