amqp-client 0.2.2 → 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 = "0.2.2"
5
+ # Version of the client library
6
+ VERSION = "1.0.1"
6
7
  end
7
8
  end
data/lib/amqp/client.rb CHANGED
@@ -3,152 +3,261 @@
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
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 a new AMQP connection, does not try to reconnect
39
+ # @see Connection.connect
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.connect(@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
+ # @return [nil]
77
126
  def publish(body, exchange, routing_key, **properties)
78
127
  with_connection do |conn|
79
- # Use channel 1 for publishes
128
+ properties = { delivery_mode: 2 }.merge!(properties)
80
129
  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
130
  end
85
- rescue => e
86
- warn "AMQP-Client error publishing, retrying (#{e.inspect})"
87
- retry
88
131
  end
89
132
 
90
- def bind(queue, exchange, routing_key, **headers)
133
+ # Publish a (persistent) message but don't wait for a confirmation
134
+ # @return [nil]
135
+ def publish_and_forget(body, exchange, routing_key, **properties)
91
136
  with_connection do |conn|
92
- conn.channel(1).queue_bind(queue, exchange, routing_key, **headers)
137
+ properties = { delivery_mode: 2 }.merge!(properties)
138
+ conn.channel(1).basic_publish(body, exchange, routing_key, **properties)
93
139
  end
94
140
  end
95
141
 
96
- def unbind(queue, exchange, routing_key, **headers)
142
+ # Wait for unconfirmed publishes
143
+ # @return [Boolean] True if successful, false if any message negatively acknowledged
144
+ def wait_for_confirms
97
145
  with_connection do |conn|
98
- conn.channel(1).queue_unbind(queue, exchange, routing_key, **headers)
146
+ conn.channel(1).wait_for_confirms
99
147
  end
100
148
  end
101
149
 
102
- def purge(queue)
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]
165
+
103
166
  with_connection do |conn|
104
- conn.channel(1).queue_purge(queue)
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
105
172
  end
106
173
  end
107
174
 
108
- def delete_queue(queue)
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: {})
109
182
  with_connection do |conn|
110
- conn.channel(1).queue_delete(queue)
183
+ conn.channel(1).queue_bind(queue, exchange, binding_key, arguments: arguments)
111
184
  end
112
185
  end
113
186
 
114
- # Queue abstraction
115
- class Queue
116
- def initialize(client, name)
117
- @client = client
118
- @name = name
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: {})
194
+ with_connection do |conn|
195
+ conn.channel(1).queue_unbind(queue, exchange, binding_key, arguments: arguments)
119
196
  end
197
+ end
120
198
 
121
- def publish(body, **properties)
122
- @client.publish(body, "", @name, **properties)
123
- self
199
+ # Purge a queue
200
+ # @param queue [String] Name of the queue
201
+ # @return [nil]
202
+ def purge(queue)
203
+ with_connection do |conn|
204
+ conn.channel(1).queue_purge(queue)
124
205
  end
206
+ end
125
207
 
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
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)
214
+ with_connection do |conn|
215
+ msgs = conn.channel(1).queue_delete(name, if_unused: if_unused, if_empty: if_empty)
216
+ @queues.delete(name)
217
+ msgs
129
218
  end
219
+ end
130
220
 
131
- def bind(exchange, routing_key, **headers)
132
- @client.bind(@name, exchange, routing_key, **headers)
133
- self
134
- end
221
+ # @!endgroup
222
+ # @!group Exchange actions
135
223
 
136
- def unbind(exchange, routing_key, **headers)
137
- @client.unbind(@name, exchange, routing_key, **headers)
138
- self
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: {})
231
+ with_connection do |conn|
232
+ conn.channel(1).exchange_bind(destination, source, binding_key, arguments: arguments)
139
233
  end
234
+ end
140
235
 
141
- def purge
142
- @client.purge(@name)
143
- 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)
144
245
  end
246
+ end
145
247
 
146
- def delete
147
- @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)
148
255
  nil
149
256
  end
150
257
  end
151
258
 
259
+ # @!endgroup
260
+
152
261
  private
153
262
 
154
263
  def with_connection