simply_the_tenant 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+
5
+ require "bundler/gem_tasks"
@@ -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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimplyTheTenant
4
+ class Engine < ::Rails::Engine
5
+ engine_name :simply_the_tenant
6
+ isolate_namespace SimplyTheTenant
7
+ end
8
+ 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimplyTheTenant
4
+ VERSION = "0.1.2"
5
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # desc "Explaining what the task does"
3
+ # task :simply_the_tenant do
4
+ # # Task goes here
5
+ # 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: []