gorynich 1.1.0.142046

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 (94) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +414 -0
  4. data/Rakefile +20 -0
  5. data/config/locales/en.yml +6 -0
  6. data/lib/generators/gorynich/gorynich_generator.rb +9 -0
  7. data/lib/generators/gorynich/install_generator.rb +17 -0
  8. data/lib/generators/templates/gorynich.rb +32 -0
  9. data/lib/gorynich/config.rb +241 -0
  10. data/lib/gorynich/configuration.rb +17 -0
  11. data/lib/gorynich/current.rb +13 -0
  12. data/lib/gorynich/engine.rb +11 -0
  13. data/lib/gorynich/fetcher.rb +54 -0
  14. data/lib/gorynich/fetchers/consul.rb +19 -0
  15. data/lib/gorynich/fetchers/file.rb +22 -0
  16. data/lib/gorynich/head/action_cable.rb +44 -0
  17. data/lib/gorynich/head/active_job.rb +36 -0
  18. data/lib/gorynich/head/active_record.rb +46 -0
  19. data/lib/gorynich/head/delayed_job.rb +22 -0
  20. data/lib/gorynich/head/rack_middleware.rb +34 -0
  21. data/lib/gorynich/head.rb +9 -0
  22. data/lib/gorynich/switcher.rb +48 -0
  23. data/lib/gorynich/version.rb +3 -0
  24. data/lib/gorynich.rb +137 -0
  25. data/lib/tasks/gorynich_tasks.rake +27 -0
  26. data/spec/dummy/Rakefile +6 -0
  27. data/spec/dummy/app/assets/config/manifest.js +3 -0
  28. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  29. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  30. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  31. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  32. data/spec/dummy/app/controllers/users_controller.rb +10 -0
  33. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  34. data/spec/dummy/app/javascript/packs/application.js +15 -0
  35. data/spec/dummy/app/jobs/application_job.rb +7 -0
  36. data/spec/dummy/app/jobs/test_job.rb +6 -0
  37. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  38. data/spec/dummy/app/models/application_record.rb +3 -0
  39. data/spec/dummy/app/models/user.rb +2 -0
  40. data/spec/dummy/app/views/layouts/application.html.erb +13 -0
  41. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  42. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  43. data/spec/dummy/app/views/users/index.html.erb +9 -0
  44. data/spec/dummy/bin/rails +4 -0
  45. data/spec/dummy/bin/rake +4 -0
  46. data/spec/dummy/bin/setup +33 -0
  47. data/spec/dummy/config/application.rb +34 -0
  48. data/spec/dummy/config/boot.rb +5 -0
  49. data/spec/dummy/config/cable.yml +10 -0
  50. data/spec/dummy/config/database.yml +3 -0
  51. data/spec/dummy/config/environment.rb +5 -0
  52. data/spec/dummy/config/environments/development.rb +46 -0
  53. data/spec/dummy/config/environments/production.rb +112 -0
  54. data/spec/dummy/config/environments/test.rb +49 -0
  55. data/spec/dummy/config/gorynich_config.yml +49 -0
  56. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  57. data/spec/dummy/config/initializers/assets.rb +12 -0
  58. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  59. data/spec/dummy/config/initializers/content_security_policy.rb +28 -0
  60. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  61. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  62. data/spec/dummy/config/initializers/gorynich.rb +34 -0
  63. data/spec/dummy/config/initializers/inflections.rb +16 -0
  64. data/spec/dummy/config/initializers/middleware.rb +0 -0
  65. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  66. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  67. data/spec/dummy/config/locales/en.yml +33 -0
  68. data/spec/dummy/config/puma.rb +38 -0
  69. data/spec/dummy/config/routes.rb +5 -0
  70. data/spec/dummy/config/spring.rb +6 -0
  71. data/spec/dummy/config/storage.yml +34 -0
  72. data/spec/dummy/config.ru +5 -0
  73. data/spec/dummy/db/migrate/20220907101031_create_users.rb +8 -0
  74. data/spec/dummy/public/404.html +67 -0
  75. data/spec/dummy/public/422.html +67 -0
  76. data/spec/dummy/public/500.html +66 -0
  77. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  78. data/spec/dummy/public/apple-touch-icon.png +0 -0
  79. data/spec/dummy/public/favicon.ico +0 -0
  80. data/spec/dummy/tmp/development_secret.txt +1 -0
  81. data/spec/fixtures/env_test_database.yml +7 -0
  82. data/spec/fixtures/fetchers/consul_config.yml +1 -0
  83. data/spec/fixtures/fetchers/file_config.yml +46 -0
  84. data/spec/fixtures/fetchers/wrong_file_config.yml +44 -0
  85. data/spec/fixtures/test_database.yml +1 -0
  86. data/spec/lib/gorynich/config_spec.rb +428 -0
  87. data/spec/lib/gorynich/fetcher_spec.rb +112 -0
  88. data/spec/lib/gorynich/fetchers/consul_spec.rb +58 -0
  89. data/spec/lib/gorynich/fetchers/file_spec.rb +29 -0
  90. data/spec/lib/gorynich/version_spec.rb +6 -0
  91. data/spec/lib/gorynich_spec.rb +99 -0
  92. data/spec/rails_helper.rb +8 -0
  93. data/spec/spec_helper.rb +48 -0
  94. metadata +458 -0
@@ -0,0 +1,241 @@
1
+ module Gorynich
2
+ class Config
3
+ attr_reader :fetcher,
4
+ :tenants,
5
+ :databases,
6
+ :hosts,
7
+ :uris,
8
+ :default
9
+
10
+ #
11
+ # Create instance of config
12
+ #
13
+ # @param [Fetcher] fetcher data loader
14
+ #
15
+ def initialize(**opts)
16
+ @default = 'default'
17
+ @fetcher = opts.fetch(:fetcher, Fetcher.new)
18
+ @mx = Mutex.new
19
+
20
+ actualize
21
+ end
22
+
23
+ #
24
+ # Update configs from data source
25
+ #
26
+ def actualize
27
+ cfg = fetcher.fetch.fetch(Rails.env)
28
+
29
+ @mx.synchronize do
30
+ @tenants = tenants_from_config(cfg)
31
+ @databases = databases_from_config(cfg)
32
+ @secrets = secrets_from_config(cfg)
33
+ @uris = uris_from_config(@secrets)
34
+ @hosts = hosts_from_config(@secrets)
35
+ end
36
+ end
37
+
38
+ #
39
+ # Database config
40
+ #
41
+ # @param [String, Symbol] tenant
42
+ #
43
+ # @return [Hash]
44
+ #
45
+ def database(tenant)
46
+ databases.fetch(tenant.to_s)
47
+ rescue StandardError
48
+ raise TenantNotFound, tenant
49
+ end
50
+
51
+ %i[uris hosts secrets].each do |name|
52
+ define_method(name) do |tenant = nil|
53
+ values = instance_variable_get("@#{name}")
54
+ return values if tenant.nil?
55
+
56
+ values.fetch(tenant.to_s)
57
+ rescue StandardError
58
+ raise TenantNotFound, tenant
59
+ end
60
+ end
61
+
62
+ #
63
+ # Find tenant by URI
64
+ #
65
+ # @param [String] uri
66
+ #
67
+ # @return [String]
68
+ #
69
+ def tenant_by_uri(uri)
70
+ uri = URI(uri)
71
+ search_tenant = uris.select do |tenant, tenant_uris|
72
+ tenant if tenant_uris.map { |t_uri| URI(t_uri) }.include?(uri)
73
+ end.keys.first
74
+
75
+ raise UriNotFound, uri.host if search_tenant.nil?
76
+
77
+ search_tenant
78
+ end
79
+
80
+ #
81
+ # Find tenant by host
82
+ #
83
+ # @param [String] host
84
+ #
85
+ # @return [String]
86
+ #
87
+ def tenant_by_host(host)
88
+ tenant = hosts.select { |t, h| t if h.include?(host) }.keys.first
89
+ raise HostNotFound, host if tenant.nil?
90
+
91
+ tenant
92
+ end
93
+
94
+ #
95
+ # Find URI by host
96
+ #
97
+ # @param [String] host
98
+ # @param [String, Symbol] tenant tenant of config (optional)
99
+ #
100
+ # @return [String]
101
+ #
102
+ def uri_by_host(host, tenant = nil)
103
+ tenant ||= tenant_by_host(host)
104
+ tenant_uris = uris(tenant)
105
+ search_uri = tenant_uris.select { |uri| uri.include?(host) }.first
106
+
107
+ raise UriNotFound, search_uri.host if search_uri.nil?
108
+
109
+ search_uri
110
+ end
111
+
112
+ #
113
+ # Full config from data source by tenant
114
+ #
115
+ # @param [String, Symbol] tenant
116
+ #
117
+ # @return [Hash]
118
+ #
119
+ def config(tenant)
120
+ {
121
+ tenant: tenant.to_s,
122
+ database: database(tenant),
123
+ secrets: secrets(tenant)
124
+ }
125
+ end
126
+
127
+ #
128
+ # Database config for database.yml
129
+ #
130
+ # @param [String] env enviroment
131
+ # @param [Boolean] with_ignore ignore if there is no configuration for the environment
132
+ #
133
+ # @return [String] yaml result
134
+ #
135
+ def database_config(env = nil, fail_ignore: false)
136
+ envs = Dir.glob(Rails.root.join('config/environments/*.rb').to_s).map { |f| File.basename(f, '.rb') }
137
+ cfg = fetcher.fetch.extract!(*envs)
138
+
139
+ result =
140
+ if env.nil?
141
+ cfg.to_h do |cfg_env, tenant_cfg|
142
+ [
143
+ cfg_env,
144
+ configs_sort(tenant_cfg).to_h { |t, c| [t, c.fetch('db_config')] }
145
+ ]
146
+ end
147
+ else
148
+ if fail_ignore && cfg.fetch(env, nil).nil?
149
+ { env => nil}
150
+ else
151
+ {
152
+ env => configs_sort(cfg.fetch(env)).to_h { |t, c| [t, c.fetch('db_config')] }
153
+ }
154
+ end
155
+ end
156
+
157
+ result.to_yaml.gsub('---', '')
158
+ end
159
+
160
+ #
161
+ # For connection to ActiveRecord
162
+ #
163
+ # @return [Hash]
164
+ #
165
+ def connects_to_config
166
+ actualize
167
+ tenants.each_with_object({ default: :default }) do |tenant, cfg|
168
+ cfg[tenant.to_sym] = tenant.to_sym
169
+ end
170
+ end
171
+
172
+ private
173
+
174
+ def databases_from_config(cfg)
175
+ cfg.to_h { |tenant, config| [tenant, config.fetch('db_config')] }
176
+ end
177
+
178
+ def tenants_from_config(cfg)
179
+ available_tenants = cfg.keys
180
+ raise TenantNotFound, default unless available_tenants.include?(default)
181
+
182
+ available_tenants
183
+ end
184
+
185
+ def secrets_from_config(cfg)
186
+ cfg.to_h { |tenant, config| [tenant, config.fetch('secrets', {})] }
187
+ end
188
+
189
+ def uris_from_config(secrets_by_tenant)
190
+ secrets_by_tenant.to_h { |tenant, secrets| [tenant, processed_uris(secrets)] }
191
+ end
192
+
193
+ def hosts_from_config(secrets_by_tenant)
194
+ secrets_by_tenant.to_h do |tenant, secrets|
195
+ hosts = processed_uris(secrets).map { |uri| URI(uri).host }
196
+ [tenant, hosts]
197
+ end
198
+ end
199
+
200
+ #
201
+ # Make default database first in the list
202
+ #
203
+ # @param [Hash] cfg config of enviroment
204
+ #
205
+ # @return [Hash]
206
+ #
207
+ def configs_sort(cfg)
208
+ sorted_config = { 'default' => cfg['default'] }
209
+ sorted_config.merge!(cfg)
210
+ end
211
+
212
+ # consul KV can store only array into json string, so need to parse
213
+ def processed_uris(secrets)
214
+ secrets_uris = secrets.fetch('uris', [])
215
+ is_array = secrets_uris.is_a?(Array)
216
+ return secrets_uris if is_array && secrets_uris.empty?
217
+
218
+ if is_array
219
+ validate_uris(secrets_uris)
220
+ return secrets_uris
221
+ end
222
+
223
+ secrets_uris = JSON.parse(secrets_uris)
224
+ validate_uris(secrets_uris)
225
+ secrets_uris
226
+ rescue JSON::ParserError => e
227
+ raise ConfigError, 'URI parse error. Must be an array of URI'
228
+ end
229
+
230
+ def valid_url?(uri)
231
+ uri = URI.parse(uri)
232
+ !!uri.host && !!uri.scheme
233
+ rescue URI::InvalidURIError
234
+ false
235
+ end
236
+
237
+ def validate_uris(secrets_uris)
238
+ secrets_uris.each { |uri| raise ConfigError, 'URI parse error. Must be an array of URI' unless valid_url?(uri) }
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,17 @@
1
+ module Gorynich
2
+ class Configuration
3
+ attr_accessor :cache,
4
+ :fetcher,
5
+ :namespace,
6
+ :cache_expiration,
7
+ :rack_env_handler
8
+
9
+ def initialize
10
+ @cache = ActiveSupport::Cache::NullStore.new
11
+ @fetcher = nil
12
+ @namespace = nil
13
+ @cache_expiration = 30
14
+ @rack_env_handler = nil
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Gorynich
2
+ class Current < ::ActiveSupport::CurrentAttributes
3
+ attribute :tenant
4
+ attribute :uri
5
+ attribute :host
6
+ attribute :secrets
7
+ attribute :database
8
+
9
+ def secrets
10
+ super || {}
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module Gorynich
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Gorynich
4
+
5
+ initializer 'gorynich.add_middleware' do |app|
6
+ app.middleware.insert_after ActionDispatch::RemoteIp, Gorynich::Head::RackMiddleware
7
+ app.config.active_record.writing_role = :default
8
+ app.config.hosts.clear
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,54 @@
1
+ require_relative 'fetchers/file'
2
+ require_relative 'fetchers/consul'
3
+
4
+ module Gorynich
5
+ class Fetcher
6
+ attr_reader :fetcher, :namespace, :cache_expiration
7
+
8
+ #
9
+ # Create instance of fetcher
10
+ #
11
+ # @param [Object] fetcher data source
12
+ # @param [String, Symbol] namespace cache namespace
13
+ # @param [Integer] cache_expiration how long your cache will be alive
14
+ #
15
+ def initialize(fetcher: nil, namespace: nil, cache_expiration: nil)
16
+ @fetcher = fetcher || Gorynich.configuration.fetcher
17
+ @namespace = namespace || Gorynich.configuration.namespace
18
+ @cache_expiration = cache_expiration || Gorynich.configuration.cache_expiration
19
+ end
20
+
21
+ #
22
+ # Load data from source
23
+ #
24
+ # @return [Hash]
25
+ #
26
+ def fetch
27
+ cfg = Gorynich.configuration.cache.fetch(
28
+ %i[gorynich fetcher fetch], expires_in: @cache_expiration.seconds, namespace: @namespace
29
+ ) do
30
+ if @fetcher.nil?
31
+ {}
32
+ elsif @fetcher.is_a?(Array)
33
+ result = {}
34
+ @fetcher.each do |f|
35
+ result =
36
+ begin
37
+ f.fetch
38
+ rescue ::StandardError
39
+ {}
40
+ end
41
+ break unless result.empty?
42
+ end
43
+ result
44
+ else
45
+ @fetcher.fetch
46
+ end
47
+ end
48
+
49
+ raise Error, 'Config is empty' if cfg.empty?
50
+
51
+ cfg.deep_transform_keys(&:downcase)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,19 @@
1
+ require 'diplomat'
2
+
3
+ module Gorynich
4
+ module Fetchers
5
+ class Consul
6
+ attr_reader :storage, :consul_opts
7
+
8
+ def initialize(storage:, **opts)
9
+ @storage = storage
10
+ @consul_opts = opts
11
+ end
12
+
13
+ def fetch
14
+ config = ::Diplomat::Kv.get_all(storage, convert_to_hash: true, **consul_opts)
15
+ config.dig(*storage.split('/')) || {}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+
4
+ module Gorynich
5
+ module Fetchers
6
+ class File
7
+ attr_reader :file_path
8
+
9
+ def initialize(file_path:)
10
+ @file_path = file_path
11
+ end
12
+
13
+ def fetch
14
+ data = ::ERB.new(::File.read(file_path)).result
15
+
16
+ ::YAML.load(data, aliases: true) || {}
17
+ rescue ArgumentError
18
+ ::YAML.load(data) || {}
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,44 @@
1
+ module Gorynich
2
+ module Head
3
+ module ActionCable
4
+ module Connection
5
+ extend ::ActiveSupport::Concern
6
+
7
+ included do
8
+ attr_reader :host, :tenant
9
+
10
+ def connect
11
+ @host = env['SERVER_NAME']
12
+ @tenant = ::Gorynich.instance.tenant_by_host(@host)
13
+ end
14
+ end
15
+ end
16
+
17
+ module Channel
18
+ def subscribe_to_channel(*args)
19
+ ::Gorynich.with(tenant, host: host) do
20
+ super
21
+ end
22
+ end
23
+
24
+ def unsubscribe_from_channel(*args)
25
+ ::Gorynich.with(tenant, host: host) do
26
+ super
27
+ end
28
+ end
29
+
30
+ def perform_action(*args)
31
+ ::Gorynich.with(tenant, host: host) do
32
+ super
33
+ end
34
+ end
35
+
36
+ def self.broadcasting_for(model)
37
+ raise 'unable to broadcast message without tenant' if ::Gorynich::Current.tenant.nil?
38
+
39
+ serialize_broadcasting([channel_name, ::Gorynich::Current.tenant, model])
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,36 @@
1
+ module Gorynich
2
+ module Head
3
+ module ActiveJob
4
+ extend ::ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_reader :current_uri, :current_tenant
8
+
9
+ def serialize
10
+ super.merge(uri: Gorynich::Current.uri, tenant: Gorynich::Current.tenant)
11
+ end
12
+
13
+ def deserialize(job_data)
14
+ super
15
+ @current_uri = job_data.fetch(:uri)
16
+ @current_tenant = job_data.fetch(:tenant)
17
+ end
18
+
19
+ around_perform do |job, block|
20
+ Gorynich.with(
21
+ job.current_tenant || Gorynich::Current.tenant,
22
+ uri: job.current_uri || Gorynich::Current.uri
23
+ ) do |_current|
24
+ block.call
25
+ end
26
+ end
27
+
28
+ around_enqueue do |_job, block|
29
+ Gorynich.with_database(Gorynich.instance.default) do
30
+ block.call
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ module Gorynich
2
+ module Head
3
+ module ActiveRecord
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ module ::GlobalID::Locator
8
+ class << self
9
+ def original_locate(gid, options = {})
10
+ if (gid = GlobalID.parse(gid)) && find_allowed?(gid.model_class, options[:only])
11
+ locator_for(gid).locate gid
12
+ end
13
+ end
14
+
15
+ def locate(gid, options = {})
16
+ gid = GlobalID.parse(gid)
17
+ return original_locate(gid, options) unless gid
18
+
19
+ tenant = gid.params['tenant']
20
+ if tenant && tenant != Gorynich::Current.tenant
21
+ Gorynich.with(tenant) do
22
+ original_locate(gid, options)
23
+ end
24
+ else
25
+ original_locate(gid, options)
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ connects_to database: Gorynich.instance.connects_to_config
32
+
33
+ def cache_key(*args)
34
+ "#{Gorynich::Current.tenant}:#{super}"
35
+ end
36
+
37
+ def to_global_id(options = {})
38
+ options[:tenant] ||= Gorynich::Current.tenant
39
+ super
40
+ end
41
+
42
+ alias_method :to_gid, :to_global_id
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ # Do not require this until want to use DelayedJob
2
+
3
+ module Gorynich
4
+ module Head
5
+ class DelayedJob < ::Delayed::Plugin
6
+
7
+ callbacks do |lifecycle|
8
+ lifecycle.around(:execute) do |*_args, &block|
9
+ Gorynich.with(Gorynich.instance.default) do |current|
10
+ if ::Delayed::Worker.logger.respond_to?(:tagged)
11
+ ::Delayed::Worker.logger.tagged(tenant: current.tenant) { block.call }
12
+ else
13
+ block.call
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,34 @@
1
+ # connects_to database:
2
+ module Gorynich
3
+ module Head
4
+ class RackMiddleware
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ @config ||= Gorynich.instance
11
+ @config.actualize
12
+
13
+ tenant, opts = Gorynich.switcher.analyze(env)
14
+
15
+ Gorynich.with(tenant, **opts) do
16
+ if Rails.logger.respond_to?(:tagged)
17
+ Rails.logger.tagged("Tenant(#{tenant})") { @app.call(env) }
18
+ else
19
+ @app.call(env)
20
+ end
21
+ end
22
+ rescue Gorynich::UriNotFound => e
23
+ Rails.logger.error(e.inspect)
24
+ [404, { 'Content-Type' => 'text/plain', 'charset' => 'UTF-8' }, [e.message]]
25
+ rescue Gorynich::HostNotFound => e
26
+ Rails.logger.error(e.inspect)
27
+ [404, { 'Content-Type' => 'text/plain', 'charset' => 'UTF-8' }, [e.message]]
28
+ rescue Gorynich::TenantNotFound => e
29
+ Rails.logger.error(e.inspect)
30
+ [404, { 'Content-Type' => 'text/plain', 'charset' => 'UTF-8' }, [e.message]]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ require_relative 'head/rack_middleware'
2
+ require_relative 'head/active_record'
3
+ require_relative 'head/action_cable'
4
+ require_relative 'head/active_job'
5
+
6
+ module Gorynich
7
+ module Head
8
+ end
9
+ end
@@ -0,0 +1,48 @@
1
+ module Gorynich
2
+ class Switcher
3
+ def initialize(config:)
4
+ @config = config
5
+ end
6
+
7
+ #
8
+ # Hander for rack middleware's variables
9
+ #
10
+ # @param [Hash] env middleware's variables
11
+ #
12
+ # @return [[String, Hash]] tenant, options
13
+ #
14
+ def analyze(env)
15
+ return Gorynich.configuration.rack_env_handler.call(env) unless Gorynich.configuration.rack_env_handler.nil?
16
+
17
+ host = env['SERVER_NAME']
18
+ tenant = Gorynich.instance.tenant_by_host(host)
19
+ uri = Gorynich.instance.uri_by_host(host, tenant)
20
+ [tenant, { host: host, uri: uri }]
21
+ end
22
+
23
+ def with_database(tenant)
24
+ ::ActiveRecord::Base.connected_to role: tenant.to_sym do
25
+ yield(tenant)
26
+ end
27
+ end
28
+
29
+ def with_current(tenant, **opts, &block)
30
+ Gorynich::Current.set(@config.config(tenant.to_s).merge(opts)) do
31
+ block.call(Gorynich::Current.instance) if block.present?
32
+ end
33
+ end
34
+
35
+ def with(tenant, **opts, &block)
36
+ with_database(tenant) do
37
+ with_current(tenant, **opts, &block)
38
+ end
39
+ end
40
+
41
+ def with_each_tenant(except: [], &block)
42
+ except = except.map(&:to_s)
43
+ @config.tenants.reject { |v| except.include?(v) }.each do |tenant|
44
+ with(tenant, &block)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module Gorynich
2
+ VERSION = '1.1.0'
3
+ end