bunny-publisher 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3285bb8050efb8421bb572040231fa85c1b12ee7bcd3cda02af8649101559ce6
4
+ data.tar.gz: f00f996623c6d099cf405f9555b5cbf437b67ad65aeb34ea2722bc3235492388
5
+ SHA512:
6
+ metadata.gz: a0148e4c6275e4983b9853a8f12d1e38d1f9eb97c57ed4f4d0adbbb8a071638fa0998544d908ead27841d1b513bc11e284ddd83efe37f9d0d3f1302476f900c2
7
+ data.tar.gz: 0de5b4d2f9e9cc53c5da44582548b387b7939e8a23d45014bfe7a6868ec98c580c434bc65792790fdfe932ef671667acff2ec0eb2a5c7610e3d2eff014045c5f
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /Gemfile.lock
10
+ /.ruby-gemset
11
+ /.ruby-version
12
+
13
+ # rspec failure tracking
14
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,16 @@
1
+ Layout/LineLength:
2
+ Max: 120
3
+ Exclude:
4
+ - "spec/**/*"
5
+
6
+ Lint/AmbiguousBlockAssociation:
7
+ Exclude:
8
+ - "spec/**/*"
9
+
10
+ Metrics/BlockLength:
11
+ Exclude:
12
+ - "spec/**/*"
13
+
14
+ Style/EvalWithLocation:
15
+ Exclude:
16
+ - "spec/**/*"
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ ---
2
+ dist: xenial
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.8
7
+ - 2.6.6
8
+ - 2.7.1
9
+ env:
10
+ - BUNNY_VERSION="~> 2.15"
11
+ - BUNNY_VERSION="2.10.0"
12
+
13
+ before_install: gem install bundler
14
+ before_script:
15
+ - ".ci/install_rabbitmq"
16
+ bundler_args: --jobs 3 --retry 3
data/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ ## Original Release: 0.1.0
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in bunny-publisher.gemspec
8
+ gemspec
9
+
10
+ gem 'bunny', ENV.fetch('BUNNY_VERSION', '~> 2.15')
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Rustam Sharshenov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # BunnyPublisher
2
+
3
+ [![Build Status](https://travis-ci.com/veeqo/bunny-publisher.svg?branch=master)](https://travis-ci.com/veeqo/bunny-publisher) [![Gem Version](https://badge.fury.io/rb/bunny-publisher.svg)](https://badge.fury.io/rb/bunny-publisher)
4
+
5
+ Ruby publisher(producer) for [RabbitMQ](https://www.rabbitmq.com/) based on [bunny](https://github.com/ruby-amqp/bunny).
6
+
7
+ ## Why?
8
+
9
+ Bunny is a great RabbitMQ client that allows to implement both consumers & producers. In order to publish a message it requires only few lines of code:
10
+ ```ruby
11
+ conn = Bunny.new
12
+ conn.start
13
+ channel = conn.create_channel
14
+ exchange = channel.exchange('my_exchange')
15
+ exchange.publish('message', routing_key: 'some.key')
16
+ ```
17
+
18
+ But usually more features are requested. Such as:
19
+ 1. Multi-thread environment **should re-use AMQP-connection**.
20
+ 2. No message **should be lost** if there is no sutable routing at the moment of publishing.
21
+ 3. Callbacks support (e.g. for **logging** or **metrics**)
22
+
23
+ So publisher implementation becomes more complex and hard to maintain. This project aims to reduce amount of boiler-plate code to write. Just use basic publisher with modules for your needs:
24
+
25
+ 1. [`BunnyPublisher::Base`](#basic-usage) - basic publisher with callbacks support. Based on [publisher of Sneakers](https://github.com/jondot/sneakers/blob/ed620b642b447701be490666ee284cf7d60ccf22/lib/sneakers/publisher.rb).
26
+ 2. [`BunnyPublisher::Mandatory`](#mandatory-publishing) - module for publisher that uses mandatory option to handle unrouted messages
27
+ 3. [`BunnyPublisher::RPC`](#remote-procedure-call-rpc) - module for publisher to support [RPC](https://www.rabbitmq.com/tutorials/tutorial-six-ruby.html)
28
+
29
+ ## Installation
30
+
31
+ Required ruby version is **2.5**. Add this line to your application's Gemfile:
32
+
33
+ ```ruby
34
+ gem 'bunny-publisher'
35
+ ```
36
+
37
+ And then execute:
38
+
39
+ $ bundle
40
+
41
+ Or install it yourself as:
42
+
43
+ $ gem install bunny-publisher
44
+
45
+ ## Basic usage
46
+
47
+ Publisher is ready to be used out of the box
48
+
49
+ ```ruby
50
+ # publishes to default exchange, expects "such.easy" queue to exist
51
+ BunnyPublisher.publish 'wow', routing_key: 'such.easy'
52
+ ```
53
+
54
+ Set `RABBITMQ_URL` environment variable to connect to custom host/vhost.
55
+
56
+ Publisher can also be configured
57
+
58
+ ```ruby
59
+ BunnyPublisher.configure do |c|
60
+ # custom exchange options
61
+ c.exchange = 'custom'
62
+ c.exchange_options = { type: 'fanout' }
63
+
64
+ # custom connection (e.g. with ssl configured)
65
+ c.connection = Bunny.new(something: 'custom')
66
+
67
+ # or just custom options for connection
68
+ c.amqp = ENV['CLOUDAMQP_URL']
69
+ c.heartbeat = 10
70
+ end
71
+ ```
72
+
73
+ ## Mandatory publishing
74
+
75
+ The publisher also supports [:mandatory](http://rubybunny.info/articles/exchanges.html#publishing_messages_as_mandatory) option to handle unrouted messages. In case of unrouted message publisher:
76
+
77
+ 1. Will create a queue by the name of routing key
78
+ 2. Will bind queue to the exchange
79
+ 3. Publish message again
80
+
81
+ Configure publisher to use mandatory option
82
+
83
+ ```ruby
84
+ BunnyPublisher.configure do |c|
85
+ c.mandatory = true
86
+
87
+ # ...
88
+ end
89
+ ```
90
+
91
+ Publish message with new routing key
92
+
93
+ ```ruby
94
+ BunnyPublisher.publish 'wow', routing_key: 'such.reliable' # this will create "such.reliable" queue
95
+ ```
96
+
97
+ You also can set custom settings for queue definition
98
+
99
+ ```ruby
100
+ BunnyPublisher.configure do |c|
101
+ c.mandatory = true
102
+
103
+ c.queue = 'funnel' # if not set, routing_key is used for the name
104
+ c.queue_options = { durable: true }
105
+
106
+ # ...
107
+ end
108
+ ```
109
+
110
+ ## Remote Procedure Call RPC
111
+
112
+ Not implemented yet
113
+
114
+ ## Contributing
115
+
116
+ Bug reports and pull requests are welcome on GitHub at https://github.com/veeqo/bunny-publisher.
117
+
118
+ ## License
119
+
120
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
121
+
122
+ ## Sponsored by [Veeqo](https://veeqo.com/)
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'bunny_publisher/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'bunny-publisher'
9
+ spec.version = BunnyPublisher::VERSION
10
+ spec.authors = ['Rustam Sharshenov']
11
+ spec.email = ['rustam@sharshenov.com']
12
+
13
+ spec.summary = 'AMQP publisher for RabbitMQ based on Bunny'
14
+ spec.description = 'AMQP publisher for RabbitMQ based on Bunny'
15
+ spec.homepage = 'https://github.com/veeqo/bunny-publisher'
16
+ spec.license = 'MIT'
17
+
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = spec.homepage
21
+ spec.metadata['changelog_uri'] = 'https://github.com/veeqo/bunny-publisher/blob/master/CHANGELOG.md'
22
+ end
23
+
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|\.ci)/}) }
26
+ end
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_dependency 'bunny', '~> 2.10'
30
+
31
+ spec.add_development_dependency 'bundler'
32
+ spec.add_development_dependency 'pry-byebug'
33
+ spec.add_development_dependency 'rabbitmq_http_api_client', '~> 1.13'
34
+ spec.add_development_dependency 'rake'
35
+ spec.add_development_dependency 'rspec'
36
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bunny_publisher'
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bunny'
4
+ require 'bunny_publisher/version'
5
+ require 'bunny_publisher/callbacks'
6
+ require 'bunny_publisher/errors'
7
+ require 'bunny_publisher/base'
8
+ require 'bunny_publisher/mandatory'
9
+
10
+ module BunnyPublisher
11
+ class << self
12
+ def publish(message, options = {})
13
+ publisher.publish(message, options)
14
+ end
15
+
16
+ def publisher
17
+ @publisher ||= Base.new
18
+ end
19
+
20
+ def configure
21
+ require 'ostruct'
22
+
23
+ config = OpenStruct.new({})
24
+
25
+ yield(config)
26
+
27
+ klass = Class.new(Base).tap { |k| k.include(::BunnyPublisher::Mandatory) if config.delete_field(:mandatory) }
28
+
29
+ @publisher = klass.new(config.to_h)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BunnyPublisher
4
+ # Based on Publisher of Sneakers
5
+ # https://github.com/jondot/sneakers/blob/ed620b642b447701be490666ee284cf7d60ccf22/lib/sneakers/publisher.rb
6
+ class Base
7
+ include Callbacks
8
+
9
+ define_callbacks :after_publish, :before_publish, :around_publish
10
+
11
+ attr_reader :connection, :channel, :exchange
12
+
13
+ def initialize(publish_connection: nil, connection: nil, exchange: nil, exchange_options: {}, **options)
14
+ @mutex = Mutex.new
15
+
16
+ @exchange_name = exchange
17
+ @exchange_options = exchange_options
18
+ @options = options
19
+
20
+ # Arguments are compatible with Sneakers::CONFIG and if connection given publisher will use it.
21
+ # But using of same connection for publishing & consumers could cause problems.
22
+ # https://www.cloudamqp.com/blog/2017-12-29-part1-rabbitmq-best-practice.html#separate-connections-for-publisher-and-consumer
23
+ # Therefore, publish_connection allows to explicitly make publishers use different connection
24
+ @connection = publish_connection || connection
25
+ end
26
+
27
+ def publish(message, options = {})
28
+ ensure_connection!
29
+
30
+ run_callback(:before_publish, message, options)
31
+ result = run_callback(:around_publish, message, options) { exchange.publish(message, options) }
32
+ run_callback(:after_publish, message, options)
33
+
34
+ result
35
+ end
36
+
37
+ def close
38
+ connection&.close
39
+ end
40
+
41
+ alias stop close
42
+
43
+ private
44
+
45
+ def ensure_connection!
46
+ @mutex.synchronize { connect! unless connected? }
47
+ end
48
+
49
+ def connect!
50
+ @connection ||= build_connection
51
+ connection.start
52
+ @channel = connection.create_channel
53
+ @exchange = build_exchange
54
+ end
55
+
56
+ def build_connection
57
+ Bunny.new(@options[:amqp] || ENV['RABBITMQ_URL'], @options)
58
+ end
59
+
60
+ def build_exchange
61
+ return channel.default_exchange if @exchange_name.nil? || @exchange_name == ''
62
+
63
+ channel.exchange(@exchange_name, @exchange_options)
64
+ end
65
+
66
+ def connected?
67
+ @connection&.connected? && channel
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BunnyPublisher
4
+ # Adds support for callbacks (one per event!)
5
+ module Callbacks
6
+ def self.included(klass)
7
+ klass.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def define_callbacks(*events)
12
+ events.each do |event|
13
+ singleton_class.define_method(event) do |method_or_proc|
14
+ callbacks[event] = method_or_proc
15
+ end
16
+ end
17
+ end
18
+
19
+ def callbacks
20
+ @callbacks ||= {}
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def run_callback(event, *args, &block)
27
+ case (callback = callback_for_event(event))
28
+ when nil
29
+ yield if block_given?
30
+ when Symbol
31
+ send(callback, self, *args, &block)
32
+ when Proc
33
+ callback.call(self, *args, &block)
34
+ end
35
+ end
36
+
37
+ def callback_for_event(event)
38
+ self.class.callbacks[event]
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BunnyPublisher
4
+ class PublishError < StandardError; end
5
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BunnyPublisher
4
+ # Enforces mandatory option for message publishing.
5
+ # Catches returned message if they are not routed.
6
+ # Creates queue/binding before re-publishing the same message again.
7
+ # This publisher DUPLICATES the connection for re-publishing messages!
8
+ module Mandatory
9
+ def self.included(klass)
10
+ klass.define_callbacks :on_message_return,
11
+ :after_republish,
12
+ :before_republish,
13
+ :around_republish
14
+ end
15
+
16
+ attr_reader :queue_name, :queue_options
17
+
18
+ def initialize(republish_connection: nil, queue: nil, queue_options: {}, timeout_at_exit: 5, **options)
19
+ super(options)
20
+
21
+ @queue_name = queue
22
+ @queue_options = queue_options
23
+
24
+ @republish_mutex = Mutex.new
25
+ @republish_connection = republish_connection
26
+
27
+ at_exit { wait_for_unrouted_messages_processing(timeout: timeout_at_exit) }
28
+ end
29
+
30
+ def publish(message, options = {})
31
+ super(message, options.merge(mandatory: true))
32
+ end
33
+
34
+ def close
35
+ republish_connection&.close
36
+
37
+ super
38
+ end
39
+
40
+ alias stop close
41
+
42
+ def declare_republish_queue(return_info, _properties, _message)
43
+ republish_channel.queue(queue_name || return_info.routing_key, queue_options)
44
+ end
45
+
46
+ def declare_republish_queue_binding(queue, return_info, _properties, _message)
47
+ queue.bind(republish_exchange, routing_key: return_info.routing_key)
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :republish_connection, :republish_channel, :republish_exchange
53
+
54
+ def connect!
55
+ super
56
+
57
+ # `on_return` is called within a frameset of amqp connection.
58
+ # Any interaction within the same connection leads to error. This is why we need extra connection.
59
+ # https://github.com/ruby-amqp/bunny/blob/7fb05abf36637557f75a69790be78f9cc1cea807/lib/bunny/session.rb#L683
60
+ if callback_for_event(:on_message_return)
61
+ exchange.on_return { |*attrs| run_callback(:on_message_return, *attrs) }
62
+ else
63
+ exchange.on_return { |*attrs| on_message_return(*attrs) }
64
+ end
65
+ end
66
+
67
+ def ensure_republish_connection!
68
+ @republish_mutex.synchronize { connect_for_republish! unless connected_for_republish? }
69
+ end
70
+
71
+ def connected_for_republish?
72
+ republish_connection&.connected? && republish_channel
73
+ end
74
+
75
+ def connect_for_republish!
76
+ @republish_connection ||= build_republish_connection
77
+ republish_connection.start
78
+ @republish_channel = republish_connection.create_channel
79
+ @republish_exchange = clone_exchange_for_republish
80
+ end
81
+
82
+ def build_republish_connection
83
+ Bunny.new(connection.instance_variable_get(:'@opts')) # TODO: find more elegant way to "clone" connection
84
+ end
85
+
86
+ def clone_exchange_for_republish
87
+ republish_channel.default_exchange if exchange.name == ''
88
+
89
+ republish_channel.exchange exchange.name,
90
+ exchange.instance_variable_get(:'@options').merge(type: exchange.type)
91
+ end
92
+
93
+ def on_message_return(return_info, properties, message)
94
+ @unrouted_message = true
95
+
96
+ ensure_message_is_unrouted!(return_info, properties, message)
97
+
98
+ setup_queue_for_republish(return_info, properties, message)
99
+
100
+ run_callback(:before_republish, return_info, properties, message)
101
+ result = run_callback(:around_republish, return_info, properties, message) do
102
+ republish_exchange.publish(message, properties.to_h.merge(routing_key: return_info.routing_key))
103
+ end
104
+ run_callback(:after_republish, return_info, properties, message)
105
+
106
+ @unrouted_message = false
107
+ result
108
+ end
109
+
110
+ def setup_queue_for_republish(return_info, properties, message)
111
+ ensure_republish_connection!
112
+
113
+ queue = declare_republish_queue(return_info, properties, message)
114
+
115
+ # default exchange already has bindings with queues
116
+ declare_republish_queue_binding(queue, return_info, properties, message) unless republish_exchange.name == ''
117
+
118
+ republish_channel.deregister_queue(queue) # we are not going to work with this queue in this channel
119
+ end
120
+
121
+ def ensure_message_is_unrouted!(return_info, properties, message)
122
+ return if return_info.reply_text == 'NO_ROUTE'
123
+
124
+ raise BunnyPublisher::PublishError, message: message,
125
+ return_info: return_info,
126
+ properties: properties
127
+ end
128
+
129
+ # TODO: introduce more reliable way to wait for handling of unrouted messages at exit
130
+ def wait_for_unrouted_messages_processing(timeout:)
131
+ sleep(0.05) # gives exchange some time to receive retuned message
132
+
133
+ return unless @unrouted_message
134
+
135
+ puts("Waiting up to #{timeout} seconds for unrouted messages handling")
136
+
137
+ Timeout.timeout(timeout) { sleep 0.01 while @unrouted_message }
138
+ rescue Timeout::Error
139
+ puts('Some unrouted messages are lost on process exit!')
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BunnyPublisher
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bunny-publisher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rustam Sharshenov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bunny
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rabbitmq_http_api_client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: AMQP publisher for RabbitMQ based on Bunny
98
+ email:
99
+ - rustam@sharshenov.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".rubocop.yml"
107
+ - ".travis.yml"
108
+ - CHANGELOG.md
109
+ - Gemfile
110
+ - LICENSE.txt
111
+ - README.md
112
+ - Rakefile
113
+ - bunny-publisher.gemspec
114
+ - lib/bunny/publisher.rb
115
+ - lib/bunny_publisher.rb
116
+ - lib/bunny_publisher/base.rb
117
+ - lib/bunny_publisher/callbacks.rb
118
+ - lib/bunny_publisher/errors.rb
119
+ - lib/bunny_publisher/mandatory.rb
120
+ - lib/bunny_publisher/version.rb
121
+ homepage: https://github.com/veeqo/bunny-publisher
122
+ licenses:
123
+ - MIT
124
+ metadata:
125
+ homepage_uri: https://github.com/veeqo/bunny-publisher
126
+ source_code_uri: https://github.com/veeqo/bunny-publisher
127
+ changelog_uri: https://github.com/veeqo/bunny-publisher/blob/master/CHANGELOG.md
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubygems_version: 3.0.8
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: AMQP publisher for RabbitMQ based on Bunny
147
+ test_files: []