gorynich 1.1.0.142046

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