ekylibre-multi_tenancy 0.2.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e5b1cf84d71e00f4d04863e55a50295ea7d4858ce81eff7e032920c27e06fa18
4
+ data.tar.gz: c51f6ae7c6d84d0f3f48e0051cb45f35b828174e86e277daea45f230b9db9623
5
+ SHA512:
6
+ metadata.gz: b6ca1863661aaa5236367e5eee3c5d9cff6cf73a95d37adb88604842e51858f403b5ce762db4c1e552d69ab107cc1afa05177f187a15fbb4cfc81605036202c2
7
+ data.tar.gz: c6671183e662add0049036eb4a448e794960f71244e7a0998f2174a56efa150d2cc69cb08c78821dcf7b08119186627e84cf36b5ae387b435fbbe4c9e6b33149
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/ekylibre/multi_tenancy/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'ekylibre-multi_tenancy'
7
+ spec.version = Ekylibre::MultiTenancy::VERSION
8
+ spec.authors = ['Ekylibre developers']
9
+ spec.email = ['dev@ekylibre.com']
10
+
11
+ spec.summary = 'Apartment integration as a plugin for Ekylibre'
12
+ spec.required_ruby_version = '>= 2.6.0'
13
+ spec.homepage = 'https://www.ekylibre.com'
14
+ spec.license = 'AGPL-3.0-only'
15
+
16
+ spec.files = Dir.glob(%w[lib/**/* *.gemspec])
17
+
18
+ spec.require_paths = ['lib']
19
+
20
+ # Needed to fix a bundler problem where is tries to install version 3.2.14 even though the plugin system requires railties > 4
21
+ spec.add_dependency 'activerecord', '> 4'
22
+ spec.add_dependency 'apartment', '~> 2.2.0'
23
+ spec.add_dependency 'ekylibre-plugin_system', '~> 0.5.1'
24
+ spec.add_dependency 'public_suffix', '~> 4.0.6'
25
+ spec.add_dependency 'zeitwerk', '~> 2.4.0'
26
+
27
+ spec.add_development_dependency 'bundler', '>= 1.17'
28
+ spec.add_development_dependency 'minitest', '~> 5.14'
29
+ spec.add_development_dependency 'minitest-reporters', '~> 1.4'
30
+ spec.add_development_dependency 'rake', '~> 13.0'
31
+ spec.add_development_dependency 'rubocop', '~> 1.3.0'
32
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apartment'
4
+ require 'ekylibre-plugin_system'
5
+ require 'zeitwerk'
6
+
7
+ module Ekylibre # :nodoc:
8
+ end
9
+
10
+ loader = Zeitwerk::Loader.for_gem
11
+ loader.ignore(__FILE__)
12
+ loader.ignore("#{__dir__}/multi_tenancy/rails/**/*.rb")
13
+ loader.ignore("#{__dir__}/multi_tenancy/testing/**/*.rb")
14
+ loader.setup
15
+
16
+ require_relative 'ekylibre/multi_tenancy/rails/railtie' if defined?(::Rails)
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ekylibre
4
+ module MultiTenancy
5
+ # Helper methods to switch tenants in the Rails console
6
+ module ConsoleHelper
7
+ # @param [String] name
8
+ def switch_tenant!(name)
9
+ container.get(TenantSwitcher)
10
+ .switch!(name)
11
+ end
12
+
13
+ def leave_tenant!
14
+ container.get(TenantSwitcher)
15
+ .leave!
16
+ end
17
+
18
+ # @param [String] name
19
+ def switch_tenant(name, &block)
20
+ return if block.nil?
21
+
22
+ container.get(TenantSwitcher)
23
+ .switch(name) { block.call }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/request'
4
+
5
+ # Heavily inspired from Apartment::Elevators::Generic but with the ability to
6
+ module Ekylibre
7
+ module MultiTenancy
8
+ module Middleware
9
+ # Provides a rack based tenant switching solution based on request
10
+ #
11
+ class ContainerAwareRackMiddleware
12
+ class << self
13
+ attr_writer :excluded_subdomains
14
+
15
+ def excluded_subdomains
16
+ @excluded_subdomains ||= []
17
+ end
18
+ end
19
+
20
+ def initialize(app)
21
+ @app = app
22
+ end
23
+
24
+ def call(env)
25
+ request = Rack::Request.new(env)
26
+ container = env.fetch('container').dup
27
+
28
+ PluginSystem::GlobalContainer.replace_with(container) do
29
+ env['container'] = container
30
+
31
+ database = extract_tenant(request)
32
+
33
+ begin
34
+ if database
35
+ container
36
+ .get(TenantSwitcher)
37
+ .switch(database) { @app.call(env) }
38
+ else
39
+ @app.call(env)
40
+ end
41
+ rescue ::Apartment::TenantNotFound
42
+ Rails.logger.error "Apartment Tenant not found: #{subdomain(request.host)}"
43
+ return [404, { 'Content-Type' => 'text/html' }, [File.read(Rails.root.join('public', '404.html'))]]
44
+ end
45
+ end
46
+ end
47
+
48
+ def extract_tenant(request)
49
+ if (tenant = ENV['tenant'])
50
+ tenant
51
+ elsif ::Rails.env.test?
52
+ 'test'
53
+ elsif ENV['ELEVATOR'] == 'header'
54
+ request.env['HTTP_X_TENANT']
55
+ else
56
+ extract_subdomain(request)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def extract_subdomain(request)
63
+ request_subdomain = subdomain(request.host)
64
+
65
+ # If the domain acquired is set to be excluded, set the tenant to whatever is currently
66
+ # next in line in the schema search path.
67
+ tenant = if self.class.excluded_subdomains.include?(request_subdomain)
68
+ nil
69
+ else
70
+ request_subdomain
71
+ end
72
+
73
+ tenant.presence
74
+ end
75
+
76
+ # *Almost* a direct ripoff of ActionDispatch::Request subdomain methods
77
+
78
+ # Only care about the first subdomain for the database name
79
+ def subdomain(host)
80
+ subdomains(host).first
81
+ end
82
+
83
+ def subdomains(host)
84
+ host_valid?(host) ? parse_host(host) : []
85
+ end
86
+
87
+ def host_valid?(host)
88
+ !ip_host?(host) && domain_valid?(host)
89
+ end
90
+
91
+ def ip_host?(host)
92
+ !/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/.match(host).nil?
93
+ end
94
+
95
+ def domain_valid?(host)
96
+ PublicSuffix.valid?(host, ignore_private: true)
97
+ end
98
+
99
+ def parse_host(host)
100
+ (PublicSuffix.parse(host, ignore_private: true).trd || '').split('.')
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Heavily inspired from apartment-sidekiq
4
+ module Ekylibre
5
+ module MultiTenancy
6
+ module Middleware
7
+ # Module containing Client ans Server Sidekiq middlewares for the MultiTenancy plugin
8
+ module SidekiqMiddleware
9
+ class << self
10
+ def setup
11
+ ::Sidekiq.configure_client do |config|
12
+ configure_client(config)
13
+ end
14
+
15
+ ::Sidekiq.configure_server do |config|
16
+ configure_client(config)
17
+
18
+ configure_server(config)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def configure_client(config)
25
+ config.client_middleware do |chain|
26
+ chain.insert_after(
27
+ Ekylibre::PluginSystem::Middleware::SidekiqMiddleware::ClientMiddleware,
28
+ ContainerAwareClientMiddleware
29
+ )
30
+ end
31
+ end
32
+
33
+ def configure_server(config)
34
+ config.server_middleware do |chain|
35
+ chain.insert_after(
36
+ Ekylibre::PluginSystem::Middleware::SidekiqMiddleware::ServerMiddleware,
37
+ ContainerAwareServerMiddleware
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Heavily inspired from apartment-sidekiq
4
+ module Ekylibre
5
+ module MultiTenancy
6
+ module Middleware
7
+ module SidekiqMiddleware
8
+ # Client Sidekiq middleware to add the name of the current tenant to the job context
9
+ class ContainerAwareClientMiddleware
10
+ def call(worker_class, item, queue, redis_pool = nil)
11
+ item['tenant'] ||= item.fetch('container').get(TenantStack).current&.name
12
+
13
+ yield
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Heavily inspired from apartment-sidekiq
4
+ module Ekylibre
5
+ module MultiTenancy
6
+ module Middleware
7
+ module SidekiqMiddleware
8
+ # Server Sidekiq middleware switching to the correct tenant (if any) based on the job context
9
+ class ContainerAwareServerMiddleware
10
+ def call(worker_class, item, queue)
11
+ tenant = item.fetch('tenant', nil)
12
+ container = item.fetch('container').dup
13
+
14
+ PluginSystem::GlobalContainer.replace_with(container) do
15
+ item['container'] = container
16
+
17
+ if tenant.nil?
18
+ yield
19
+ else
20
+ container
21
+ .get(TenantSwitcher)
22
+ .switch(tenant) { yield }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ using Corindon::DependencyInjection::Injectable
4
+
5
+ module Ekylibre
6
+ module MultiTenancy
7
+ # Plugin adding multi-tenancy to Ekylibre
8
+ class MultiTenancyPlugin < Ekylibre::PluginSystem::Plugin
9
+ injectable do
10
+ args(
11
+ app: Ekylibre::PluginSystem::Parameters::RAILS_APPLICATION,
12
+ engine: param(Ekylibre::MultiTenancy::Rails::Railtie),
13
+ )
14
+ tag 'ekylibre.system.plugin'
15
+ end
16
+
17
+ APARTMENT = make_parameter('apartment')
18
+ PRIVATE_ROOT = make_parameter('private_root')
19
+ TENANTS_FILE = make_parameter('tenants_file')
20
+
21
+ def initialize(app:, engine:)
22
+ super(engine: engine)
23
+
24
+ @app = app
25
+ end
26
+
27
+ # @param [Corindon::DependencyInjection::Container] container
28
+ def boot(container)
29
+ container.set_parameter(APARTMENT.key, ::Apartment::Tenant)
30
+ container.set_parameter(PRIVATE_ROOT.key, app.root.join('private'))
31
+ container.set_parameter(TENANTS_FILE.key, app.root.join('config', 'tenants.yml'))
32
+
33
+ container.add_definition(TenantRepository)
34
+ container.add_definition(TenantStack)
35
+ container.add_definition(TenantSwitcher)
36
+ end
37
+
38
+ def version
39
+ MultiTenancy::VERSION
40
+ end
41
+
42
+ private
43
+
44
+ # @return [::Rails::Application]
45
+ attr_reader :app
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ekylibre
4
+ module MultiTenancy
5
+ module Rails
6
+ # Railtie to integrate the MultiTenancy Plugin into a Rails application
7
+ class Railtie < ::Rails::Railtie
8
+ extend PluginSystem::PluginRegistration
9
+
10
+ register_plugin(MultiTenancyPlugin)
11
+
12
+ initializer(:register_middleware, after: :engines_blank_point) do |app|
13
+ app.config.middleware.insert_after(
14
+ Ekylibre::PluginSystem::Middleware::RackMiddleware,
15
+ Ekylibre::MultiTenancy::Middleware::ContainerAwareRackMiddleware
16
+ )
17
+
18
+ Middleware::SidekiqMiddleware.setup if defined?(::Sidekiq)
19
+ end
20
+
21
+ # rubocop:disable Lint/Debugger
22
+ console do
23
+ ::Rails::ConsoleMethods.send :include, ConsoleHelper
24
+ end
25
+ # rubocop:enable Lint/Debugger
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ using Corindon::DependencyInjection::Injectable
4
+
5
+ module Ekylibre
6
+ module MultiTenancy
7
+ # Represents a tenant in the MultiTenancy plugin
8
+ class Tenant
9
+ # @return [String]
10
+ attr_reader :name
11
+ # @return [Pathname]
12
+ attr_reader :private_directory
13
+
14
+ # @param [String] name
15
+ # @param [Pathname] private_directory
16
+ def initialize(name:, private_directory:)
17
+ @name = name
18
+ @private_directory = private_directory
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ using Corindon::DependencyInjection::Injectable
4
+
5
+ module Ekylibre
6
+ module MultiTenancy
7
+ class TenantRepository
8
+ injectable(
9
+ private_root: MultiTenancyPlugin::PRIVATE_ROOT,
10
+ tenants_file: MultiTenancyPlugin::TENANTS_FILE,
11
+ )
12
+
13
+ # @param [Pathname] private_root
14
+ def initialize(private_root:, tenants_file:)
15
+ @private_root = private_root
16
+ @tenants_file = tenants_file
17
+ @write_mutex = Mutex.new
18
+ end
19
+
20
+ # @param [String] name
21
+ # @return [Tenant, nil]
22
+ def get(name)
23
+ if has?(name)
24
+ Tenant.new(name: name, private_directory: private_root.join(name))
25
+ else
26
+ nil
27
+ end
28
+ end
29
+
30
+ def delete(name)
31
+ tenants.delete(name)
32
+
33
+ write
34
+ end
35
+
36
+ # @param [String] name
37
+ # @return [Boolean]
38
+ def has?(name)
39
+ list.include?(name)
40
+ end
41
+
42
+ # @return [Array<String>]
43
+ def list
44
+ tenants.fetch(::Rails.env.to_s, [])
45
+ end
46
+
47
+ private
48
+
49
+ # @return [Hash{String => Array<String>}]
50
+ def load
51
+ if tenants_file.exist?
52
+ YAML.load_file(tenants_file) || {}
53
+ else
54
+ {}
55
+ end
56
+ end
57
+
58
+ def write
59
+ write_mutex.synchronize do
60
+ tenants_file.write(tenants.to_yaml)
61
+ end
62
+
63
+ nil
64
+ end
65
+
66
+ # @return [Hash{String => Array<String>}]
67
+ def tenants
68
+ @tenants ||= load
69
+ end
70
+
71
+ # @return [Pathname]
72
+ attr_reader :private_root
73
+ # @return [Pathname]
74
+ attr_reader :tenants_file
75
+ # @return [Mutex]
76
+ attr_reader :write_mutex
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ekylibre
4
+ module MultiTenancy
5
+ # Stack allowing to track which is the current tenant across multiple nested contexts
6
+ class TenantStack
7
+ def initialize
8
+ @stack = []
9
+ end
10
+
11
+ # @return [Tenant, nil]
12
+ def current
13
+ @stack.last
14
+ end
15
+
16
+ def pop
17
+ @stack.pop
18
+
19
+ nil
20
+ end
21
+
22
+ # @param [Tenant] tenant
23
+ def push(tenant)
24
+ @stack << tenant
25
+ end
26
+
27
+ def size
28
+ @stack.size
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ using Corindon::DependencyInjection::Injectable
4
+
5
+ module Ekylibre
6
+ module MultiTenancy
7
+ # Service allowing to switch between tenants and keeping track of what need to be cleaned through the use of a TenantStack
8
+ class TenantSwitcher
9
+ injectable(
10
+ apartment: MultiTenancyPlugin::APARTMENT,
11
+ tenant_stack: TenantStack,
12
+ tenant_repository: TenantRepository,
13
+ )
14
+
15
+ # @param [#switch, #switch!] apartment
16
+ # @param [TenantStack] tenant_stack
17
+ # @param [TenantRepository] tenant_repository
18
+ def initialize(apartment:, tenant_stack:, tenant_repository:)
19
+ @apartment = apartment
20
+ @tenant_stack = tenant_stack
21
+ @tenant_repository = tenant_repository
22
+ @bang_stack = []
23
+ @bang_switched = false
24
+ end
25
+
26
+ # @param [String, #to_s] name
27
+ def switch(name, &block)
28
+ name = name.to_s
29
+
30
+ tenant = tenant_repository.get(name)
31
+ return if tenant.nil?
32
+
33
+ begin
34
+ @bang_stack << @bang_switched
35
+ @bang_switched = false
36
+ tenant_stack.push(tenant)
37
+
38
+ apartment.switch(name, &block)
39
+ ensure
40
+ # Make sure we pop the stack twice if we bang_switched
41
+ leave! if @bang_switched
42
+ @bang_switched = @bang_stack.pop
43
+ tenant_stack.pop
44
+ end
45
+ end
46
+
47
+ # @param [String, #to_s] name
48
+ def switch!(name)
49
+ name = name.to_s
50
+ tenant = tenant_repository.get(name)
51
+ return if tenant.nil?
52
+
53
+ # Some housekeeping to keep the tenant_stack up-to-date
54
+ tenant_stack.pop if @bang_switched
55
+ @bang_switched = true
56
+ tenant_stack.push(tenant)
57
+
58
+ apartment.switch!(name)
59
+ end
60
+
61
+ def leave!
62
+ if @bang_switched
63
+ @bang_switched = false
64
+ tenant_stack.pop
65
+ apartment.switch!(tenant_stack.current)
66
+ else
67
+ raise StandardError.new('Cannot leave! if not after switch!')
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ # @return [#switch, #switch!]
74
+ attr_reader :apartment
75
+ # @return [TenantRepository]
76
+ attr_reader :tenant_repository
77
+ # @return [TenantStack]
78
+ attr_reader :tenant_stack
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ekylibre
4
+ module MultiTenancy
5
+ module Testing
6
+ class MockApartment
7
+ def switch(*, **)
8
+ yield if block_given?
9
+ end
10
+
11
+ def switch!(*, **) end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ekylibre
4
+ module MultiTenancy
5
+ VERSION = '0.2.0'
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,198 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ekylibre-multi_tenancy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Ekylibre developers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-02-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: '4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">"
25
+ - !ruby/object:Gem::Version
26
+ version: '4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: apartment
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: ekylibre-plugin_system
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.5.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: public_suffix
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 4.0.6
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 4.0.6
69
+ - !ruby/object:Gem::Dependency
70
+ name: zeitwerk
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 2.4.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 2.4.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '1.17'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '1.17'
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '5.14'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '5.14'
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest-reporters
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.4'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.4'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '13.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '13.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 1.3.0
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 1.3.0
153
+ description:
154
+ email:
155
+ - dev@ekylibre.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - ekylibre-multi_tenancy.gemspec
161
+ - lib/ekylibre-multi_tenancy.rb
162
+ - lib/ekylibre/multi_tenancy/console_helper.rb
163
+ - lib/ekylibre/multi_tenancy/middleware/container_aware_rack_middleware.rb
164
+ - lib/ekylibre/multi_tenancy/middleware/sidekiq_middleware.rb
165
+ - lib/ekylibre/multi_tenancy/middleware/sidekiq_middleware/container_aware_client_middleware.rb
166
+ - lib/ekylibre/multi_tenancy/middleware/sidekiq_middleware/container_aware_server_middleware.rb
167
+ - lib/ekylibre/multi_tenancy/multi_tenancy_plugin.rb
168
+ - lib/ekylibre/multi_tenancy/rails/railtie.rb
169
+ - lib/ekylibre/multi_tenancy/tenant.rb
170
+ - lib/ekylibre/multi_tenancy/tenant_repository.rb
171
+ - lib/ekylibre/multi_tenancy/tenant_stack.rb
172
+ - lib/ekylibre/multi_tenancy/tenant_switcher.rb
173
+ - lib/ekylibre/multi_tenancy/testing/mock_apartment.rb
174
+ - lib/ekylibre/multi_tenancy/version.rb
175
+ homepage: https://www.ekylibre.com
176
+ licenses:
177
+ - AGPL-3.0-only
178
+ metadata: {}
179
+ post_install_message:
180
+ rdoc_options: []
181
+ require_paths:
182
+ - lib
183
+ required_ruby_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: 2.6.0
188
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ requirements: []
194
+ rubygems_version: 3.0.3
195
+ signing_key:
196
+ specification_version: 4
197
+ summary: Apartment integration as a plugin for Ekylibre
198
+ test_files: []