artery 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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +43 -0
  4. data/Rakefile +29 -0
  5. data/app/models/concerns/artery/message_model.rb +53 -0
  6. data/config/routes.rb +4 -0
  7. data/db/migrate/20170110090949_create_artery_messages.rb +14 -0
  8. data/db/migrate/20170116143013_create_artery_last_model_updates.rb +10 -0
  9. data/db/migrate/20170420155129_change_last_model_updates_to_subscription_infos.rb +17 -0
  10. data/db/migrate/20171020104646_add_subscriber_to_artery_subscription_infos.rb +5 -0
  11. data/db/migrate/20181211110018_add_latest_index_to_artery_subscription_infos.rb +5 -0
  12. data/db/migrate/20200109120304_add_index_on_model_to_artery_messages.rb +9 -0
  13. data/db/migrate/20200109120305_remove_last_message_at_from_artery_subscription_infos.rb +9 -0
  14. data/db/migrate/20240411120304_add_synchronization_heartbeat_to_artery_subscription_infos.rb +11 -0
  15. data/exe/artery-check +9 -0
  16. data/exe/artery-clean +9 -0
  17. data/exe/artery-sync +9 -0
  18. data/exe/artery-worker +9 -0
  19. data/lib/artery/active_record/message.rb +41 -0
  20. data/lib/artery/active_record/subscription_info.rb +50 -0
  21. data/lib/artery/active_record.rb +8 -0
  22. data/lib/artery/backend.rb +95 -0
  23. data/lib/artery/backends/base.rb +41 -0
  24. data/lib/artery/backends/fake.rb +24 -0
  25. data/lib/artery/backends/nats_pure.rb +86 -0
  26. data/lib/artery/check.rb +48 -0
  27. data/lib/artery/config.rb +72 -0
  28. data/lib/artery/engine.rb +16 -0
  29. data/lib/artery/errors.rb +76 -0
  30. data/lib/artery/healthz_subscription.rb +14 -0
  31. data/lib/artery/model/callbacks.rb +40 -0
  32. data/lib/artery/model/subscriptions.rb +147 -0
  33. data/lib/artery/model.rb +96 -0
  34. data/lib/artery/no_brainer/message.rb +67 -0
  35. data/lib/artery/no_brainer/subscription_info.rb +67 -0
  36. data/lib/artery/no_brainer.rb +8 -0
  37. data/lib/artery/routing.rb +63 -0
  38. data/lib/artery/subscription/incoming_message.rb +94 -0
  39. data/lib/artery/subscription/synchronization.rb +221 -0
  40. data/lib/artery/subscription.rb +136 -0
  41. data/lib/artery/subscriptions.rb +42 -0
  42. data/lib/artery/sync.rb +35 -0
  43. data/lib/artery/version.rb +5 -0
  44. data/lib/artery/worker.rb +75 -0
  45. data/lib/artery/worker_healthz_subscription.rb +23 -0
  46. data/lib/artery.rb +56 -0
  47. data/lib/multiblock_has_block.rb +11 -0
  48. data/lib/tasks/artery_tasks.rake +6 -0
  49. metadata +160 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1e98c3102570a6fbac048ecad94e61d537475a13e74d64108d41a5538e209a65
4
+ data.tar.gz: b1d4dee0d394e6d3e358a8bcf53d46130ec4f6cb342bcbfddf281b8700d3d8d5
5
+ SHA512:
6
+ metadata.gz: 0afa95d312f8863dfcec43024bd910552021176eea27779ed34eca7f8d4ff36031e0b62572a8ca234d893eaa1e739eed3e4774e5e4a566d7564b2b1dede49ad8
7
+ data.tar.gz: 272299e4ad9c8df1fa915e53d1629ea102187c0e828e17d3d714d0d792ead53cf64539b1a9819a79337c890c5ee30521b03efaf026a371684c7f64edd1e5d753
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Sergey Gnuskov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # Artery
2
+ Main messaging system between Rails [micro]services implementing message bus pattern on NATS (for now).
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'artery'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install artery
22
+ ```
23
+
24
+ Then install migrations and run (if using ActiveRecord):
25
+ ```bash
26
+ $ rake artery:install:migrations
27
+ $ rake db:migrate
28
+ ```
29
+
30
+ ## Admin interface
31
+
32
+ In admin interface you can list your artery endpoints and check their statuses.
33
+ You can mount admin ui to your routes via:
34
+ ```ruby
35
+ mount Artery::Engine => '/artery'
36
+ ```
37
+ And then you can access it by url `http(s)://{ your_app_url }/artery/`.
38
+
39
+ ## Contributing
40
+ Contribution directions go here.
41
+
42
+ ## License
43
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'Artery'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
25
+ require 'rspec/core/rake_task'
26
+
27
+ RSpec::Core::RakeTask.new(:spec)
28
+
29
+ task default: :spec
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ module MessageModel
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ end
9
+
10
+ module ClassMethods
11
+ MAX_MESSAGE_AGE = ENV.fetch('ARTERY_MAX_MESSAGE_AGE', '90').to_i.days
12
+
13
+ def after_index(model, index)
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def latest_index(model)
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def delete_old
22
+ raise NotImplementedError
23
+ end
24
+ end
25
+
26
+ def uri
27
+ Artery::Routing.uri(model: model, action: action)
28
+ end
29
+
30
+ def uri=(uri)
31
+ self.model = uri.model
32
+ self.action = uri.action
33
+ end
34
+
35
+ def route
36
+ uri.to_route
37
+ end
38
+
39
+ def to_artery
40
+ data.merge('_index' => index)
41
+ end
42
+
43
+ def previous_index
44
+ raise NotImplementedError
45
+ end
46
+
47
+ protected
48
+
49
+ def send_to_artery
50
+ Artery.publish route, to_artery.merge('_previous_index' => previous_index)
51
+ end
52
+ end
53
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ Artery::Engine.routes.draw do
4
+ end
@@ -0,0 +1,14 @@
1
+ class CreateArteryMessages < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :artery_messages do |t|
4
+ t.string :version
5
+
6
+ t.string :model, null: false
7
+ t.string :action, null: false
8
+
9
+ t.text :data, null: false
10
+
11
+ t.timestamp :created_at, limit: 6
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ class CreateArteryLastModelUpdates < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :artery_last_model_updates do |t|
4
+ t.string :service, null: false
5
+ t.string :model, null: false
6
+
7
+ t.timestamp :last_message_at, limit: 6, null: false
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ class ChangeLastModelUpdatesToSubscriptionInfos < ActiveRecord::Migration[5.0]
2
+ def up
3
+ rename_table :artery_last_model_updates, :artery_subscription_infos
4
+
5
+ change_column :artery_subscription_infos, :last_message_at, :timestamp, limit: 6, null: true
6
+ add_column :artery_subscription_infos, :synchronization_in_progress, :boolean, default: false
7
+ add_column :artery_subscription_infos, :synchronization_page, :integer
8
+ end
9
+
10
+ def down
11
+ remove_column :artery_subscription_infos, :synchronization_in_progress
12
+ remove_column :artery_subscription_infos, :synchronization_page
13
+ change_column :artery_subscription_infos, :last_message_at, :timestamp, limit: 6, null: false
14
+
15
+ rename_table :artery_subscription_infos, :artery_last_model_updates
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ class AddSubscriberToArterySubscriptionInfos < ActiveRecord::Migration[5.0]
2
+ def change
3
+ add_column :artery_subscription_infos, :subscriber, :string, after: :id
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddLatestIndexToArterySubscriptionInfos < ActiveRecord::Migration[5.0]
2
+ def change
3
+ add_column :artery_subscription_infos, :latest_index, :integer, unsigned: true
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ class AddIndexOnModelToArteryMessages < ActiveRecord::Migration[5.2]
2
+ def up
3
+ add_index :artery_messages, :model unless index_exists?(:artery_messages, :model)
4
+ end
5
+
6
+ def down
7
+ remove_index :artery_messages, :model
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class RemoveLastMessageAtFromArterySubscriptionInfos < ActiveRecord::Migration[5.2]
2
+ def up
3
+ remove_column :artery_subscription_infos, :last_message_at if column_exists?(:artery_subscription_infos, :last_message_at)
4
+ end
5
+
6
+ def down
7
+ add_column :artery_subscription_infos, :last_message_at, :timestamp
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ class AddSynchronizationHeartbeatToArterySubscriptionInfos < ActiveRecord::Migration[5.2]
2
+ def up
3
+ return if column_exists?(:artery_subscription_infos, :synchronization_heartbeat)
4
+
5
+ add_column :artery_subscription_infos, :synchronization_heartbeat, :timestamp, after: :synchronization_in_progress
6
+ end
7
+
8
+ def down
9
+ remove_column :artery_subscription_infos, :synchronization_heartbeat
10
+ end
11
+ end
data/exe/artery-check ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require ENV['APP_PATH'] || File.join(File.expand_path('.'), 'config', 'application')
5
+
6
+ Rails.application.initialize!
7
+ Rails.application.eager_load!
8
+
9
+ Artery::Check.run ARGV.map(&:strip)
data/exe/artery-clean ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require ENV['APP_PATH'] || File.join(File.expand_path('.'), 'config', 'application')
5
+
6
+ Rails.application.initialize!
7
+ Rails.application.eager_load!
8
+
9
+ Artery.message_class.delete_old
data/exe/artery-sync ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require ENV['APP_PATH'] || File.join(File.expand_path('.'), 'config', 'application')
5
+
6
+ Rails.application.initialize!
7
+ Rails.application.eager_load!
8
+
9
+ Artery::Sync.run ARGV.map(&:strip)
data/exe/artery-worker ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require ENV['APP_PATH'] || File.join(File.expand_path('.'), 'config', 'application')
5
+
6
+ Rails.application.initialize!
7
+ Rails.application.eager_load!
8
+
9
+ Artery::Worker.new.run ARGV.map(&:strip)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ module ActiveRecord
5
+ class Message < ::ActiveRecord::Base
6
+ include MessageModel
7
+
8
+ self.table_name = 'artery_messages'
9
+
10
+ serialize :data, coder: JSON
11
+
12
+ after_commit :send_to_artery, on: :create
13
+
14
+ alias index id
15
+
16
+ class << self
17
+ def after_index(model, index)
18
+ where(model: model)
19
+ .where(arel_table[:id].gt(index)).order(:id)
20
+ end
21
+
22
+ def latest_index(model)
23
+ where(model: model).last&.id.to_i
24
+ end
25
+
26
+ def delete_old
27
+ max_aged_id = where(arel_table[:created_at].lt(MAX_MESSAGE_AGE.ago)).maximum(:id)
28
+ where(arel_table[:id].lteq(max_aged_id)).delete_all if max_aged_id.to_i.positive?
29
+ end
30
+ end
31
+
32
+ # It is used in after_commit, so we always know previous index based on our current index
33
+ def previous_index
34
+ scope = self.class.where(model: model).order(:id)
35
+ scope = scope.where(self.class.arel_table[:id].lt(index)) if index
36
+
37
+ scope.select(:id).last&.id.to_i
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ module ActiveRecord
5
+ class SubscriptionInfo < ::ActiveRecord::Base
6
+ self.table_name = 'artery_subscription_infos'
7
+
8
+ class << self
9
+ def find_for_subscription(subscription)
10
+ info = find_or_initialize_by(subscriber: subscription.subscriber.to_s,
11
+ service: subscription.uri.service,
12
+ model: subscription.uri.model)
13
+
14
+ info.save! if info.new_record?
15
+ info
16
+ end
17
+ end
18
+
19
+ def synchronization_transaction(&block)
20
+ with_lock(&block)
21
+ end
22
+
23
+ def with_lock
24
+ self.class.transaction do
25
+ unless (was_locked = @locked) # prevent double lock to reduce selects
26
+ Artery.logger.debug "WAITING FOR LOCK... [LATEST_INDEX: #{latest_index}]"
27
+
28
+ reload lock: true # explicitely reload record
29
+
30
+ Artery.logger.debug "GOT LOCK! [LATEST_INDEX: #{latest_index}]"
31
+
32
+ @locked = true
33
+ end
34
+
35
+ yield
36
+ ensure
37
+ @locked = false unless was_locked
38
+ end
39
+ end
40
+
41
+ def lock_for_message(message, &blk)
42
+ if message.has_index? # only 'indexed' messages should lock
43
+ with_lock(&blk)
44
+ else
45
+ yield
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ module ActiveRecord
5
+ autoload :Message, 'artery/active_record/message'
6
+ autoload :SubscriptionInfo, 'artery/active_record/subscription_info'
7
+ end
8
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ module Backend
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class << self
9
+ attr_reader :backend_in_use, :backends
10
+
11
+ def register_backend(type, class_name)
12
+ @backends ||= {}
13
+ @backends[type.to_sym] = class_name
14
+ end
15
+
16
+ def use_backend(type)
17
+ type = type.to_sym
18
+ raise ArgumentError, "Artery has no registered backend '#{type}'" unless backends.key?(type)
19
+
20
+ @backend_in_use = type
21
+
22
+ @backend&.stop
23
+ @backend = nil
24
+ end
25
+
26
+ def backend
27
+ @backend ||= begin
28
+ backend_class = Artery::Backends.const_get(backends[backend_in_use])
29
+ backend_class.new backend_config
30
+ rescue LoadError, NameError => e
31
+ raise "Unable to load backend #{type}: #{e.message}"
32
+ end
33
+ end
34
+
35
+ delegate :start, :stop, :connect, :unsubscribe, to: :backend
36
+ end
37
+ end
38
+
39
+ module ClassMethods
40
+ def subscribe(route, options = {})
41
+ backend.subscribe(route, options) do |json, reply, from|
42
+ json = '{}' if json.blank?
43
+
44
+ yield(JSON.parse(json).with_indifferent_access, reply, from)
45
+ rescue StandardError => e
46
+ Artery.handle_error FormatError.new(from, json, original_exception: e,
47
+ subscription: { route: from, data: json })
48
+ end
49
+ end
50
+
51
+ # rubocop:disable Metrics/AbcSize
52
+ def request(route, data = nil, options = {})
53
+ raise ArgumentError, 'You must provide block to handle response' unless block_given?
54
+
55
+ handler = Multiblock.wrapper
56
+ uri = Routing.uri(route)
57
+
58
+ yield(handler)
59
+
60
+ data ||= {}
61
+ Artery.logger.debug "REQUESTED: <#{uri.to_route}> #{data.to_json}"
62
+
63
+ backend.request(uri.to_route, data.to_json, options) do |message|
64
+ if message.is_a?(Error) # timeout case
65
+ Artery.logger.debug "REQUEST ERROR: <#{uri.to_route}> #{message.message}"
66
+ handler.call :error, message
67
+ else
68
+ Artery.logger.debug "REQUEST RESPONSE: <#{uri.to_route}> #{message}"
69
+ begin
70
+ message ||= '{}'
71
+ response = JSON.parse(message).with_indifferent_access
72
+
73
+ if response.key?(:error)
74
+ handler.call :error, RequestError.new(uri, response, request: { route: uri.to_route, data: data.to_json },
75
+ response: message)
76
+ else
77
+ handler.call :success, response
78
+ end
79
+ rescue JSON::ParserError => e
80
+ Artery.handle_error FormatError.new(uri, message, original_exception: e,
81
+ request: { route: uri.to_route, data: data.to_json },
82
+ response: message)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ # rubocop:enable Metrics/AbcSize
88
+
89
+ def publish(route, data)
90
+ backend.publish(route, data.to_json)
91
+ Artery.logger.debug "PUBLISHED: <#{route}> #{data.to_json}"
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ module Backends
5
+ class Base
6
+ attr_accessor :config
7
+
8
+ def initialize(config = {})
9
+ @config = config
10
+ end
11
+
12
+ def start(*_args)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def connect(*_args)
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def subscribe(*_args)
21
+ raise NotImplementedError
22
+ end
23
+
24
+ def unsubscribe(*_args)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def publish(*_args)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def request(*_args)
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def stop(*_args)
37
+ raise NotImplementedError
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # For the test environment
4
+ module Artery
5
+ module Backends
6
+ class Fake < Base
7
+ def start(*_args); end
8
+
9
+ def connect(*_args); end
10
+
11
+ def subscribe(*_args); end
12
+
13
+ def unsubscribe(*_args); end
14
+
15
+ def publish(*_args); end
16
+
17
+ def request(_route, _data, _opts = {})
18
+ yield if block_given?
19
+ end
20
+
21
+ def stop(*_args); end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nats/client'
4
+
5
+ module Artery
6
+ module Backends
7
+ class NATSPure < Base
8
+ def client
9
+ @client || connect
10
+ end
11
+
12
+ delegate :connected?, :connecting?, :subscribe, to: :client
13
+
14
+ def connect
15
+ @client ||= begin
16
+ client = ::NATS.connect(options)
17
+
18
+ client.on_reconnect do
19
+ Artery.logger.debug "Reconnected to server at #{client.connected_server}"
20
+ end
21
+
22
+ client.on_disconnect do
23
+ Artery.logger.debug 'Disconnected!'
24
+ end
25
+
26
+ client.on_close do
27
+ Artery.logger.debug 'Connection to NATS closed'
28
+ end
29
+ client
30
+ end
31
+
32
+ Artery.logger.debug "Connected to #{@client.connected_server}"
33
+ @client.connect unless @client.connected?
34
+
35
+ @client
36
+ end
37
+
38
+ def stop
39
+ client.close
40
+ @stop = true
41
+ end
42
+
43
+ def start
44
+ @stop = false
45
+ connect
46
+
47
+ yield
48
+
49
+ sleep 0.1 until @stop
50
+ end
51
+
52
+ def request(route, data, opts = {})
53
+ opts[:timeout] ||= Artery.request_timeout
54
+ # Always synchronous for now
55
+ response = client.request route, data, **opts
56
+ yield response.data
57
+ rescue ::NATS::Timeout
58
+ yield(TimeoutError.new(request: { route: route, data: data }))
59
+ end
60
+
61
+ def publish(route, data)
62
+ client.publish route, data
63
+ end
64
+
65
+ private
66
+
67
+ def options
68
+ options = {}
69
+
70
+ options[:servers] = config[:servers] unless config[:servers].blank?
71
+ options[:user] = config[:user] unless config[:user].blank?
72
+ options[:pass] = config[:password] unless config[:password].blank?
73
+
74
+ options[:reconnect_time_wait] = config[:reconnect_timeout] unless config[:reconnect_timeout].blank?
75
+ options[:max_reconnect_attempts] = config[:reconnect_attempts] unless config[:reconnect_attempts].blank?
76
+
77
+ if ENV.key?('NATS_URL')
78
+ options[:servers] ||= []
79
+ options[:servers] << ENV['NATS_URL']
80
+ end
81
+
82
+ options
83
+ end
84
+ end
85
+ end
86
+ end