simply_the_tenant 0.1.2
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +76 -0
- data/Rakefile +5 -0
- data/lib/simply_the_tenant/controller_ext.rb +41 -0
- data/lib/simply_the_tenant/engine.rb +8 -0
- data/lib/simply_the_tenant/model_ext.rb +51 -0
- data/lib/simply_the_tenant/version.rb +5 -0
- data/lib/simply_the_tenant.rb +106 -0
- data/lib/tasks/simply_the_tenant_tasks.rake +5 -0
- metadata +69 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '052989680f273776dd26e7decbe5928bbb5c8062b683ca12683f6842ec0d1eb7'
|
4
|
+
data.tar.gz: cda1b3f3b4985dad3a0c1b74cbadb8ae161702575fd28ec1db32ff1a1357c2c1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 19840f4d70c359dedd629047bfa7da3b9f79b81c0fc794925782dc8a509b19afd94b3330e0b9718b7a536220ae072b8f45a1f581c68dc10045f61169ff5f4982
|
7
|
+
data.tar.gz: af1aee722e97ee874a5e69b93bb299d06713e6f92ba9c184836b991f6c01f64730736ccef6bc441a7fa8b6e9e2977b8a0cc45250390bc5ea8e0be76fbd1c82a0
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright Ethan Kircher
|
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,76 @@
|
|
1
|
+
# SimplyTheTenant
|
2
|
+
Short description and motivation.
|
3
|
+
|
4
|
+
A simpler alternative to [`acts_as_tenant`](https://github.com/ErwinM/acts_as_tenant). For most applications [`acts_as_tenant`](https://github.com/ErwinM/acts_as_tenant) is probably what you should reach for, it's battletested, and more feature rich.
|
5
|
+
|
6
|
+
So, that begs the question, what is the point of this gem at all?
|
7
|
+
|
8
|
+
1. I just kinda wanted to write a multitenancy gem. For basically any applications, prefer `acts_as_tenant`, since it's battletested.
|
9
|
+
2. `acts_as_tenant` uses `ActiveSupport::CurrentAttributes` under the hood, but that isn't exposed to the user. While that isn't exactly hard to add with `acts_as_tenant`, `simply_the_tenant` does this out of the box.
|
10
|
+
3. `simply_the_tenant` adheres to your domain model. `Current.account` vs `current_tenant`, making things marginally easier to reason about.
|
11
|
+
4. `simply_the_tenant` uses Rails 7 `query_constraints`, so certain queries will use compound indices, which is nice for anyone who wants to use composite primary keys or sharding.
|
12
|
+
5. `simply_the_tenant` requires _explicit_ scoping to access the data of a tenant or global data.
|
13
|
+
|
14
|
+
Overall, `acts_as_tenant` does do all of the things this gem does. `simply_the_tenant` just presents them in a different way and provides less configuration options. If that pleases you, feel free to use `simply_the_tenant` instead!
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem "simply_the_tenant"
|
21
|
+
```
|
22
|
+
|
23
|
+
# Getting started
|
24
|
+
Setting up `simply_the_tenant` is essentially identical to `acts_as_tenant`. But, a thing to keep in mind is that `simply_the_tenatn` is strict about naming. There is currently no way to tell `simply_the_tenant` what foreign key to use.
|
25
|
+
|
26
|
+
## Model Setup
|
27
|
+
```ruby
|
28
|
+
class MyTenant < ApplicationRecord
|
29
|
+
simply_the_tenant
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
Anything that belongs to the `MyTenant` _must_ have a `my_tenant_id` column.
|
34
|
+
```ruby
|
35
|
+
class User < ApplicationRecord
|
36
|
+
belongs_to_tenant :my_tenant
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
This will set up a default scope, query constraints, automatic setting of `my_tenant_id` and validations for the tenant.
|
41
|
+
|
42
|
+
You'll also need to setup a `Current` model if you don't already have one.
|
43
|
+
```ruby
|
44
|
+
class Current < ActiveSupport::CurrentAttributes
|
45
|
+
attribute :my_tenant
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
## Controller Setup
|
50
|
+
`simply_the_tenant` uses the last subdomain from a request to determine the tenant to scope a given request to. There is currently no way to change this in `simple_tenant`
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
class ApplicationController < ActionController::Base
|
54
|
+
sets_current_tenant :my_tenant
|
55
|
+
|
56
|
+
def some_cool_action
|
57
|
+
# Current.my_tenant is accessible here automatically
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
## Background Processing
|
63
|
+
`simply_the_tenant` currently doesn't support automatically setting the tenant for any background processing libraries out of the box. This will change shortly.
|
64
|
+
|
65
|
+
## Testing
|
66
|
+
`simply_the_tenant` also doesn't support automatically setting the tenant for any testing libraries out of the box. This will change shortly.
|
67
|
+
|
68
|
+
## Contributing
|
69
|
+
1. Fork the repo
|
70
|
+
2. Make changes
|
71
|
+
3. Run the tests `bundle exec appraisals bin/test`
|
72
|
+
4. Run the linter `bundle exec rubocop`
|
73
|
+
5. Submit a PR
|
74
|
+
|
75
|
+
## License
|
76
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimplyTheTenant
|
4
|
+
module ControllerExt
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
def sets_current_tenant(name)
|
9
|
+
SimplyTheTenant.tenant_class = name.to_s.classify.constantize
|
10
|
+
|
11
|
+
around_action(:with_current_tenant)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
included do
|
16
|
+
def with_current_tenant(&block)
|
17
|
+
SimplyTheTenant.tenant = find_tenant_by_subdomain
|
18
|
+
|
19
|
+
SimplyTheTenant.with_tenant(SimplyTheTenant.tenant, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def with_global_access(&block)
|
23
|
+
SimplyTheTenant.with_global_access(&block)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def find_tenant_by_subdomain
|
29
|
+
SimplyTheTenant.tenant_class.find_by!(subdomain: tenant_subdomain)
|
30
|
+
end
|
31
|
+
|
32
|
+
def tenant_subdomain
|
33
|
+
subdomain = request.subdomains.last
|
34
|
+
|
35
|
+
raise SimplyTheTenant::NoSubdomainError if subdomain.blank?
|
36
|
+
|
37
|
+
subdomain
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimplyTheTenant
|
4
|
+
module ModelExt
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
def simply_the_tenant
|
9
|
+
SimplyTheTenant.tenant_class = self if SimplyTheTenant.tenant_class.blank?
|
10
|
+
end
|
11
|
+
|
12
|
+
def belongs_to_tenant(tenant_name)
|
13
|
+
SimplyTheTenant.tenant_class = tenant_name.to_s.classify.constantize if SimplyTheTenant.tenant_class.blank?
|
14
|
+
|
15
|
+
belongs_to(tenant_name)
|
16
|
+
|
17
|
+
validates(tenant_name, presence: true)
|
18
|
+
|
19
|
+
before_validation(:set_tenant, on: :create)
|
20
|
+
|
21
|
+
query_constraints(SimplyTheTenant.tenant_id, :id)
|
22
|
+
|
23
|
+
default_scope do
|
24
|
+
if SimplyTheTenant.global_access?
|
25
|
+
all
|
26
|
+
else
|
27
|
+
tenant_id = SimplyTheTenant.tenant_id
|
28
|
+
tenant = SimplyTheTenant.tenant
|
29
|
+
|
30
|
+
raise SimplyTheTenant::NoTenantSetError if tenant.blank?
|
31
|
+
|
32
|
+
where(tenant_id => tenant.id)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
included do
|
39
|
+
private
|
40
|
+
|
41
|
+
define_method("set_tenant") do
|
42
|
+
tenant_name = SimplyTheTenant.tenant_name
|
43
|
+
tenant_id = SimplyTheTenant.tenant_id
|
44
|
+
|
45
|
+
return if send(tenant_id).present?
|
46
|
+
|
47
|
+
send("#{tenant_name}=", SimplyTheTenant.tenant)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "simply_the_tenant/version"
|
4
|
+
require "simply_the_tenant/engine"
|
5
|
+
|
6
|
+
module SimplyTheTenant
|
7
|
+
autoload :ModelExt, "simply_the_tenant/model_ext"
|
8
|
+
autoload :ControllerExt, "simply_the_tenant/controller_ext"
|
9
|
+
|
10
|
+
@@tenant_class = nil
|
11
|
+
@@global_access = false
|
12
|
+
|
13
|
+
class << self
|
14
|
+
end
|
15
|
+
def self.tenant_class
|
16
|
+
@@tenant_class
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.tenant_class=(klass)
|
20
|
+
@@tenant_class = klass
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.tenant_name
|
24
|
+
@@tenant_class.name.underscore.to_sym
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.tenant_id
|
28
|
+
"#{tenant_name}_id"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.tenant
|
32
|
+
raise CurrentNeedsTenantError unless Current.respond_to?(tenant_name)
|
33
|
+
|
34
|
+
Current.public_send(tenant_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.tenant=(tenant)
|
38
|
+
raise CurrentNeedsTenantError unless Current.respond_to?("#{tenant_name}=")
|
39
|
+
|
40
|
+
Current.public_send("#{tenant_name}=", tenant)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.with_global_access
|
44
|
+
@@global_access = true
|
45
|
+
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
@@global_access = false
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.global_access?
|
52
|
+
@@global_access
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def self.with_tenant(tenant)
|
57
|
+
raise NilTenantError if tenant.nil?
|
58
|
+
|
59
|
+
previous_tenant = self.tenant
|
60
|
+
self.tenant = tenant
|
61
|
+
|
62
|
+
yield
|
63
|
+
ensure
|
64
|
+
self.tenant = previous_tenant
|
65
|
+
end
|
66
|
+
|
67
|
+
class NilTenantError < StandardError
|
68
|
+
def initialize
|
69
|
+
super("Cannot set a nil tenant.")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class MultipleTenantError < StandardError
|
74
|
+
def initialize
|
75
|
+
super("Multiple tenant classes are not supported.")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class NoTenantSetError < StandardError
|
80
|
+
def initialize
|
81
|
+
super("No tenant class has been set. Use `SimplyTheTenant.with_tenant` to set a tenant, " \
|
82
|
+
"or `SimplyTheTenant.with_global_access` to bypass tenant scoping.")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class CurrentNeedsTenantError < StandardError
|
87
|
+
def initialize
|
88
|
+
super("Current needs to respond to the tenant class name. Use `attribute :{tenant_name}` to define the method.")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class NoSubdomainError < StandardError
|
93
|
+
def initialize
|
94
|
+
super("No subdomain found in request. Ensure that the request has a subdomain, " \
|
95
|
+
"or override `tenant_subdomain` to provide a custom subdomain.")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
ActiveSupport.on_load(:active_record) do
|
101
|
+
include SimplyTheTenant::ModelExt
|
102
|
+
end
|
103
|
+
|
104
|
+
ActiveSupport.on_load(:action_controller) do
|
105
|
+
include SimplyTheTenant::ControllerExt
|
106
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simply_the_tenant
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ethan Kircher
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-03-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.1'
|
27
|
+
description: A simple multi-tenant solution for Rails.
|
28
|
+
email:
|
29
|
+
- ethanmichaelk@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- MIT-LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- lib/simply_the_tenant.rb
|
38
|
+
- lib/simply_the_tenant/controller_ext.rb
|
39
|
+
- lib/simply_the_tenant/engine.rb
|
40
|
+
- lib/simply_the_tenant/model_ext.rb
|
41
|
+
- lib/simply_the_tenant/version.rb
|
42
|
+
- lib/tasks/simply_the_tenant_tasks.rake
|
43
|
+
homepage: https://github.com/MSILycanthropy/simply_the_tenant
|
44
|
+
licenses:
|
45
|
+
- MIT
|
46
|
+
metadata:
|
47
|
+
homepage_uri: https://github.com/MSILycanthropy/simply_the_tenant
|
48
|
+
source_code_uri: https://github.com/MSILycanthropy/simply_the_tenant
|
49
|
+
changelog_uri: https://github.com/MSILycanthropy/simply_the_tenant/blob/main/CHANGELOG.md
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '2.7'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubygems_version: 3.5.6
|
66
|
+
signing_key:
|
67
|
+
specification_version: 4
|
68
|
+
summary: A simple multi-tenant solution for Rails.
|
69
|
+
test_files: []
|