amqp-client 1.0.0 → 1.0.1

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