tenant_realm 1.0.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: ec5080c5a6b4e997fd094811df564e8cb6c7520030a1fb0617917a155dd05223
4
+ data.tar.gz: 159100e048f540997d9e9ffa33ba215c035357573bb297a65afa52bd6547d593
5
+ SHA512:
6
+ metadata.gz: 789611a5e9bc41b794a65e39d4d4cb48a69ad6d37a6d1bde72cb10e6f2bf499278f6715c5f50a7ba26023360408919bf921e33e72b9b994263747582eb0e8dc8
7
+ data.tar.gz: a7dc2cddb27f123a9772bd53d4e4b1f3d39f9f5cad245f83de9329aa9c3bd6acebde917cb42ce84927b12b896396f8837fe715f5f22ede2cbcec365cb2d72bfc
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.2
data/README.md ADDED
@@ -0,0 +1,121 @@
1
+ Tenant Realm is a lightweight gem provides some helpers to support working on multi-tenant easily using [Multiple Database with Active Record](https://guides.rubyonrails.org/active_record_multiple_databases.html).
2
+
3
+ # Installation
4
+
5
+ ```sh
6
+ gem 'tenant_realm'
7
+ ```
8
+
9
+ # CLI
10
+
11
+ - init `tenant_realm`
12
+
13
+ ```sh
14
+ rails g tenant_realm
15
+ ```
16
+
17
+ - run migration for all tenants
18
+
19
+ ```sh
20
+ rake tenant_realm:migrate
21
+ ```
22
+
23
+ # Configuration
24
+
25
+ ```rb
26
+ # frozen_string_literal: true
27
+
28
+ Rails.application.config.to_prepare do
29
+ TenantRealm.configure do |config|
30
+ # required
31
+ # identifier is extracted from `identifier_resolver`
32
+ config.fetch_tenant = lambda { |identifier|
33
+ # get tenant using identifier
34
+ }
35
+
36
+ # required
37
+ config.fetch_tenants = lambda {
38
+ # get tenant list
39
+ }
40
+
41
+ # optional
42
+ # default is getting from key :db_config from tenant
43
+ config.dig_db_config = lambda { |tenant|
44
+ tenant[:db_config]
45
+ }
46
+
47
+ # optional
48
+ # set this when you wanna skip the switch database
49
+ config.skip_resolver = lambda { |request|
50
+ request.env['REQUEST_PATH'].match?(/health|favicon.ico/)
51
+ }
52
+
53
+ # required
54
+ # method to extract the identifier of tenant
55
+ config.identifier_resolver = lambda { |request|
56
+ # get identifier from request
57
+ # tenant is retrieved using its domain
58
+ domain = request.referer || request.origin
59
+ domain ? URI(domain).host : nil
60
+ }
61
+
62
+ # optional
63
+ # extract the sharding name for multi_db
64
+ # default will be this proc return value -> tenant's shard_name -> slug -> id
65
+ config.shard_name_from_tenant = lambda { |tenant|
66
+ tenant[:slug]
67
+ }
68
+
69
+ # optional
70
+ # default will be TenantRealm::CurrentTenant
71
+ config.current_tenant = MyCurrentTenant
72
+
73
+ # optional
74
+ config.cache do |cache_config|
75
+ # using cache service
76
+ cache_config.service = :redis
77
+
78
+ # when will the tenant list and tenant are expired
79
+ cache_config.expires_in = 6.days
80
+
81
+ # list keys to cache the tenant
82
+ cache_config.tenant_uniq_cols = %i[slug domain]
83
+
84
+ # in case the column is not simple like above
85
+ # this option will override the one above
86
+ cache_config.tenant_keys_resolver = lambda { |tenant|
87
+ [
88
+ tenant[:slug],
89
+ tenant[:settings][:domain]
90
+ ]
91
+ }
92
+ end
93
+ end
94
+ end
95
+ ```
96
+
97
+ # Customize
98
+
99
+ - Custom current tenant
100
+
101
+ ```rb
102
+ # frozen_string_literal: true
103
+
104
+ class CurrentTenant < TenantRealm::CurrentTenant
105
+ attribute :additional_info
106
+
107
+ def tenant=(tenant)
108
+ super
109
+
110
+ self.additional_info = {
111
+ domain: tenant[:settings][:domain]
112
+ }
113
+ end
114
+ end
115
+ ```
116
+
117
+ # TODO
118
+
119
+ - [ ] support Row-Level Security (RLS)
120
+
121
+ - [ ] support multi-schema
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rubocop/rake_task'
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.config.to_prepare do
4
+ TenantRealm.configure do |config|
5
+ config.fetch_tenant = lambda { |identifier|
6
+ # get tenant using identifier
7
+ }
8
+
9
+ config.fetch_tenants = lambda {
10
+ # get tenant list
11
+ }
12
+
13
+ config.dig_db_config = lambda { |tenant|
14
+ tenant[:db_config]
15
+ }
16
+
17
+ config.skip_resolver = lambda { |request|
18
+ request.env['REQUEST_PATH'].match?(/health|favicon.ico/)
19
+ }
20
+
21
+ config.identifier_resolver = lambda { |request|
22
+ # get identifier from request
23
+ # domain = request.referer || request.origin
24
+ # domain ? URI(domain).host : nil
25
+ }
26
+
27
+ config.shard_name_from_tenant = lambda { |tenant|
28
+ tenant[:slug]
29
+ }
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TenantRealmGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('templates', __dir__)
5
+
6
+ def generate_tenant_realm
7
+ template(
8
+ 'tenant_realm.tt',
9
+ 'config/initializers/tenant_realm.rb'
10
+ )
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :tenant_realm do
4
+ desc 'Migrate db for all tenants'
5
+ task migrate: :environment do
6
+ tenants = TenantRealm::Tenant.tenants
7
+
8
+ tenants.each do |tenant|
9
+ shard = TenantRealm::Utils.shard_name_from_tenant(tenant:)
10
+
11
+ puts "Migrating #{shard}"
12
+
13
+ db_config = TenantRealm::Utils.dig_db_config(tenant:)
14
+
15
+ if db_config.blank?
16
+ puts "Skip Migrating #{shard}"
17
+
18
+ next
19
+ end
20
+
21
+ db_config = TenantRealm::DbContext.root_db_config.merge(
22
+ database: db_config[:database]
23
+ )
24
+
25
+ ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection(db_config) do
26
+ ActiveRecord::Tasks::DatabaseTasks.migrate
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TenantRealm
4
+ module Cache
5
+ class BaseCache
6
+ class << self
7
+ def cache_tenants(_tenants)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def tenants
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def cache_tenant(_tenant)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def tenant(_identifier)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ private
24
+
25
+ def tenant_unique_keys(tenant)
26
+ config = Configuration::Cache
27
+
28
+ if config.tenant_keys_resolver.present?
29
+ Helpers.raise_if_not_proc(config.tenant_keys_resolver, 'cache_config.tenant_keys_resolver')
30
+ Helpers.wrap_array(config.tenant_keys_resolver.call(tenant))
31
+ elsif config.tenant_uniq_cols.present?
32
+ cols = Helpers.wrap_array(config.tenant_uniq_cols)
33
+
34
+ cols.map do |col|
35
+ tenant[col]
36
+ end
37
+ else
38
+ [
39
+ tenant[:slug]
40
+ ]
41
+ end
42
+ end
43
+
44
+ def tenants_key
45
+ 'tenant_realm:tenants'
46
+ end
47
+
48
+ def tenant_key
49
+ 'tenant_realm:tenant'
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redis'
4
+ require 'kredis'
5
+
6
+ require_relative 'base_cache'
7
+
8
+ module TenantRealm
9
+ module Cache
10
+ class KredisCache < BaseCache
11
+ class << self
12
+ def cache_tenants(tenants)
13
+ return if tenants.blank?
14
+
15
+ cached_tenants = tenants_kredis
16
+ cached_tenants.value = tenants
17
+ tenants
18
+ end
19
+
20
+ def tenants
21
+ cached_tenants = tenants_kredis
22
+ cached_tenants.value&.map(&:deep_symbolize_keys) || []
23
+ end
24
+
25
+ def cache_tenant(tenant)
26
+ tenant_unique_keys(tenant).each do |key|
27
+ cached_tenant = tenant_kredis(key)
28
+ cached_tenant.value = tenant
29
+ end
30
+
31
+ tenant
32
+ end
33
+
34
+ def tenant(identifier)
35
+ cached_tenant = tenant_kredis(identifier)
36
+ cached_tenant.value&.deep_symbolize_keys
37
+ end
38
+
39
+ private
40
+
41
+ def tenants_kredis
42
+ Kredis.json(tenants_key, expires_in: Configuration::Cache.expires_in)
43
+ end
44
+
45
+ def tenant_kredis(identifier)
46
+ Kredis.json("#{tenant_key}:#{identifier}", expires_in: Configuration::Cache.expires_in)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TenantRealm
4
+ class << self
5
+ def configure
6
+ yield Config
7
+ end
8
+
9
+ def configuration
10
+ Config
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TenantRealm
4
+ class Config
5
+ class << self
6
+ attr_accessor(
7
+ :fetch_tenant,
8
+ :fetch_tenants,
9
+ :dig_db_config,
10
+ :skip_resolver,
11
+ :current_tenant,
12
+ :identifier_resolver,
13
+ :shard_name_from_tenant
14
+ )
15
+
16
+ def cache
17
+ yield Configuration::Cache
18
+ end
19
+
20
+ def current
21
+ @current ||= current_tenant || CurrentTenant
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TenantRealm
4
+ module Configuration
5
+ class Cache
6
+ class << self
7
+ attr_accessor :service,
8
+ :expires_in,
9
+ :tenant_uniq_cols,
10
+ :tenant_keys_resolver
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'utils'
4
+ require_relative 'tenant'
5
+
6
+ module TenantRealm
7
+ class DbContext
8
+ # to cache all tenant's connections
9
+ @@shards = {}
10
+
11
+ # to cache all tenant's connections already connected
12
+ @@connected_shards = nil
13
+
14
+ class << self
15
+ def root_db_config
16
+ @@db_config ||= ActiveRecord::Base.connection_db_config.configuration_hash.deep_dup.freeze
17
+ end
18
+
19
+ def init_shards
20
+ config = Utils.load_database_yml
21
+ tenants = Tenant.tenants
22
+
23
+ @@shards = config[Rails.env].keys.each_with_object({}) do |shard, shards|
24
+ shards[shard] = shard
25
+ end
26
+
27
+ tenants.each do |tenant|
28
+ db_config = Utils.dig_db_config(tenant:)
29
+ shard = Utils.shard_name_from_tenant(tenant:)
30
+ next if db_config.blank?
31
+
32
+ add_shard(shard:, db_config:)
33
+ end
34
+
35
+ ActiveRecord::Base.connects_to(shards: connected_shards)
36
+ end
37
+
38
+ def connected_shards
39
+ @@connected_shards ||= build_connected_shards
40
+ end
41
+
42
+ def add_shard(shard:, db_config:)
43
+ return if shard_exist?(shard:) || db_config.blank?
44
+
45
+ shard_name = shard.underscore
46
+ config = Utils.load_database_yml
47
+ tenant_shard_config = root_db_config.merge(database: db_config[:database])
48
+ config[Rails.env][shard_name] = JSON.parse(tenant_shard_config.to_json)
49
+ @@shards[shard_name] = shard_name
50
+
51
+ # sometimes the dynamic tenant's db_config causes connection pool checkout
52
+ # write tenants' db_config to database.yml
53
+ # to fix this problem when restart server
54
+ File.open('config/database.yml', 'w') do |f|
55
+ YAML.dump(config, f)
56
+ end
57
+
58
+ ActiveRecord::Base.configurations.configurations << ActiveRecord::DatabaseConfigurations::HashConfig.new(
59
+ Rails.env,
60
+ shard_name,
61
+ tenant_shard_config
62
+ )
63
+
64
+ sym_shard = shard_name.to_sym
65
+
66
+ connected_shards[sym_shard] = {
67
+ writing: sym_shard,
68
+ reading: sym_shard
69
+ }
70
+ end
71
+
72
+ def shard_exist?(shard:)
73
+ @@shards.key?(shard.underscore)
74
+ end
75
+
76
+ def switch_database(shard:, db_config: nil, &block)
77
+ add_shard(shard:, db_config:)
78
+
79
+ ActiveRecord::Base.connected_to(shard: shard.underscore.to_sym, &block)
80
+ end
81
+
82
+ def flush_connection!
83
+ ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
84
+ ActiveRecord::Base.connection_handler.flush_idle_connections!(:all)
85
+ end
86
+
87
+ def run_migrate(db_config:)
88
+ ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection(
89
+ root_db_config.merge(database: db_config[:database])
90
+ ) do
91
+ ActiveRecord::Tasks::DatabaseTasks.migrate
92
+ end
93
+
94
+ false
95
+ rescue StandardError
96
+ flush_connection!
97
+
98
+ true
99
+ end
100
+
101
+ def create_db(shard:, affix: nil)
102
+ database = [shard, affix].compact.join('-').underscore
103
+ db_config = root_db_config.merge(database:).tap do |config|
104
+ config[:host] ||= 'localhost'
105
+ config[:port] ||= 3306
106
+ end
107
+
108
+ ActiveRecord::Base.connection.create_database(database)
109
+
110
+ db_config
111
+ end
112
+
113
+ private
114
+
115
+ def build_connected_shards
116
+ @@shards.keys.each_with_object({}) do |shard, shards|
117
+ sym_shard = shard.to_sym
118
+
119
+ shards[sym_shard] = {
120
+ writing: sym_shard,
121
+ reading: sym_shard
122
+ }
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TenantRealm
4
+ class Helpers
5
+ class << self
6
+ def wrap_array(data)
7
+ return [] if data.blank?
8
+
9
+ data.is_a?(Array) ? data : [data]
10
+ end
11
+
12
+ def dev_log(message)
13
+ p message if Rails.env.development?
14
+ end
15
+
16
+ def raise_if_not_proc(source, name)
17
+ raise Error, "#{name} must be a Proc" unless source.is_a?(Proc)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TenantRealm
4
+ class Railtie < Rails::Railtie
5
+ rake_tasks do
6
+ Dir[File.expand_path('../tasks/*.rake', __dir__)].each do |file|
7
+ load(file)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cache/kredis_cache'
4
+
5
+ module TenantRealm
6
+ class Tenant
7
+ @@cache = {
8
+ redis: Cache::KredisCache,
9
+ kredis: Cache::KredisCache
10
+ }
11
+
12
+ class << self
13
+ def tenants
14
+ if cache.present?
15
+ tenants = cache.tenants
16
+ return tenants if tenants.present?
17
+
18
+ tenants = Utils.fetch_tenants
19
+ cache.cache_tenants(tenants)
20
+ tenants
21
+ else
22
+ Utils.fetch_tenants
23
+ end
24
+ end
25
+
26
+ def tenant(identifier)
27
+ if cache.present?
28
+ tenant = cache.tenant(identifier)
29
+ return tenant if tenant.present?
30
+
31
+ tenant = Utils.fetch_tenant(identifier)
32
+ cache.cache_tenant(tenant)
33
+ tenant
34
+ else
35
+ Utils.fetch_tenant
36
+ end
37
+ end
38
+
39
+ def cache_tenants(tenants)
40
+ return Helpers.dev_log('Tenant Realm: Skip cache tenants because cache not configured') if cache.blank?
41
+
42
+ cache.cache_tenants(tenants)
43
+ end
44
+
45
+ def cache_tenant(tenant)
46
+ return Helpers.dev_log('Tenant Realm: Skip cache tenant because cache not configured') if cache.blank?
47
+
48
+ cache.cache_tenant(tenant)
49
+ end
50
+
51
+ private
52
+
53
+ def cache
54
+ @cache ||= @@cache[Configuration::Cache.service]
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TenantRealm
4
+ class Utils
5
+ class << self
6
+ def load_database_yml
7
+ YAML.load_file('config/database.yml', aliases: true)
8
+ end
9
+
10
+ def fetch_tenants
11
+ Helpers.raise_if_not_proc(Config.fetch_tenants, 'config.fetch_tenants')
12
+
13
+ (Config.fetch_tenants.call.presence || []).map(&:deep_symbolize_keys)
14
+ end
15
+
16
+ def fetch_tenant(identifier)
17
+ Helpers.raise_if_not_proc(Config.fetch_tenant, 'config.fetch_tenant')
18
+
19
+ Config.fetch_tenant.call(identifier)&.deep_symbolize_keys
20
+ end
21
+
22
+ def dig_db_config(tenant:)
23
+ if Config.dig_db_config.is_a?(Proc)
24
+ Config.dig_db_config.call(tenant)
25
+ else
26
+ key = Config.dig_db_config || :db_config
27
+
28
+ tenant[key.to_sym]
29
+ end
30
+ end
31
+
32
+ def shard_name_from_tenant(tenant:)
33
+ shard = if Config.shard_name_from_tenant.is_a?(Proc)
34
+ Config.shard_name_from_tenant.call(tenant)
35
+ else
36
+ key = Config.shard_name_from_tenant || :shard_name
37
+
38
+ tenant[key.to_sym] || tenant[:slug] || tenant[:id]
39
+ end
40
+
41
+ shard.underscore
42
+ end
43
+
44
+ def identifier_resolver(request)
45
+ raise Error, 'config.identifier_resolver must be provided' if Config.identifier_resolver.blank?
46
+
47
+ Helpers.raise_if_not_proc(Config.identifier_resolver, 'config.identifier_resolver')
48
+
49
+ Config.identifier_resolver.call(request)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TenantRealm
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'tenant_realm/config'
4
+ require_relative 'tenant_realm/version'
5
+ require_relative 'tenant_realm/helpers'
6
+ require_relative 'tenant_realm/db_context'
7
+ require_relative 'tenant_realm/class_methods'
8
+ require_relative 'tenant_realm/configuration/cache'
9
+
10
+ module TenantRealm
11
+ require 'tenant_realm/railtie' if defined?(Rails)
12
+
13
+ class Error < StandardError; end
14
+
15
+ class CurrentTenant < ActiveSupport::CurrentAttributes
16
+ attribute :tenant
17
+ end
18
+
19
+ class Railtie < Rails::Railtie
20
+ config.before_configuration do
21
+ Helpers.dev_log('Tenant Realm: Init shard resolver')
22
+
23
+ config.active_record.shard_selector = { lock: false }
24
+ config.active_record.shard_resolver = lambda { |request|
25
+ skip_switch_db = if Config.skip_resolver.is_a?(Proc)
26
+ Config.skip_resolver.call(request)
27
+ else
28
+ false
29
+ end
30
+
31
+ return :primary if skip_switch_db
32
+
33
+ identifier = Utils.identifier_resolver(request)
34
+ tenant = Tenant.tenant(identifier)
35
+ db_config = Utils.dig_db_config(tenant:)
36
+ shard = Utils.shard_name_from_tenant(tenant:)
37
+
38
+ return :primary if db_config.blank?
39
+
40
+ Config.current.tenant = tenant
41
+ DbContext.add_shard(shard:, db_config:)
42
+ ActiveRecord::Base.connects_to(shards: DbContext.connected_shards)
43
+
44
+ shard
45
+ }
46
+ end
47
+
48
+ initializer 'active_record.setup_tenant_realm' do
49
+ Helpers.dev_log('Tenant Realm: Init database shards')
50
+
51
+ ActiveSupport.on_load(:active_record) do
52
+ ActiveRecord::Base.default_shard = :primary
53
+ ActiveRecord::Base.connects_to(
54
+ shards: {
55
+ primary: { writing: :primary, reading: :primary }
56
+ }
57
+ )
58
+
59
+ DbContext.init_shards
60
+ rescue ActiveRecord::ActiveRecordError
61
+ nil
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/tenant_realm/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'tenant_realm'
7
+ spec.version = TenantRealm::VERSION
8
+ spec.authors = ['Alpha']
9
+ spec.email = ['alphanolucifer@gmail.com']
10
+
11
+ spec.summary = 'Ruby on Rails gem to support multi-tenant'
12
+ spec.description = 'Ruby on Rails gem to support multi-tenant'
13
+ spec.homepage = 'https://github.com/zgid123/tenant_realm'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.1.0'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+
19
+ spec.files = Dir.chdir(__dir__) do
20
+ `git ls-files -z`.split("\x0").reject do |f|
21
+ (File.expand_path(f) == __FILE__) ||
22
+ f.start_with?(
23
+ *%w[
24
+ bin/
25
+ test/
26
+ spec/
27
+ features/
28
+ .git
29
+ .circleci
30
+ appveyor
31
+ examples/
32
+ Gemfile
33
+ .rubocop.yml
34
+ .vscode/settings.json
35
+ LICENSE.txt
36
+ lefthook.yml
37
+ ]
38
+ )
39
+ end
40
+ end
41
+
42
+ spec.require_paths = ['lib']
43
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tenant_realm
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Alpha
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-01-27 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Ruby on Rails gem to support multi-tenant
14
+ email:
15
+ - alphanolucifer@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".ruby-version"
21
+ - README.md
22
+ - Rakefile
23
+ - lib/generators/templates/tenant_realm.tt
24
+ - lib/generators/tenant_realm_generator.rb
25
+ - lib/tasks/migrate.rake
26
+ - lib/tenant_realm.rb
27
+ - lib/tenant_realm/cache/base_cache.rb
28
+ - lib/tenant_realm/cache/kredis_cache.rb
29
+ - lib/tenant_realm/class_methods.rb
30
+ - lib/tenant_realm/config.rb
31
+ - lib/tenant_realm/configuration/cache.rb
32
+ - lib/tenant_realm/db_context.rb
33
+ - lib/tenant_realm/helpers.rb
34
+ - lib/tenant_realm/railtie.rb
35
+ - lib/tenant_realm/tenant.rb
36
+ - lib/tenant_realm/utils.rb
37
+ - lib/tenant_realm/version.rb
38
+ - tenant_realm.gemspec
39
+ homepage: https://github.com/zgid123/tenant_realm
40
+ licenses:
41
+ - MIT
42
+ metadata:
43
+ homepage_uri: https://github.com/zgid123/tenant_realm
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 3.1.0
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.4.13
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Ruby on Rails gem to support multi-tenant
63
+ test_files: []