├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .standard.yml
├── Appraisals
├── CHANGELOG.md
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── acts_as_tenant.gemspec
├── bin
├── rails
└── test
├── docs
└── blog_post.md
├── gemfiles
├── rails_6.gemfile
├── rails_6.gemfile.lock
├── rails_6_1.gemfile
├── rails_6_1.gemfile.lock
├── rails_7.gemfile
├── rails_7.gemfile.lock
├── rails_7_1.gemfile
├── rails_7_1.gemfile.lock
├── rails_main.gemfile
├── rails_main.gemfile.lock
├── sidekiq_6.gemfile
├── sidekiq_6.gemfile.lock
├── sidekiq_7.gemfile
└── sidekiq_7.gemfile.lock
├── lib
├── acts_as_tenant.rb
└── acts_as_tenant
│ ├── active_job_extensions.rb
│ ├── configuration.rb
│ ├── controller_extensions.rb
│ ├── controller_extensions
│ ├── filter.rb
│ ├── subdomain.rb
│ └── subdomain_or_domain.rb
│ ├── errors.rb
│ ├── model_extensions.rb
│ ├── sidekiq.rb
│ ├── tenant_helper.rb
│ ├── test_tenant_middleware.rb
│ └── version.rb
└── spec
├── acts_as_tenant
├── configuration_spec.rb
└── sidekiq_spec.rb
├── controllers
├── filter_spec.rb
├── subdomain_or_domain_spec.rb
└── subdomain_spec.rb
├── dummy
├── .ruby-version
├── Rakefile
├── app
│ ├── assets
│ │ ├── config
│ │ │ └── manifest.js
│ │ ├── images
│ │ │ └── .keep
│ │ └── stylesheets
│ │ │ └── application.css
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── controllers
│ │ ├── application_controller.rb
│ │ └── concerns
│ │ │ └── .keep
│ ├── helpers
│ │ └── application_helper.rb
│ ├── javascript
│ │ └── packs
│ │ │ └── application.js
│ ├── jobs
│ │ └── application_job.rb
│ ├── mailers
│ │ ├── application_mailer.rb
│ │ └── user_mailer.rb
│ ├── models
│ │ ├── account.rb
│ │ ├── aliased_task.rb
│ │ ├── article.rb
│ │ ├── comment.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ ├── custom_counter_cache_task.rb
│ │ ├── custom_foreign_key_task.rb
│ │ ├── custom_primary_key_task.rb
│ │ ├── global_project.rb
│ │ ├── global_project_with_conditions.rb
│ │ ├── global_project_with_if.rb
│ │ ├── global_project_with_scope.rb
│ │ ├── manager.rb
│ │ ├── polymorphic_tenant_comment.rb
│ │ ├── project.rb
│ │ ├── task.rb
│ │ ├── unique_task.rb
│ │ ├── unscoped_model.rb
│ │ ├── user.rb
│ │ └── users_account.rb
│ └── views
│ │ ├── layouts
│ │ ├── application.html.erb
│ │ ├── mailer.html.erb
│ │ └── mailer.text.erb
│ │ └── user_mailer
│ │ └── welcome_email.html.erb
├── bin
│ ├── rails
│ ├── rake
│ └── setup
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── cable.yml
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── application_controller_renderer.rb
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── content_security_policy.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── puma.rb
│ ├── routes.rb
│ ├── spring.rb
│ └── storage.yml
├── db
│ └── schema.rb
├── lib
│ └── assets
│ │ └── .keep
├── log
│ └── .keep
├── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ └── favicon.ico
├── test
│ └── mailers
│ │ └── previews
│ │ └── user_mailer_preview.rb
└── tmp
│ └── development_secret.txt
├── fixtures
├── accounts.yml
├── custom_primary_key_tasks.yml
├── global_projects.yml
├── projects.yml
└── users.yml
├── helpers
└── tenant_helper_spec.rb
├── jobs
└── active_job_extensions_spec.rb
├── middlewares
└── test_tenant_middleware_spec.rb
├── models
└── model_extensions_spec.rb
└── spec_helper.rb
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [excid3]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - "*"
7 | push:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | test:
13 | runs-on: ubuntu-latest
14 | name: ruby-${{ matrix.ruby }} ${{ matrix.gemfile }}
15 | strategy:
16 | matrix:
17 | ruby: ['2.7', '3.0', '3.1', '3.2']
18 | gemfile:
19 | - rails_6
20 | - rails_6_1
21 | - rails_7
22 | - rails_7_1
23 | - rails_main
24 | - sidekiq_6
25 | - sidekiq_7
26 | exclude:
27 | - ruby: '3.2'
28 | gemfile: rails_6
29 |
30 | env:
31 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
32 | BUNDLE_PATH_RELATIVE_TO_CWD: true
33 |
34 | steps:
35 | - uses: actions/checkout@v3
36 | - name: Set up Ruby
37 | uses: ruby/setup-ruby@v1
38 | with:
39 | ruby-version: ${{ matrix.ruby }}
40 | bundler: default
41 | bundler-cache: true
42 | rubygems: latest
43 | - name: StandardRb check
44 | run: bundle exec standardrb
45 | - name: Run tests
46 | env:
47 | RAILS_ENV: test
48 | run: |
49 | bundle exec rake
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .rvmrc
2 | *.gem
3 | .bundle
4 | Gemfile.lock
5 | pkg/*
6 | spec/dummy/db/*.sqlite3
7 | spec/dummy/db/*.sqlite3-journal
8 | spec/dummy/db/*.sqlite3-*
9 | spec/dummy/log/*.log
10 | spec/dummy/storage/
11 | spec/dummy/tmp/
12 | .byebug_history
13 | .DS_Store
14 |
--------------------------------------------------------------------------------
/.standard.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - "gemfiles/vendor/**/*"
3 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | appraise "rails-6" do
2 | gem "rails", "~> 6.0.0"
3 | end
4 |
5 | appraise "rails-6-1" do
6 | gem "rails", "~> 6.1.0"
7 | end
8 |
9 | appraise "rails-7" do
10 | gem "rails", "~> 7.0.0"
11 | end
12 |
13 | appraise "rails-7-1" do
14 | gem "rails", "~> 7.1.0"
15 | end
16 |
17 | appraise "rails-main" do
18 | gem "rails", github: "rails/rails", branch: :main
19 | %w[rspec rspec-core rspec-expectations rspec-mocks rspec-support rspec-rails].each do |lib|
20 | gem lib, git: "https://github.com/rspec/#{lib}.git", branch: "main"
21 | end
22 | end
23 |
24 | appraise "sidekiq-6" do
25 | gem "sidekiq", "~> 6.0"
26 | end
27 |
28 | appraise "sidekiq-7" do
29 | gem "sidekiq", "~> 7.0"
30 | end
31 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Unreleased
2 | ----------
3 |
4 | * Add `config.tenant_change_hook` callback when a tenant changes. [#333](https://github.com/ErwinM/acts_as_tenant/pull/333)
5 |
6 | This can be used to implement Postgres's row-level security for example
7 |
8 | ```ruby
9 | ActsAsTenant.configure do |config|
10 | config.tenant_change_hook = lambda do |tenant|
11 | if tenant.present?
12 | ActiveRecord::Base.connection.execute(ActiveRecord::Base.sanitize_sql_array(["SET rls.account_id = ?;", tenant.id]))
13 | Rails.logger.info "Changed tenant to " + [tenant.id, tenant.name].to_json
14 | end
15 | end
16 | end
17 | ```
18 |
19 | 1.0.1
20 | -----
21 |
22 | * Cast GID to string for job args #326
23 |
24 | 1.0.0
25 | -----
26 |
27 | * [Breaking] Drop Rails 5.2 support
28 | * Set current_tenant with ActiveJob automatically #319
29 | * Replace RequestStore dependency with CurrentAttributes. #313 - @excid3
30 | * Add `scope` support to `acts_as_tenant :account, ->{ with_deleted }` #282 - @adrian-gomez
31 | The scope will be forwarded to `belongs_to`.
32 | * Add `job_scope` configuration to customize how tenants are loaded in background jobs - @excid3
33 | This is helpful for situations like soft delete:
34 |
35 | ```ruby
36 | ActsAsTenant.configure do |config|
37 | config.job_scope = ->{ with_deleted }
38 | end
39 | ```
40 |
41 | 0.6.1
42 | -----
43 |
44 | * Add `touch` for `belongs_to` association #306
45 |
46 | 0.6.0
47 | -----
48 |
49 | * Add `ActsAsTenant.with_mutable_tenant` for allowing tenants to be changed within a block #230
50 |
51 | 0.5.3
52 | -----
53 |
54 | * Add support for Sidekiq 7 - @excid3
55 | * Fix global record validations with existing scope #294 - @mikecmpbll
56 |
57 | 0.5.2
58 | -----
59 |
60 | * `test_tenant` uses current thread for parallel testing - @mikecmpbll
61 | * Reset `test_tenant` in `with_tenant` - @hakimaryan
62 | * Add `acts_as_tenant through:` option for HABTM - @scarhand
63 | * Allow callable object (lambda, proc, block, etc) for `require_tenant` - @cmer
64 |
65 | 0.5.1
66 | -----
67 |
68 | * Use `klass` from Rails association instead of our own custom lookup - @bramjetten
69 |
70 | 0.5.0
71 | -----
72 |
73 | * Drop support for Rails 5.1 or earlier
74 | * Add tests for Rails 5.2, 6.0, and Rails master
75 | * Use standardrb
76 | * Refactor controller extensions into modules
77 | * Add `subdomain_lookup` option to change which subdomain is used - @excid3
78 | * Unsaved tenant records will now return no records. #227 - @excid3
79 | * Refactor test suite and use dummy Rails app - @excid3
80 | * Remove tenant getter override. Fixes caching issues with association. - @bernardeli
81 |
82 | 0.4.4
83 | -----
84 | * Implement support for polymorphic tenant
85 | * Ability to use acts_as_tenant with only ActiveRecord (no Rails)
86 | * Allow setting of custom primary key
87 | * Bug fixes
88 |
89 | 0.4.3
90 | -----
91 | * allow 'optional' relations
92 | * Sidekiq fixes
93 | * Replace all `before_filter` with `before_action` for Rails 5.1 compatibility
94 |
95 | 0.4.1
96 | ------
97 | * Removed (stale, no longer working) MongoDB support; moved code to separate branch
98 | * Added without_tenant option (see readme, thx duboff)
99 |
100 | 0.4.0
101 | ------
102 | * (Sub)domain lookup is no longer case insensitive
103 | * Added ability to use inverse_of (thx lowjoel)
104 | * Added ability to disable tenant checking for a block (thx duboff)
105 | * Allow for validation that associations belong to the tenant to reflect on associations which return an Array from `where` (thx ludamillion)
106 |
107 | 0.3.9
108 | -----
109 | * Added ability to configure a default tenant for testing purposes. (thx iangreenleaf)
110 | * AaT will now accept a string for a tenant_id (thx calebthompson)
111 | * Improvements to readme (thx stgeneral)
112 |
113 | 0.3.8
114 | -----
115 | * Added Mongoid compatibility [thx iangreenleaf]
116 |
117 | 0.3.7
118 | -----
119 | * Fix for proper handling of polymorphic associations (thx sol1dus)
120 | * Fix fefault scope to generate correct sql when using database prefix (thx IgorDobryn)
121 | * Added ability to specify a custom Primary Key (thx matiasdim)
122 | * Sidekiq 3.2.2+ no longer supports Ruby 1.9. Locking Sidekiq in gemspec at 3.2.1.
123 | * Update RSPEC to 3.0. Convert all specs (thx petergoldstein)
124 | * support sidekiq 3 interface (thx davekaro)
125 |
126 | 0.3.6
127 | -----
128 | * Added method `set_current_tenant_by_subdomain_or_domain` (thx preth00nker)
129 |
130 | 0.3.5
131 | -----
132 | * Fix to degredation introduced after 3.1 that prevented tenant_id from being set during initialization (thx jorgevaldivia)
133 |
134 | 0.3.4
135 | -----
136 | * Fix to a bug introduced in 0.3.2
137 |
138 | 0.3.3
139 | -----
140 | * Support user defined foreign keys on scoped models
141 |
142 | 0.3.2
143 | -----
144 | * correctly support nested models with has_many :through (thx dexion)
145 | * Support 'www.subdomain.example.com' (thx wtfiwtz)
146 | * Support setting `tenant_id` on scoped models if the `tenant_id` is nil (thx Matt Wilson)
147 |
148 | 0.3.1
149 | -----
150 | * Added support for Rails 4
151 |
152 | 0.3.0
153 | -----
154 | * You can now raise an exception if a query on a scope model is made without a tenant set. Adding an initializer that sets config.require_tenant to true will accomplish this. See readme for more details.
155 | * `ActsAsTenant.with_tenant` will now return the value of the block it evaluates instead of the original tenant. The original tenant is restored automatically.
156 | * acts_as_tenant now raises standard errors which can be caught individually.
157 | * `set_current_tenant_to`, which was deprecated some versions ago and could lead to weird errors, has been removed.
158 |
159 |
160 | 0.2.9
161 | -----
162 | * Added support for many-to-many associations (thx Nucleoid)
163 |
164 | 0.2.8
165 | -----
166 | * Added dependencies to gemspec (thx aaronrenner)
167 | * Added the `ActsAsTenant.with_tenant` block method (see readme) (thx aaronrenner)
168 | * Acts_as_Tenant is now thread safe (thx davide)
169 |
170 | 0.2.7
171 | -----
172 | * Changed the interface for passing in the current_tenant manually in the controller. `set_current_tenant_to` has been deprecated and replaced by `set_current_tenant_through_filter` declaration and the `set_current_tenant` method. See readme for details.
173 |
174 | 0.2.6
175 | -----
176 | * Fixed a bug with resolving the tenant model name (thx devton!)
177 | * Added support for using relations: User.create(:account => Account.first) now works, while it wouldn't before (thx bnmrrs)
178 |
179 | 0.2.5
180 | -----
181 | * Added Rails 3.2 compatibility (thx nickveys!)
182 |
183 | 0.2.4
184 | -----
185 | * Added correct handling of child models that do not have their parent set (foreign key == nil)
186 |
187 |
188 | 0.2.3
189 | -----
190 | * Added support for models that declare a has_one relationships, these would error out in the previous versions.
191 |
192 |
193 | 0.2.2
194 | -----
195 | * Enhancements
196 | * Added support for aliased associations ( belongs_to :something, :class_name => 'SomethingElse'). In previous version these would raise an 'uninitialized constant' error.
197 |
198 | 0.2.1
199 | -----
200 | * Initial release
201 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | # Declare your gem's dependencies in noticed.gemspec.
5 | # Bundler will treat runtime dependencies like base dependencies, and
6 | # development dependencies will be added by default to the :development group.
7 | gemspec
8 |
9 | # Declare any dependencies that are still in development here instead of in
10 | # your gemspec. These might include edge Rails or gems from your path or
11 | # Git. Remember to move these dependencies to your gemspec before releasing
12 | # your gem to rubygems.org.
13 |
14 | gem "rspec", ">=3.0"
15 | gem "rspec-rails"
16 | gem "sqlite3"
17 | gem "standard"
18 | gem "sidekiq", "~> 7.0"
19 | gem "appraisal", github: "thoughtbot/appraisal"
20 |
21 | # To use a debugger
22 | gem "byebug", group: [:development, :test]
23 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 [name of plugin creator]
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.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Acts As Tenant
2 |
3 | [](https://github.com/ErwinM/acts_as_tenant/actions) [](https://badge.fury.io/rb/acts_as_tenant)
4 |
5 | Row-level multitenancy for Ruby on Rails apps.
6 |
7 | This gem was born out of our own need for a fail-safe and out-of-the-way manner to add multi-tenancy to our Rails app through a shared database strategy, that integrates (near) seamless with Rails.
8 |
9 | acts_as_tenant adds the ability to scope models to a tenant. Tenants are represented by a tenant model, such as `Account`. acts_as_tenant will help you set the current tenant on each request and ensures all 'tenant models' are always properly scoped to the current tenant: when viewing, searching and creating.
10 |
11 | In addition, acts_as_tenant:
12 |
13 | * sets the current tenant using the subdomain or allows you to pass in the current tenant yourself
14 | * protects against various types of nastiness directed at circumventing the tenant scoping
15 | * adds a method to validate uniqueness to a tenant, `validates_uniqueness_to_tenant`
16 | * sets up a helper method containing the current tenant
17 |
18 | **Note**: acts_as_tenant was introduced in this [blog post](https://github.com/ErwinM/acts_as_tenant/blob/master/docs/blog_post.md).
19 |
20 | **Row-level vs schema multitenancy**
21 |
22 | What's the difference?
23 |
24 | Row-level multitenancy each model must have a tenant ID column on it. This makes it easy to filter records for each tenant using your standard database columns and indexes. ActsAsTenant uses row-level multitenancy.
25 |
26 | Schema multitenancy uses database schemas to handle multitenancy. For this approach, your database has multiple schemas and each schema contains your database tables. Schemas require migrations to be run against each tenant and generally makes it harder to scale as you add more tenants. The Apartment gem uses schema multitenancy.
27 |
28 | #### 🎬 Walkthrough
29 |
30 | Want to see how it works? Check out [the ActsAsTenant walkthrough video](https://www.youtube.com/watch?v=BIyxM9f8Jus):
31 |
32 |
33 |
34 |
35 |
36 | Installation
37 | ------------
38 |
39 | To use it, add it to your Gemfile:
40 |
41 | ```ruby
42 | gem 'acts_as_tenant'
43 | ```
44 |
45 | Getting started
46 | ===============
47 |
48 | There are two steps in adding multi-tenancy to your app with acts_as_tenant:
49 |
50 | 1. setting the current tenant and
51 | 2. scoping your models.
52 |
53 | Setting the current tenant
54 | --------------------------
55 |
56 | There are three ways to set the current tenant:
57 |
58 | 1. by using the subdomain to lookup the current tenant,
59 | 2. by setting the current tenant in the controller, and
60 | 3. by setting the current tenant for a block.
61 |
62 | ### Looking Up Tenants
63 |
64 | #### By Subdomain to lookup the current tenant
65 |
66 | ```ruby
67 | class ApplicationController < ActionController::Base
68 | set_current_tenant_by_subdomain(:account, :subdomain)
69 | end
70 | ```
71 |
72 | This tells acts_as_tenant to use the last subdomain to identify the current tenant. In addition, it tells acts_as_tenant that tenants are represented by the Account model and this model has a column named 'subdomain' which can be used to lookup the Account using the actual subdomain. If ommitted, the parameters will default to the values used above.
73 |
74 | By default, the *last* subdomain will be used for lookup. Pass in `subdomain_lookup: :first` to use the first subdomain instead.
75 |
76 | #### By Domain to lookup the current tenant
77 |
78 | ```ruby
79 | class ApplicationController < ActionController::Base
80 | set_current_tenant_by_subdomain_or_domain(:account, :subdomain, :domain)
81 | end
82 | ```
83 |
84 | You can locate the tenant using `set_current_tenant_by_subdomain_or_domain( :account, :subdomain, :domain )` which will check for a subdomain and fallback to domain.
85 |
86 | By default, the *last* subdomain will be used for lookup. Pass in `subdomain_lookup: :first` to use the first subdomain instead.
87 |
88 | #### Manually using before_action
89 |
90 | ```ruby
91 | class ApplicationController < ActionController::Base
92 | set_current_tenant_through_filter
93 | before_action :your_method_that_finds_the_current_tenant
94 |
95 | def your_method_that_finds_the_current_tenant
96 | current_account = Account.find_it
97 | set_current_tenant(current_account)
98 | end
99 | end
100 | ```
101 |
102 | Setting the `current_tenant` yourself, requires you to declare `set_current_tenant_through_filter` at the top of your application_controller to tell acts_as_tenant that you are going to use a before_action to setup the current tenant. Next you should actually setup that before_action to fetch the current tenant and pass it to `acts_as_tenant` by using `set_current_tenant(current_tenant)` in the before_action.
103 |
104 | If you are setting the tenant in a specific controller (except `application_controller`), it should to be included **AT THE TOP** of the file.
105 |
106 | ```ruby
107 | class MembersController < ActionController::Base
108 | set_current_tenant_through_filter
109 | before_action :set_tenant
110 | before_action :set_member, only: [:show, :edit, :update, :destroy]
111 |
112 | def set_tenant
113 | set_current_tenant(current_user.account)
114 | end
115 | end
116 | ```
117 |
118 | This allows the tenant to be set before any other code runs so everything is within the current tenant.
119 |
120 | ### Setting the current tenant for a block
121 |
122 | ```ruby
123 | ActsAsTenant.with_tenant(current_account) do
124 | # Current tenant is set for all code in this block
125 | end
126 | ```
127 |
128 | This approach is useful when running background processes for a specified tenant. For example, by putting this in your worker's run method,
129 | any code in this block will be scoped to the current tenant. All methods that set the current tenant are thread safe.
130 |
131 | **Note:** If the current tenant is not set by one of these methods, Acts_as_tenant will be unable to apply the proper scope to your models. So make sure you use one of the two methods to tell acts_as_tenant about the current tenant.
132 |
133 | ### Disabling tenant checking for a block
134 |
135 | ```ruby
136 | ActsAsTenant.without_tenant do
137 | # Tenant checking is disabled for all code in this block
138 | end
139 | ```
140 |
141 | This is useful in shared routes such as admin panels or internal dashboards when `require_tenant` option is enabled throughout the app.
142 |
143 | ### Allowing tenant updating for a block
144 |
145 | ```ruby
146 | ActsAsTenant.with_mutable_tenant do
147 | # Tenant updating is enabled for all code in this block
148 | end
149 | ```
150 |
151 | This will allow you to change the tenant of a model. This feature is useful for admin screens, where it is ok to allow certain users to change the tenant on existing models in specific cases.
152 |
153 | ### Require tenant to be set always
154 |
155 | If you want to require the tenant to be set at all times, you can configure acts_as_tenant to raise an error when a query is made without a tenant available. See below under configuration options.
156 |
157 | Scoping your models
158 | -------------------
159 |
160 | ```ruby
161 | class AddAccountToProjects < ActiveRecord::Migration
162 | def up
163 | add_column :projects, :account_id, :integer
164 | add_index :projects, :account_id
165 | end
166 | end
167 |
168 | class Project < ActiveRecord::Base
169 | acts_as_tenant(:account)
170 | end
171 | ```
172 |
173 | `acts_as_tenant` requires each scoped model to have a column in its schema linking it to a tenant. Adding `acts_as_tenant` to your model declaration will scope that model to the current tenant **BUT ONLY if a current tenant has been set**.
174 |
175 | Some examples to illustrate this behavior:
176 |
177 | ```ruby
178 | # This manually sets the current tenant for testing purposes. In your app this is handled by the gem.
179 | ActsAsTenant.current_tenant = Account.find(3)
180 |
181 | # All searches are scoped by the tenant, the following searches will only return objects
182 | # where account_id == 3
183 | Project.all => # all projects with account_id => 3
184 | Project.tasks.all # => all tasks with account_id => 3
185 |
186 | # New objects are scoped to the current tenant
187 | @project = Project.new(:name => 'big project') # => <#Project id: nil, name: 'big project', :account_id: 3>
188 |
189 | # It will not allow the creation of objects outside the current_tenant scope
190 | @project.account_id = 2
191 | @project.save # => false
192 |
193 | # It will not allow association with objects outside the current tenant scope
194 | # Assuming the Project with ID: 2 does not belong to Account with ID: 3
195 | @task = Task.new # => <#Task id: nil, name: nil, project_id: nil, :account_id: 3>
196 | ```
197 |
198 | Acts_as_tenant uses Rails' `default_scope` method to scope models. Rails 3.1 changed the way `default_scope` works in a good way. A user defined `default_scope` should integrate seamlessly with the one added by `acts_as_tenant`.
199 |
200 | You should call `acts_as_tenant` after any `belongs_to` associations in your model.
201 |
202 | ### Validating attribute uniqueness
203 |
204 | If you need to validate for uniqueness, chances are that you want to scope this validation to a tenant. You can do so by using:
205 |
206 | ```ruby
207 | validates_uniqueness_to_tenant :name, :email
208 | ```
209 |
210 | All options available to Rails' own `validates_uniqueness_of` are also available to this method.
211 |
212 | ### Custom foreign_key
213 |
214 | You can explicitly specifiy a foreign_key for AaT to use should the key differ from the default:
215 |
216 | ```ruby
217 | acts_as_tenant(:account, :foreign_key => 'accountID') # by default AaT expects account_id
218 | ```
219 |
220 | ### Custom primary_key
221 |
222 | You can also explicitly specifiy a primary_key for AaT to use should the key differ from the default:
223 |
224 | ```ruby
225 | acts_as_tenant(:account, :primary_key => 'primaryID') # by default AaT expects id
226 | ```
227 |
228 | ### Has and belongs to many
229 |
230 | You can scope a model that is part of a HABTM relationship by using the `through` option.
231 |
232 | ```ruby
233 | class Organisation < ActiveRecord::Base
234 | has_many :organisations_users
235 | has_many :users, through: :organisations_users
236 | end
237 |
238 | class User < ActiveRecord::Base
239 | has_many :organisations_users
240 | acts_as_tenant :organisation, through: :organisations_users
241 | end
242 |
243 | class OrganisationsUser < ActiveRecord::Base
244 | belongs_to :user
245 | acts_as_tenant :organisation
246 | end
247 | ```
248 |
249 | Configuration options
250 | ---------------------
251 |
252 | An initializer can be created to control (currently one) option in ActsAsTenant. Defaults
253 | are shown below with sample overrides following. In `config/initializers/acts_as_tenant.rb`:
254 |
255 | ```ruby
256 | ActsAsTenant.configure do |config|
257 | config.require_tenant = false # true
258 |
259 | # Customize the query for loading the tenant in background jobs
260 | config.job_scope = ->{ all }
261 | end
262 | ```
263 |
264 | * `config.require_tenant` when set to true will raise an ActsAsTenant::NoTenant error whenever a query is made without a tenant set.
265 |
266 | `config.require_tenant` can also be assigned a lambda that is evaluated at run time. For example:
267 |
268 | ```ruby
269 | ActsAsTenant.configure do |config|
270 | config.require_tenant = lambda do
271 | if $request_env.present?
272 | return false if $request_env["REQUEST_PATH"].start_with?("/admin/")
273 | end
274 | end
275 | end
276 | ```
277 |
278 | `ActsAsTenant.should_require_tenant?` is used to determine if a tenant is required in the current context, either by evaluating the lambda provided, or by returning the boolean value assigned to `config.require_tenant`.
279 |
280 | When using `config.require_tenant` alongside the `rails console`, a nice quality of life tweak is to set the tenant in the console session in your initializer script. For example in `config/initializers/acts_as_tenant.rb`:
281 |
282 | ```ruby
283 | SET_TENANT_PROC = lambda do
284 | if defined?(Rails::Console)
285 | puts "> ActsAsTenant.current_tenant = Account.first"
286 | ActsAsTenant.current_tenant = Account.first
287 | end
288 | end
289 |
290 | Rails.application.configure do
291 | if Rails.env.development?
292 | # Set the tenant to the first account in development on load
293 | config.after_initialize do
294 | SET_TENANT_PROC.call
295 | end
296 |
297 | # Reset the tenant after calling 'reload!' in the console
298 | ActiveSupport::Reloader.to_complete do
299 | SET_TENANT_PROC.call
300 | end
301 | end
302 | end
303 | ```
304 |
305 | belongs_to options
306 | ------------------
307 |
308 | `acts_as_tenant :account` includes the belongs_to relationship.
309 | So when using acts_as_tenant on a model, do not add `belongs_to :account` alongside `acts_as_tenant :account`:
310 |
311 | ```ruby
312 | class User < ActiveRecord::Base
313 | acts_as_tenant(:account) # YES
314 | belongs_to :account # REDUNDANT
315 | end
316 | ```
317 |
318 | You can add the following `belongs_to` options to `acts_as_tenant`:
319 | `:foreign_key, :class_name, :inverse_of, :optional, :primary_key, :counter_cache, :polymorphic, :touch`
320 |
321 | Example: `acts_as_tenant(:account, counter_cache: true)`
322 |
323 | Background Processing libraries
324 | -------------------------------
325 |
326 | ActsAsTenant supports
327 |
328 | - ActiveJob - ActsAsTenant will automatically save the current tenant in ActiveJob arguments and set it when the job runs.
329 |
330 | - [Sidekiq](//sidekiq.org/)
331 | Add the following code to `config/initializers/acts_as_tenant.rb`:
332 |
333 | ```ruby
334 | require 'acts_as_tenant/sidekiq'
335 | ```
336 |
337 | - DelayedJob - [acts_as_tenant-delayed_job](https://github.com/nunommc/acts_as_tenant-delayed_job)
338 |
339 | Testing
340 | ---------------
341 |
342 | If you set the `current_tenant` in your tests, make sure to clean up the tenant after each test by calling `ActsAsTenant.current_tenant = nil`. Integration tests are more difficult: manually setting the `current_tenant` value will not survive across multiple requests, even if they take place within the same test. This can result in undesired boilerplate to set the desired tenant. Moreover, the efficacy of the test can be compromised because the set `current_tenant` value will carry over into the request-response cycle.
343 |
344 | To address this issue, ActsAsTenant provides for a `test_tenant` value that can be set to allow for setup and post-request expectation testing. It should be used in conjunction with middleware that clears out this value while an integration test is processing. A typical Rails and RSpec setup might look like:
345 |
346 | ```ruby
347 | # test.rb
348 | require_dependency 'acts_as_tenant/test_tenant_middleware'
349 |
350 | Rails.application.configure do
351 | config.middleware.use ActsAsTenant::TestTenantMiddleware
352 | end
353 | ```
354 | ```ruby
355 | # spec_helper.rb
356 | config.before(:suite) do |example|
357 | # Make the default tenant globally available to the tests
358 | $default_account = Account.create!
359 | end
360 |
361 | config.before(:each) do |example|
362 | if example.metadata[:type] == :request
363 | # Set the `test_tenant` value for integration tests
364 | ActsAsTenant.test_tenant = $default_account
365 | else
366 | # Otherwise just use current_tenant
367 | ActsAsTenant.current_tenant = $default_account
368 | end
369 | end
370 |
371 | config.after(:each) do |example|
372 | # Clear any tenancy that might have been set
373 | ActsAsTenant.current_tenant = nil
374 | ActsAsTenant.test_tenant = nil
375 | end
376 | ```
377 | Bug reports & suggested improvements
378 | ------------------------------------
379 |
380 | If you have found a bug or want to suggest an improvement, please use our issue tracked at:
381 |
382 | [github.com/ErwinM/acts_as_tenant/issues](http://github.com/ErwinM/acts_as_tenant/issues)
383 |
384 | If you want to contribute, fork the project, code your improvements and make a pull request on [Github](http://github.com/ErwinM/acts_as_tenant/). When doing so, please don't forget to add tests. If your contribution is fixing a bug it would be perfect if you could also submit a failing test, illustrating the issue.
385 |
386 | Contributing to this gem
387 | ------------------------
388 |
389 | We use the Appraisal gem to run tests against supported versions of Rails to test for compatibility against them all. StandardRb also helps keep code formatted cleanly.
390 |
391 | 1. Fork the repo
392 | 2. Make changes
393 | 3. Run test suite with `bundle exec appraisal`
394 | 4. Run `bundle exec standardrb` to standardize code formatting
395 | 5. Submit a PR
396 |
397 | Author & Credits
398 | ----------------
399 |
400 | acts_as_tenant is written by Erwin Matthijssen & Chris Oliver.
401 |
402 | This gem was inspired by Ryan Sonnek's [Multitenant](https://github.com/wireframe/multitenant) gem and its use of default_scope.
403 |
404 | License
405 | -------
406 |
407 | Copyright (c) 2011 Erwin Matthijssen, released under the MIT license
408 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
3 | APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
4 |
5 | require "rspec/core/rake_task"
6 | RSpec::Core::RakeTask.new(:spec)
7 | task default: :spec
8 |
--------------------------------------------------------------------------------
/acts_as_tenant.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path("../lib", __FILE__)
2 | require "acts_as_tenant/version"
3 |
4 | Gem::Specification.new do |spec|
5 | spec.name = "acts_as_tenant"
6 | spec.version = ActsAsTenant::VERSION
7 | spec.authors = ["Erwin Matthijssen", "Chris Oliver"]
8 | spec.email = ["erwin.matthijssen@gmail.com", "excid3@gmail.com"]
9 | spec.homepage = "https://github.com/ErwinM/acts_as_tenant"
10 | spec.summary = "Add multi-tenancy to Rails applications using a shared db strategy"
11 | spec.description = "Integrates multi-tenancy into a Rails application in a convenient and out-of-your way manner"
12 | spec.metadata = { "rubygems_mfa_required" => "true" }
13 |
14 | spec.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
15 |
16 | spec.require_paths = ["lib"]
17 |
18 | spec.add_dependency "rails", ">= 6.0"
19 | end
20 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails gems
3 | # installed from the root of your application.
4 |
5 | ENGINE_ROOT = File.expand_path('..', __dir__)
6 | APP_PATH = File.expand_path('../spec/dummy/config/application', __dir__)
7 |
8 | # Set up gems listed in the Gemfile.
9 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
10 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
11 |
12 | require 'rails/all'
13 | require 'rails/engine/commands'
14 |
--------------------------------------------------------------------------------
/bin/test:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | $: << File.expand_path("../test", __dir__)
3 |
4 | require "bundler/setup"
5 | require "rails/plugin/test"
6 |
--------------------------------------------------------------------------------
/docs/blog_post.md:
--------------------------------------------------------------------------------
1 |
With a multi-db or multi-schema approach, you only deal with the multi-tenancy-aspect in a few specific spots in your app (Jerod Santo recently wrote an excellent post on implementing a multi-schema strategy). Compared to the previous two strategies, a shared database strategy has the downside that the ‘multi-tenancy’-logic is something you need to actively be aware of and manage in almost every part of your app.
4 |For various other reasons we opted for a shared database strategy. However, for us the prospect of dealing with the multi-tenancy-logic throughout our app, was not appealing. Worse, we run the risk of accidently exposing content of one tenant to another one, if we mismanage this logic. While researching this topic I noticed there are no real ready made solutions available that get you on your way, Ryan Sonnek wrote his ‘multitenant’ gem and Mark Connel did the same. Neither of these solution seemed “finished” to us. So, we wrote our own implementation.
6 |A shared database strategy manages the multi-tenancy-logic through Rails associations. A tenant is represented by an object, for example an Account
. All other objects are associated with a tenant: belongs_to :account
. Each request starts with finding the @current_account
. After that, each find is scoped through the tenant object: current_account.projects.all
. This has to be remembered everywhere: in model method declarations and in controller actions. Otherwise, you’re exposing content of other tenants.
In addition, you have to actively babysit other parts of your app: validates_uniqueness_of
requires you to scope it to the current tenant. You also have to protect agaist all sorts of form-injections that could allow one tenant to gain access or temper with the content of another tenant (see Paul Gallaghers presentation for more on these dangers).
I wanted to implement all the concerns above in an easy to manage, out of the way fashion. We should be able to add a single declaration to our model and that should implement:
11 |Account
Account
The result is acts_as_tenant
(github), a rails gem that will add multi tenancy using a shared database to your rails app in an out-of-your way fashion.
In the README, you will find more information on using acts_as_tenant
in your projects, so we’ll give you a high-level overview here. Let’s suppose that you have an app to which you want to add multi tenancy, tenants are represented by the Account
model and Project
is one of the models that should be scoped by tenant:
Of course, all the above assumes `acts_as_tenant` actually knows who the current tenant is. Two strategies are implemented to help with this.
39 |Using the subdomain to workout the current tenant
40 | 41 | ```ruby 42 | class ApplicationController < ActionController::Base 43 | set_current_tenant_by_subdomain(:account, :subdomain) 44 | end 45 | ``` 46 |Adding the above method to your `application_controller` tells `acts_as_tenant`:
47 |Passing the current account to acts_as_tenant yourself
53 | 54 | ```ruby 55 | class ApplicationController < ActionController::Base 56 | current_account = Account.method_to_find_the_current_account 57 | set_current_tenant_to(current_account) 58 | end 59 | ``` 60 |`Acts_as_tenant` also adds a handy helper to your controllers `current_tenant`, containing the current tenant object.
61 |We have been testing acts_as_tenant within Roll Call during recent weeks and it seems to be behaving well. Having said that, we welcome any feedback. This is my first real attempt at a plugin and the possibility of various improvements is almost a given.
68 | -------------------------------------------------------------------------------- /gemfiles/rails_6.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rspec", ">=3.0" 6 | gem "rspec-rails" 7 | gem "sqlite3" 8 | gem "standard" 9 | gem "sidekiq", "~> 7.0" 10 | gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git" 11 | gem "byebug", group: [:development, :test] 12 | gem "rails", "~> 6.0.0" 13 | 14 | gemspec path: "../" 15 | -------------------------------------------------------------------------------- /gemfiles/rails_6.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/thoughtbot/appraisal.git 3 | revision: feb78bcc6177038399bff098cb6c2bd4bca4972a 4 | specs: 5 | appraisal (2.5.0) 6 | bundler 7 | rake 8 | thor (>= 0.14.0) 9 | 10 | PATH 11 | remote: .. 12 | specs: 13 | acts_as_tenant (1.0.1) 14 | rails (>= 6.0) 15 | 16 | GEM 17 | remote: https://rubygems.org/ 18 | specs: 19 | actioncable (6.0.6.1) 20 | actionpack (= 6.0.6.1) 21 | nio4r (~> 2.0) 22 | websocket-driver (>= 0.6.1) 23 | actionmailbox (6.0.6.1) 24 | actionpack (= 6.0.6.1) 25 | activejob (= 6.0.6.1) 26 | activerecord (= 6.0.6.1) 27 | activestorage (= 6.0.6.1) 28 | activesupport (= 6.0.6.1) 29 | mail (>= 2.7.1) 30 | actionmailer (6.0.6.1) 31 | actionpack (= 6.0.6.1) 32 | actionview (= 6.0.6.1) 33 | activejob (= 6.0.6.1) 34 | mail (~> 2.5, >= 2.5.4) 35 | rails-dom-testing (~> 2.0) 36 | actionpack (6.0.6.1) 37 | actionview (= 6.0.6.1) 38 | activesupport (= 6.0.6.1) 39 | rack (~> 2.0, >= 2.0.8) 40 | rack-test (>= 0.6.3) 41 | rails-dom-testing (~> 2.0) 42 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 43 | actiontext (6.0.6.1) 44 | actionpack (= 6.0.6.1) 45 | activerecord (= 6.0.6.1) 46 | activestorage (= 6.0.6.1) 47 | activesupport (= 6.0.6.1) 48 | nokogiri (>= 1.8.5) 49 | actionview (6.0.6.1) 50 | activesupport (= 6.0.6.1) 51 | builder (~> 3.1) 52 | erubi (~> 1.4) 53 | rails-dom-testing (~> 2.0) 54 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 55 | activejob (6.0.6.1) 56 | activesupport (= 6.0.6.1) 57 | globalid (>= 0.3.6) 58 | activemodel (6.0.6.1) 59 | activesupport (= 6.0.6.1) 60 | activerecord (6.0.6.1) 61 | activemodel (= 6.0.6.1) 62 | activesupport (= 6.0.6.1) 63 | activestorage (6.0.6.1) 64 | actionpack (= 6.0.6.1) 65 | activejob (= 6.0.6.1) 66 | activerecord (= 6.0.6.1) 67 | marcel (~> 1.0) 68 | activesupport (6.0.6.1) 69 | concurrent-ruby (~> 1.0, >= 1.0.2) 70 | i18n (>= 0.7, < 2) 71 | minitest (~> 5.1) 72 | tzinfo (~> 1.1) 73 | zeitwerk (~> 2.2, >= 2.2.2) 74 | ast (2.4.2) 75 | builder (3.2.4) 76 | byebug (11.1.3) 77 | concurrent-ruby (1.2.2) 78 | connection_pool (2.4.1) 79 | crass (1.0.6) 80 | date (3.3.4) 81 | diff-lcs (1.5.0) 82 | erubi (1.12.0) 83 | globalid (1.1.0) 84 | activesupport (>= 5.0) 85 | i18n (1.14.1) 86 | concurrent-ruby (~> 1.0) 87 | json (2.7.1) 88 | language_server-protocol (3.17.0.3) 89 | lint_roller (1.1.0) 90 | loofah (2.22.0) 91 | crass (~> 1.0.2) 92 | nokogiri (>= 1.12.0) 93 | mail (2.8.1) 94 | mini_mime (>= 0.1.1) 95 | net-imap 96 | net-pop 97 | net-smtp 98 | marcel (1.0.2) 99 | method_source (1.0.0) 100 | mini_mime (1.1.5) 101 | minitest (5.20.0) 102 | net-imap (0.4.7) 103 | date 104 | net-protocol 105 | net-pop (0.1.2) 106 | net-protocol 107 | net-protocol (0.2.2) 108 | timeout 109 | net-smtp (0.4.0) 110 | net-protocol 111 | nio4r (2.7.0) 112 | nokogiri (1.15.5-arm64-darwin) 113 | racc (~> 1.4) 114 | nokogiri (1.15.5-x86_64-darwin) 115 | racc (~> 1.4) 116 | nokogiri (1.15.5-x86_64-linux) 117 | racc (~> 1.4) 118 | parallel (1.23.0) 119 | parser (3.2.2.4) 120 | ast (~> 2.4.1) 121 | racc 122 | racc (1.7.3) 123 | rack (2.2.8) 124 | rack-test (2.1.0) 125 | rack (>= 1.3) 126 | rails (6.0.6.1) 127 | actioncable (= 6.0.6.1) 128 | actionmailbox (= 6.0.6.1) 129 | actionmailer (= 6.0.6.1) 130 | actionpack (= 6.0.6.1) 131 | actiontext (= 6.0.6.1) 132 | actionview (= 6.0.6.1) 133 | activejob (= 6.0.6.1) 134 | activemodel (= 6.0.6.1) 135 | activerecord (= 6.0.6.1) 136 | activestorage (= 6.0.6.1) 137 | activesupport (= 6.0.6.1) 138 | bundler (>= 1.3.0) 139 | railties (= 6.0.6.1) 140 | sprockets-rails (>= 2.0.0) 141 | rails-dom-testing (2.2.0) 142 | activesupport (>= 5.0.0) 143 | minitest 144 | nokogiri (>= 1.6) 145 | rails-html-sanitizer (1.6.0) 146 | loofah (~> 2.21) 147 | nokogiri (~> 1.14) 148 | railties (6.0.6.1) 149 | actionpack (= 6.0.6.1) 150 | activesupport (= 6.0.6.1) 151 | method_source 152 | rake (>= 0.8.7) 153 | thor (>= 0.20.3, < 2.0) 154 | rainbow (3.1.1) 155 | rake (13.1.0) 156 | redis-client (0.18.0) 157 | connection_pool 158 | regexp_parser (2.8.3) 159 | rexml (3.2.6) 160 | rspec (3.12.0) 161 | rspec-core (~> 3.12.0) 162 | rspec-expectations (~> 3.12.0) 163 | rspec-mocks (~> 3.12.0) 164 | rspec-core (3.12.2) 165 | rspec-support (~> 3.12.0) 166 | rspec-expectations (3.12.3) 167 | diff-lcs (>= 1.2.0, < 2.0) 168 | rspec-support (~> 3.12.0) 169 | rspec-mocks (3.12.6) 170 | diff-lcs (>= 1.2.0, < 2.0) 171 | rspec-support (~> 3.12.0) 172 | rspec-rails (5.1.2) 173 | actionpack (>= 5.2) 174 | activesupport (>= 5.2) 175 | railties (>= 5.2) 176 | rspec-core (~> 3.10) 177 | rspec-expectations (~> 3.10) 178 | rspec-mocks (~> 3.10) 179 | rspec-support (~> 3.10) 180 | rspec-support (3.12.1) 181 | rubocop (1.57.2) 182 | json (~> 2.3) 183 | language_server-protocol (>= 3.17.0) 184 | parallel (~> 1.10) 185 | parser (>= 3.2.2.4) 186 | rainbow (>= 2.2.2, < 4.0) 187 | regexp_parser (>= 1.8, < 3.0) 188 | rexml (>= 3.2.5, < 4.0) 189 | rubocop-ast (>= 1.28.1, < 2.0) 190 | ruby-progressbar (~> 1.7) 191 | unicode-display_width (>= 2.4.0, < 3.0) 192 | rubocop-ast (1.30.0) 193 | parser (>= 3.2.1.0) 194 | rubocop-performance (1.19.1) 195 | rubocop (>= 1.7.0, < 2.0) 196 | rubocop-ast (>= 0.4.0) 197 | ruby-progressbar (1.13.0) 198 | sidekiq (7.2.0) 199 | concurrent-ruby (< 2) 200 | connection_pool (>= 2.3.0) 201 | rack (>= 2.2.4) 202 | redis-client (>= 0.14.0) 203 | sprockets (4.2.1) 204 | concurrent-ruby (~> 1.0) 205 | rack (>= 2.2.4, < 4) 206 | sprockets-rails (3.4.2) 207 | actionpack (>= 5.2) 208 | activesupport (>= 5.2) 209 | sprockets (>= 3.0.0) 210 | sqlite3 (1.6.9-arm64-darwin) 211 | sqlite3 (1.6.9-x86_64-darwin) 212 | sqlite3 (1.6.9-x86_64-linux) 213 | standard (1.32.1) 214 | language_server-protocol (~> 3.17.0.2) 215 | lint_roller (~> 1.0) 216 | rubocop (~> 1.57.2) 217 | standard-custom (~> 1.0.0) 218 | standard-performance (~> 1.2) 219 | standard-custom (1.0.2) 220 | lint_roller (~> 1.0) 221 | rubocop (~> 1.50) 222 | standard-performance (1.2.1) 223 | lint_roller (~> 1.1) 224 | rubocop-performance (~> 1.19.1) 225 | thor (1.3.0) 226 | thread_safe (0.3.6) 227 | timeout (0.4.1) 228 | tzinfo (1.2.11) 229 | thread_safe (~> 0.1) 230 | unicode-display_width (2.5.0) 231 | websocket-driver (0.7.6) 232 | websocket-extensions (>= 0.1.0) 233 | websocket-extensions (0.1.5) 234 | zeitwerk (2.6.12) 235 | 236 | PLATFORMS 237 | arm64-darwin-22 238 | x86_64-darwin-20 239 | x86_64-darwin-21 240 | x86_64-darwin-22 241 | x86_64-linux 242 | 243 | DEPENDENCIES 244 | acts_as_tenant! 245 | appraisal! 246 | byebug 247 | rails (~> 6.0.0) 248 | rspec (>= 3.0) 249 | rspec-rails 250 | sidekiq (~> 7.0) 251 | sqlite3 252 | standard 253 | 254 | BUNDLED WITH 255 | 2.4.22 256 | -------------------------------------------------------------------------------- /gemfiles/rails_6_1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rspec", ">=3.0" 6 | gem "rspec-rails" 7 | gem "sqlite3" 8 | gem "standard" 9 | gem "sidekiq", "~> 7.0" 10 | gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git" 11 | gem "byebug", group: [:development, :test] 12 | gem "rails", "~> 6.1.0" 13 | 14 | gemspec path: "../" 15 | -------------------------------------------------------------------------------- /gemfiles/rails_6_1.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/thoughtbot/appraisal.git 3 | revision: feb78bcc6177038399bff098cb6c2bd4bca4972a 4 | specs: 5 | appraisal (2.5.0) 6 | bundler 7 | rake 8 | thor (>= 0.14.0) 9 | 10 | PATH 11 | remote: .. 12 | specs: 13 | acts_as_tenant (1.0.1) 14 | rails (>= 6.0) 15 | 16 | GEM 17 | remote: https://rubygems.org/ 18 | specs: 19 | actioncable (6.1.7.6) 20 | actionpack (= 6.1.7.6) 21 | activesupport (= 6.1.7.6) 22 | nio4r (~> 2.0) 23 | websocket-driver (>= 0.6.1) 24 | actionmailbox (6.1.7.6) 25 | actionpack (= 6.1.7.6) 26 | activejob (= 6.1.7.6) 27 | activerecord (= 6.1.7.6) 28 | activestorage (= 6.1.7.6) 29 | activesupport (= 6.1.7.6) 30 | mail (>= 2.7.1) 31 | actionmailer (6.1.7.6) 32 | actionpack (= 6.1.7.6) 33 | actionview (= 6.1.7.6) 34 | activejob (= 6.1.7.6) 35 | activesupport (= 6.1.7.6) 36 | mail (~> 2.5, >= 2.5.4) 37 | rails-dom-testing (~> 2.0) 38 | actionpack (6.1.7.6) 39 | actionview (= 6.1.7.6) 40 | activesupport (= 6.1.7.6) 41 | rack (~> 2.0, >= 2.0.9) 42 | rack-test (>= 0.6.3) 43 | rails-dom-testing (~> 2.0) 44 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 45 | actiontext (6.1.7.6) 46 | actionpack (= 6.1.7.6) 47 | activerecord (= 6.1.7.6) 48 | activestorage (= 6.1.7.6) 49 | activesupport (= 6.1.7.6) 50 | nokogiri (>= 1.8.5) 51 | actionview (6.1.7.6) 52 | activesupport (= 6.1.7.6) 53 | builder (~> 3.1) 54 | erubi (~> 1.4) 55 | rails-dom-testing (~> 2.0) 56 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 57 | activejob (6.1.7.6) 58 | activesupport (= 6.1.7.6) 59 | globalid (>= 0.3.6) 60 | activemodel (6.1.7.6) 61 | activesupport (= 6.1.7.6) 62 | activerecord (6.1.7.6) 63 | activemodel (= 6.1.7.6) 64 | activesupport (= 6.1.7.6) 65 | activestorage (6.1.7.6) 66 | actionpack (= 6.1.7.6) 67 | activejob (= 6.1.7.6) 68 | activerecord (= 6.1.7.6) 69 | activesupport (= 6.1.7.6) 70 | marcel (~> 1.0) 71 | mini_mime (>= 1.1.0) 72 | activesupport (6.1.7.6) 73 | concurrent-ruby (~> 1.0, >= 1.0.2) 74 | i18n (>= 1.6, < 2) 75 | minitest (>= 5.1) 76 | tzinfo (~> 2.0) 77 | zeitwerk (~> 2.3) 78 | ast (2.4.2) 79 | builder (3.2.4) 80 | byebug (11.1.3) 81 | concurrent-ruby (1.2.2) 82 | connection_pool (2.4.1) 83 | crass (1.0.6) 84 | date (3.3.4) 85 | diff-lcs (1.5.0) 86 | erubi (1.12.0) 87 | globalid (1.2.1) 88 | activesupport (>= 6.1) 89 | i18n (1.14.1) 90 | concurrent-ruby (~> 1.0) 91 | json (2.7.1) 92 | language_server-protocol (3.17.0.3) 93 | lint_roller (1.1.0) 94 | loofah (2.22.0) 95 | crass (~> 1.0.2) 96 | nokogiri (>= 1.12.0) 97 | mail (2.8.1) 98 | mini_mime (>= 0.1.1) 99 | net-imap 100 | net-pop 101 | net-smtp 102 | marcel (1.0.2) 103 | method_source (1.0.0) 104 | mini_mime (1.1.5) 105 | minitest (5.20.0) 106 | net-imap (0.4.7) 107 | date 108 | net-protocol 109 | net-pop (0.1.2) 110 | net-protocol 111 | net-protocol (0.2.2) 112 | timeout 113 | net-smtp (0.4.0) 114 | net-protocol 115 | nio4r (2.7.0) 116 | nokogiri (1.15.5-arm64-darwin) 117 | racc (~> 1.4) 118 | nokogiri (1.15.5-x86_64-darwin) 119 | racc (~> 1.4) 120 | nokogiri (1.15.5-x86_64-linux) 121 | racc (~> 1.4) 122 | parallel (1.23.0) 123 | parser (3.2.2.4) 124 | ast (~> 2.4.1) 125 | racc 126 | racc (1.7.3) 127 | rack (2.2.8) 128 | rack-test (2.1.0) 129 | rack (>= 1.3) 130 | rails (6.1.7.6) 131 | actioncable (= 6.1.7.6) 132 | actionmailbox (= 6.1.7.6) 133 | actionmailer (= 6.1.7.6) 134 | actionpack (= 6.1.7.6) 135 | actiontext (= 6.1.7.6) 136 | actionview (= 6.1.7.6) 137 | activejob (= 6.1.7.6) 138 | activemodel (= 6.1.7.6) 139 | activerecord (= 6.1.7.6) 140 | activestorage (= 6.1.7.6) 141 | activesupport (= 6.1.7.6) 142 | bundler (>= 1.15.0) 143 | railties (= 6.1.7.6) 144 | sprockets-rails (>= 2.0.0) 145 | rails-dom-testing (2.2.0) 146 | activesupport (>= 5.0.0) 147 | minitest 148 | nokogiri (>= 1.6) 149 | rails-html-sanitizer (1.6.0) 150 | loofah (~> 2.21) 151 | nokogiri (~> 1.14) 152 | railties (6.1.7.6) 153 | actionpack (= 6.1.7.6) 154 | activesupport (= 6.1.7.6) 155 | method_source 156 | rake (>= 12.2) 157 | thor (~> 1.0) 158 | rainbow (3.1.1) 159 | rake (13.1.0) 160 | redis-client (0.18.0) 161 | connection_pool 162 | regexp_parser (2.8.3) 163 | rexml (3.2.6) 164 | rspec (3.12.0) 165 | rspec-core (~> 3.12.0) 166 | rspec-expectations (~> 3.12.0) 167 | rspec-mocks (~> 3.12.0) 168 | rspec-core (3.12.2) 169 | rspec-support (~> 3.12.0) 170 | rspec-expectations (3.12.3) 171 | diff-lcs (>= 1.2.0, < 2.0) 172 | rspec-support (~> 3.12.0) 173 | rspec-mocks (3.12.6) 174 | diff-lcs (>= 1.2.0, < 2.0) 175 | rspec-support (~> 3.12.0) 176 | rspec-rails (6.1.0) 177 | actionpack (>= 6.1) 178 | activesupport (>= 6.1) 179 | railties (>= 6.1) 180 | rspec-core (~> 3.12) 181 | rspec-expectations (~> 3.12) 182 | rspec-mocks (~> 3.12) 183 | rspec-support (~> 3.12) 184 | rspec-support (3.12.1) 185 | rubocop (1.57.2) 186 | json (~> 2.3) 187 | language_server-protocol (>= 3.17.0) 188 | parallel (~> 1.10) 189 | parser (>= 3.2.2.4) 190 | rainbow (>= 2.2.2, < 4.0) 191 | regexp_parser (>= 1.8, < 3.0) 192 | rexml (>= 3.2.5, < 4.0) 193 | rubocop-ast (>= 1.28.1, < 2.0) 194 | ruby-progressbar (~> 1.7) 195 | unicode-display_width (>= 2.4.0, < 3.0) 196 | rubocop-ast (1.30.0) 197 | parser (>= 3.2.1.0) 198 | rubocop-performance (1.19.1) 199 | rubocop (>= 1.7.0, < 2.0) 200 | rubocop-ast (>= 0.4.0) 201 | ruby-progressbar (1.13.0) 202 | sidekiq (7.2.0) 203 | concurrent-ruby (< 2) 204 | connection_pool (>= 2.3.0) 205 | rack (>= 2.2.4) 206 | redis-client (>= 0.14.0) 207 | sprockets (4.2.1) 208 | concurrent-ruby (~> 1.0) 209 | rack (>= 2.2.4, < 4) 210 | sprockets-rails (3.4.2) 211 | actionpack (>= 5.2) 212 | activesupport (>= 5.2) 213 | sprockets (>= 3.0.0) 214 | sqlite3 (1.6.9-arm64-darwin) 215 | sqlite3 (1.6.9-x86_64-darwin) 216 | sqlite3 (1.6.9-x86_64-linux) 217 | standard (1.32.1) 218 | language_server-protocol (~> 3.17.0.2) 219 | lint_roller (~> 1.0) 220 | rubocop (~> 1.57.2) 221 | standard-custom (~> 1.0.0) 222 | standard-performance (~> 1.2) 223 | standard-custom (1.0.2) 224 | lint_roller (~> 1.0) 225 | rubocop (~> 1.50) 226 | standard-performance (1.2.1) 227 | lint_roller (~> 1.1) 228 | rubocop-performance (~> 1.19.1) 229 | thor (1.3.0) 230 | timeout (0.4.1) 231 | tzinfo (2.0.6) 232 | concurrent-ruby (~> 1.0) 233 | unicode-display_width (2.5.0) 234 | websocket-driver (0.7.6) 235 | websocket-extensions (>= 0.1.0) 236 | websocket-extensions (0.1.5) 237 | zeitwerk (2.6.12) 238 | 239 | PLATFORMS 240 | arm64-darwin-22 241 | x86_64-darwin-20 242 | x86_64-darwin-21 243 | x86_64-darwin-22 244 | x86_64-linux 245 | 246 | DEPENDENCIES 247 | acts_as_tenant! 248 | appraisal! 249 | byebug 250 | rails (~> 6.1.0) 251 | rspec (>= 3.0) 252 | rspec-rails 253 | sidekiq (~> 7.0) 254 | sqlite3 255 | standard 256 | 257 | BUNDLED WITH 258 | 2.4.22 259 | -------------------------------------------------------------------------------- /gemfiles/rails_7.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rspec", ">=3.0" 6 | gem "rspec-rails" 7 | gem "sqlite3" 8 | gem "standard" 9 | gem "sidekiq", "~> 7.0" 10 | gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git" 11 | gem "byebug", group: [:development, :test] 12 | gem "rails", "~> 7.0.0" 13 | 14 | gemspec path: "../" 15 | -------------------------------------------------------------------------------- /gemfiles/rails_7.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/thoughtbot/appraisal.git 3 | revision: feb78bcc6177038399bff098cb6c2bd4bca4972a 4 | specs: 5 | appraisal (2.5.0) 6 | bundler 7 | rake 8 | thor (>= 0.14.0) 9 | 10 | PATH 11 | remote: .. 12 | specs: 13 | acts_as_tenant (1.0.1) 14 | rails (>= 6.0) 15 | 16 | GEM 17 | remote: https://rubygems.org/ 18 | specs: 19 | actioncable (7.0.8) 20 | actionpack (= 7.0.8) 21 | activesupport (= 7.0.8) 22 | nio4r (~> 2.0) 23 | websocket-driver (>= 0.6.1) 24 | actionmailbox (7.0.8) 25 | actionpack (= 7.0.8) 26 | activejob (= 7.0.8) 27 | activerecord (= 7.0.8) 28 | activestorage (= 7.0.8) 29 | activesupport (= 7.0.8) 30 | mail (>= 2.7.1) 31 | net-imap 32 | net-pop 33 | net-smtp 34 | actionmailer (7.0.8) 35 | actionpack (= 7.0.8) 36 | actionview (= 7.0.8) 37 | activejob (= 7.0.8) 38 | activesupport (= 7.0.8) 39 | mail (~> 2.5, >= 2.5.4) 40 | net-imap 41 | net-pop 42 | net-smtp 43 | rails-dom-testing (~> 2.0) 44 | actionpack (7.0.8) 45 | actionview (= 7.0.8) 46 | activesupport (= 7.0.8) 47 | rack (~> 2.0, >= 2.2.4) 48 | rack-test (>= 0.6.3) 49 | rails-dom-testing (~> 2.0) 50 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 51 | actiontext (7.0.8) 52 | actionpack (= 7.0.8) 53 | activerecord (= 7.0.8) 54 | activestorage (= 7.0.8) 55 | activesupport (= 7.0.8) 56 | globalid (>= 0.6.0) 57 | nokogiri (>= 1.8.5) 58 | actionview (7.0.8) 59 | activesupport (= 7.0.8) 60 | builder (~> 3.1) 61 | erubi (~> 1.4) 62 | rails-dom-testing (~> 2.0) 63 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 64 | activejob (7.0.8) 65 | activesupport (= 7.0.8) 66 | globalid (>= 0.3.6) 67 | activemodel (7.0.8) 68 | activesupport (= 7.0.8) 69 | activerecord (7.0.8) 70 | activemodel (= 7.0.8) 71 | activesupport (= 7.0.8) 72 | activestorage (7.0.8) 73 | actionpack (= 7.0.8) 74 | activejob (= 7.0.8) 75 | activerecord (= 7.0.8) 76 | activesupport (= 7.0.8) 77 | marcel (~> 1.0) 78 | mini_mime (>= 1.1.0) 79 | activesupport (7.0.8) 80 | concurrent-ruby (~> 1.0, >= 1.0.2) 81 | i18n (>= 1.6, < 2) 82 | minitest (>= 5.1) 83 | tzinfo (~> 2.0) 84 | ast (2.4.2) 85 | builder (3.2.4) 86 | byebug (11.1.3) 87 | concurrent-ruby (1.2.2) 88 | connection_pool (2.4.1) 89 | crass (1.0.6) 90 | date (3.3.4) 91 | diff-lcs (1.5.0) 92 | erubi (1.12.0) 93 | globalid (1.2.1) 94 | activesupport (>= 6.1) 95 | i18n (1.14.1) 96 | concurrent-ruby (~> 1.0) 97 | json (2.7.1) 98 | language_server-protocol (3.17.0.3) 99 | lint_roller (1.1.0) 100 | loofah (2.22.0) 101 | crass (~> 1.0.2) 102 | nokogiri (>= 1.12.0) 103 | mail (2.8.1) 104 | mini_mime (>= 0.1.1) 105 | net-imap 106 | net-pop 107 | net-smtp 108 | marcel (1.0.2) 109 | method_source (1.0.0) 110 | mini_mime (1.1.5) 111 | mini_portile2 (2.8.5) 112 | minitest (5.20.0) 113 | net-imap (0.4.7) 114 | date 115 | net-protocol 116 | net-pop (0.1.2) 117 | net-protocol 118 | net-protocol (0.2.2) 119 | timeout 120 | net-smtp (0.4.0) 121 | net-protocol 122 | nio4r (2.7.0) 123 | nokogiri (1.15.5) 124 | mini_portile2 (~> 2.8.2) 125 | racc (~> 1.4) 126 | nokogiri (1.15.5-x86_64-darwin) 127 | racc (~> 1.4) 128 | nokogiri (1.15.5-x86_64-linux) 129 | racc (~> 1.4) 130 | parallel (1.23.0) 131 | parser (3.2.2.4) 132 | ast (~> 2.4.1) 133 | racc 134 | racc (1.7.3) 135 | rack (2.2.8) 136 | rack-test (2.1.0) 137 | rack (>= 1.3) 138 | rails (7.0.8) 139 | actioncable (= 7.0.8) 140 | actionmailbox (= 7.0.8) 141 | actionmailer (= 7.0.8) 142 | actionpack (= 7.0.8) 143 | actiontext (= 7.0.8) 144 | actionview (= 7.0.8) 145 | activejob (= 7.0.8) 146 | activemodel (= 7.0.8) 147 | activerecord (= 7.0.8) 148 | activestorage (= 7.0.8) 149 | activesupport (= 7.0.8) 150 | bundler (>= 1.15.0) 151 | railties (= 7.0.8) 152 | rails-dom-testing (2.2.0) 153 | activesupport (>= 5.0.0) 154 | minitest 155 | nokogiri (>= 1.6) 156 | rails-html-sanitizer (1.6.0) 157 | loofah (~> 2.21) 158 | nokogiri (~> 1.14) 159 | railties (7.0.8) 160 | actionpack (= 7.0.8) 161 | activesupport (= 7.0.8) 162 | method_source 163 | rake (>= 12.2) 164 | thor (~> 1.0) 165 | zeitwerk (~> 2.5) 166 | rainbow (3.1.1) 167 | rake (13.1.0) 168 | redis-client (0.18.0) 169 | connection_pool 170 | regexp_parser (2.8.3) 171 | rexml (3.2.6) 172 | rspec (3.12.0) 173 | rspec-core (~> 3.12.0) 174 | rspec-expectations (~> 3.12.0) 175 | rspec-mocks (~> 3.12.0) 176 | rspec-core (3.12.2) 177 | rspec-support (~> 3.12.0) 178 | rspec-expectations (3.12.3) 179 | diff-lcs (>= 1.2.0, < 2.0) 180 | rspec-support (~> 3.12.0) 181 | rspec-mocks (3.12.6) 182 | diff-lcs (>= 1.2.0, < 2.0) 183 | rspec-support (~> 3.12.0) 184 | rspec-rails (6.1.0) 185 | actionpack (>= 6.1) 186 | activesupport (>= 6.1) 187 | railties (>= 6.1) 188 | rspec-core (~> 3.12) 189 | rspec-expectations (~> 3.12) 190 | rspec-mocks (~> 3.12) 191 | rspec-support (~> 3.12) 192 | rspec-support (3.12.1) 193 | rubocop (1.57.2) 194 | json (~> 2.3) 195 | language_server-protocol (>= 3.17.0) 196 | parallel (~> 1.10) 197 | parser (>= 3.2.2.4) 198 | rainbow (>= 2.2.2, < 4.0) 199 | regexp_parser (>= 1.8, < 3.0) 200 | rexml (>= 3.2.5, < 4.0) 201 | rubocop-ast (>= 1.28.1, < 2.0) 202 | ruby-progressbar (~> 1.7) 203 | unicode-display_width (>= 2.4.0, < 3.0) 204 | rubocop-ast (1.30.0) 205 | parser (>= 3.2.1.0) 206 | rubocop-performance (1.19.1) 207 | rubocop (>= 1.7.0, < 2.0) 208 | rubocop-ast (>= 0.4.0) 209 | ruby-progressbar (1.13.0) 210 | sidekiq (7.2.0) 211 | concurrent-ruby (< 2) 212 | connection_pool (>= 2.3.0) 213 | rack (>= 2.2.4) 214 | redis-client (>= 0.14.0) 215 | sqlite3 (1.6.9) 216 | mini_portile2 (~> 2.8.0) 217 | sqlite3 (1.6.9-x86_64-darwin) 218 | sqlite3 (1.6.9-x86_64-linux) 219 | standard (1.32.1) 220 | language_server-protocol (~> 3.17.0.2) 221 | lint_roller (~> 1.0) 222 | rubocop (~> 1.57.2) 223 | standard-custom (~> 1.0.0) 224 | standard-performance (~> 1.2) 225 | standard-custom (1.0.2) 226 | lint_roller (~> 1.0) 227 | rubocop (~> 1.50) 228 | standard-performance (1.2.1) 229 | lint_roller (~> 1.1) 230 | rubocop-performance (~> 1.19.1) 231 | thor (1.3.0) 232 | timeout (0.4.1) 233 | tzinfo (2.0.6) 234 | concurrent-ruby (~> 1.0) 235 | unicode-display_width (2.5.0) 236 | websocket-driver (0.7.6) 237 | websocket-extensions (>= 0.1.0) 238 | websocket-extensions (0.1.5) 239 | zeitwerk (2.6.12) 240 | 241 | PLATFORMS 242 | ruby 243 | x86_64-darwin-21 244 | x86_64-linux 245 | 246 | DEPENDENCIES 247 | acts_as_tenant! 248 | appraisal! 249 | byebug 250 | rails (~> 7.0.0) 251 | rspec (>= 3.0) 252 | rspec-rails 253 | sidekiq (~> 7.0) 254 | sqlite3 255 | standard 256 | 257 | BUNDLED WITH 258 | 2.4.22 259 | -------------------------------------------------------------------------------- /gemfiles/rails_7_1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rspec", ">=3.0" 6 | gem "rspec-rails" 7 | gem "sqlite3" 8 | gem "standard" 9 | gem "sidekiq", "~> 7.0" 10 | gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git" 11 | gem "byebug", group: [:development, :test] 12 | gem "rails", "~> 7.1.0" 13 | 14 | gemspec path: "../" 15 | -------------------------------------------------------------------------------- /gemfiles/rails_7_1.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/thoughtbot/appraisal.git 3 | revision: feb78bcc6177038399bff098cb6c2bd4bca4972a 4 | specs: 5 | appraisal (2.5.0) 6 | bundler 7 | rake 8 | thor (>= 0.14.0) 9 | 10 | PATH 11 | remote: .. 12 | specs: 13 | acts_as_tenant (1.0.1) 14 | rails (>= 6.0) 15 | 16 | GEM 17 | remote: https://rubygems.org/ 18 | specs: 19 | actioncable (7.1.2) 20 | actionpack (= 7.1.2) 21 | activesupport (= 7.1.2) 22 | nio4r (~> 2.0) 23 | websocket-driver (>= 0.6.1) 24 | zeitwerk (~> 2.6) 25 | actionmailbox (7.1.2) 26 | actionpack (= 7.1.2) 27 | activejob (= 7.1.2) 28 | activerecord (= 7.1.2) 29 | activestorage (= 7.1.2) 30 | activesupport (= 7.1.2) 31 | mail (>= 2.7.1) 32 | net-imap 33 | net-pop 34 | net-smtp 35 | actionmailer (7.1.2) 36 | actionpack (= 7.1.2) 37 | actionview (= 7.1.2) 38 | activejob (= 7.1.2) 39 | activesupport (= 7.1.2) 40 | mail (~> 2.5, >= 2.5.4) 41 | net-imap 42 | net-pop 43 | net-smtp 44 | rails-dom-testing (~> 2.2) 45 | actionpack (7.1.2) 46 | actionview (= 7.1.2) 47 | activesupport (= 7.1.2) 48 | nokogiri (>= 1.8.5) 49 | racc 50 | rack (>= 2.2.4) 51 | rack-session (>= 1.0.1) 52 | rack-test (>= 0.6.3) 53 | rails-dom-testing (~> 2.2) 54 | rails-html-sanitizer (~> 1.6) 55 | actiontext (7.1.2) 56 | actionpack (= 7.1.2) 57 | activerecord (= 7.1.2) 58 | activestorage (= 7.1.2) 59 | activesupport (= 7.1.2) 60 | globalid (>= 0.6.0) 61 | nokogiri (>= 1.8.5) 62 | actionview (7.1.2) 63 | activesupport (= 7.1.2) 64 | builder (~> 3.1) 65 | erubi (~> 1.11) 66 | rails-dom-testing (~> 2.2) 67 | rails-html-sanitizer (~> 1.6) 68 | activejob (7.1.2) 69 | activesupport (= 7.1.2) 70 | globalid (>= 0.3.6) 71 | activemodel (7.1.2) 72 | activesupport (= 7.1.2) 73 | activerecord (7.1.2) 74 | activemodel (= 7.1.2) 75 | activesupport (= 7.1.2) 76 | timeout (>= 0.4.0) 77 | activestorage (7.1.2) 78 | actionpack (= 7.1.2) 79 | activejob (= 7.1.2) 80 | activerecord (= 7.1.2) 81 | activesupport (= 7.1.2) 82 | marcel (~> 1.0) 83 | activesupport (7.1.2) 84 | base64 85 | bigdecimal 86 | concurrent-ruby (~> 1.0, >= 1.0.2) 87 | connection_pool (>= 2.2.5) 88 | drb 89 | i18n (>= 1.6, < 2) 90 | minitest (>= 5.1) 91 | mutex_m 92 | tzinfo (~> 2.0) 93 | ast (2.4.2) 94 | base64 (0.2.0) 95 | bigdecimal (3.1.4) 96 | builder (3.2.4) 97 | byebug (11.1.3) 98 | concurrent-ruby (1.2.2) 99 | connection_pool (2.4.1) 100 | crass (1.0.6) 101 | date (3.3.4) 102 | diff-lcs (1.5.0) 103 | drb (2.2.0) 104 | ruby2_keywords 105 | erubi (1.12.0) 106 | globalid (1.2.1) 107 | activesupport (>= 6.1) 108 | i18n (1.14.1) 109 | concurrent-ruby (~> 1.0) 110 | io-console (0.6.0) 111 | irb (1.10.1) 112 | rdoc 113 | reline (>= 0.3.8) 114 | json (2.7.1) 115 | language_server-protocol (3.17.0.3) 116 | lint_roller (1.1.0) 117 | loofah (2.22.0) 118 | crass (~> 1.0.2) 119 | nokogiri (>= 1.12.0) 120 | mail (2.8.1) 121 | mini_mime (>= 0.1.1) 122 | net-imap 123 | net-pop 124 | net-smtp 125 | marcel (1.0.2) 126 | mini_mime (1.1.5) 127 | minitest (5.20.0) 128 | mutex_m (0.2.0) 129 | net-imap (0.4.7) 130 | date 131 | net-protocol 132 | net-pop (0.1.2) 133 | net-protocol 134 | net-protocol (0.2.2) 135 | timeout 136 | net-smtp (0.4.0) 137 | net-protocol 138 | nio4r (2.7.0) 139 | nokogiri (1.15.5-arm64-darwin) 140 | racc (~> 1.4) 141 | nokogiri (1.15.5-x86_64-darwin) 142 | racc (~> 1.4) 143 | nokogiri (1.15.5-x86_64-linux) 144 | racc (~> 1.4) 145 | parallel (1.23.0) 146 | parser (3.2.2.4) 147 | ast (~> 2.4.1) 148 | racc 149 | psych (5.1.1.1) 150 | stringio 151 | racc (1.7.3) 152 | rack (3.0.8) 153 | rack-session (2.0.0) 154 | rack (>= 3.0.0) 155 | rack-test (2.1.0) 156 | rack (>= 1.3) 157 | rackup (2.1.0) 158 | rack (>= 3) 159 | webrick (~> 1.8) 160 | rails (7.1.2) 161 | actioncable (= 7.1.2) 162 | actionmailbox (= 7.1.2) 163 | actionmailer (= 7.1.2) 164 | actionpack (= 7.1.2) 165 | actiontext (= 7.1.2) 166 | actionview (= 7.1.2) 167 | activejob (= 7.1.2) 168 | activemodel (= 7.1.2) 169 | activerecord (= 7.1.2) 170 | activestorage (= 7.1.2) 171 | activesupport (= 7.1.2) 172 | bundler (>= 1.15.0) 173 | railties (= 7.1.2) 174 | rails-dom-testing (2.2.0) 175 | activesupport (>= 5.0.0) 176 | minitest 177 | nokogiri (>= 1.6) 178 | rails-html-sanitizer (1.6.0) 179 | loofah (~> 2.21) 180 | nokogiri (~> 1.14) 181 | railties (7.1.2) 182 | actionpack (= 7.1.2) 183 | activesupport (= 7.1.2) 184 | irb 185 | rackup (>= 1.0.0) 186 | rake (>= 12.2) 187 | thor (~> 1.0, >= 1.2.2) 188 | zeitwerk (~> 2.6) 189 | rainbow (3.1.1) 190 | rake (13.1.0) 191 | rdoc (6.6.1) 192 | psych (>= 4.0.0) 193 | redis-client (0.18.0) 194 | connection_pool 195 | regexp_parser (2.8.3) 196 | reline (0.4.1) 197 | io-console (~> 0.5) 198 | rexml (3.2.6) 199 | rspec (3.12.0) 200 | rspec-core (~> 3.12.0) 201 | rspec-expectations (~> 3.12.0) 202 | rspec-mocks (~> 3.12.0) 203 | rspec-core (3.12.2) 204 | rspec-support (~> 3.12.0) 205 | rspec-expectations (3.12.3) 206 | diff-lcs (>= 1.2.0, < 2.0) 207 | rspec-support (~> 3.12.0) 208 | rspec-mocks (3.12.6) 209 | diff-lcs (>= 1.2.0, < 2.0) 210 | rspec-support (~> 3.12.0) 211 | rspec-rails (6.1.0) 212 | actionpack (>= 6.1) 213 | activesupport (>= 6.1) 214 | railties (>= 6.1) 215 | rspec-core (~> 3.12) 216 | rspec-expectations (~> 3.12) 217 | rspec-mocks (~> 3.12) 218 | rspec-support (~> 3.12) 219 | rspec-support (3.12.1) 220 | rubocop (1.57.2) 221 | json (~> 2.3) 222 | language_server-protocol (>= 3.17.0) 223 | parallel (~> 1.10) 224 | parser (>= 3.2.2.4) 225 | rainbow (>= 2.2.2, < 4.0) 226 | regexp_parser (>= 1.8, < 3.0) 227 | rexml (>= 3.2.5, < 4.0) 228 | rubocop-ast (>= 1.28.1, < 2.0) 229 | ruby-progressbar (~> 1.7) 230 | unicode-display_width (>= 2.4.0, < 3.0) 231 | rubocop-ast (1.30.0) 232 | parser (>= 3.2.1.0) 233 | rubocop-performance (1.19.1) 234 | rubocop (>= 1.7.0, < 2.0) 235 | rubocop-ast (>= 0.4.0) 236 | ruby-progressbar (1.13.0) 237 | ruby2_keywords (0.0.5) 238 | sidekiq (7.2.0) 239 | concurrent-ruby (< 2) 240 | connection_pool (>= 2.3.0) 241 | rack (>= 2.2.4) 242 | redis-client (>= 0.14.0) 243 | sqlite3 (1.6.9-arm64-darwin) 244 | sqlite3 (1.6.9-x86_64-darwin) 245 | sqlite3 (1.6.9-x86_64-linux) 246 | standard (1.32.1) 247 | language_server-protocol (~> 3.17.0.2) 248 | lint_roller (~> 1.0) 249 | rubocop (~> 1.57.2) 250 | standard-custom (~> 1.0.0) 251 | standard-performance (~> 1.2) 252 | standard-custom (1.0.2) 253 | lint_roller (~> 1.0) 254 | rubocop (~> 1.50) 255 | standard-performance (1.2.1) 256 | lint_roller (~> 1.1) 257 | rubocop-performance (~> 1.19.1) 258 | stringio (3.1.0) 259 | thor (1.3.0) 260 | timeout (0.4.1) 261 | tzinfo (2.0.6) 262 | concurrent-ruby (~> 1.0) 263 | unicode-display_width (2.5.0) 264 | webrick (1.8.1) 265 | websocket-driver (0.7.6) 266 | websocket-extensions (>= 0.1.0) 267 | websocket-extensions (0.1.5) 268 | zeitwerk (2.6.12) 269 | 270 | PLATFORMS 271 | arm64-darwin-22 272 | x86_64-darwin-22 273 | x86_64-linux 274 | 275 | DEPENDENCIES 276 | acts_as_tenant! 277 | appraisal! 278 | byebug 279 | rails (~> 7.1.0) 280 | rspec (>= 3.0) 281 | rspec-rails 282 | sidekiq (~> 7.0) 283 | sqlite3 284 | standard 285 | 286 | BUNDLED WITH 287 | 2.4.22 288 | -------------------------------------------------------------------------------- /gemfiles/rails_main.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rspec", git: "https://github.com/rspec/rspec.git", branch: "main" 6 | gem "rspec-rails", git: "https://github.com/rspec/rspec-rails.git", branch: "main" 7 | gem "sqlite3" 8 | gem "standard" 9 | gem "sidekiq", "~> 7.0" 10 | gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git" 11 | gem "byebug", group: [:development, :test] 12 | gem "rails", branch: :main, git: "https://github.com/rails/rails.git" 13 | gem "rspec-core", git: "https://github.com/rspec/rspec-core.git", branch: "main" 14 | gem "rspec-expectations", git: "https://github.com/rspec/rspec-expectations.git", branch: "main" 15 | gem "rspec-mocks", git: "https://github.com/rspec/rspec-mocks.git", branch: "main" 16 | gem "rspec-support", git: "https://github.com/rspec/rspec-support.git", branch: "main" 17 | 18 | gemspec path: "../" 19 | -------------------------------------------------------------------------------- /gemfiles/rails_main.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/rails/rails.git 3 | revision: c1489a8ca4a9b91e21725f6a139e82e803c9eaa6 4 | branch: main 5 | specs: 6 | actioncable (7.2.0.alpha) 7 | actionpack (= 7.2.0.alpha) 8 | activesupport (= 7.2.0.alpha) 9 | nio4r (~> 2.0) 10 | websocket-driver (>= 0.6.1) 11 | zeitwerk (~> 2.6) 12 | actionmailbox (7.2.0.alpha) 13 | actionpack (= 7.2.0.alpha) 14 | activejob (= 7.2.0.alpha) 15 | activerecord (= 7.2.0.alpha) 16 | activestorage (= 7.2.0.alpha) 17 | activesupport (= 7.2.0.alpha) 18 | mail (>= 2.7.1) 19 | net-imap 20 | net-pop 21 | net-smtp 22 | actionmailer (7.2.0.alpha) 23 | actionpack (= 7.2.0.alpha) 24 | actionview (= 7.2.0.alpha) 25 | activejob (= 7.2.0.alpha) 26 | activesupport (= 7.2.0.alpha) 27 | mail (~> 2.5, >= 2.5.4) 28 | net-imap 29 | net-pop 30 | net-smtp 31 | rails-dom-testing (~> 2.2) 32 | actionpack (7.2.0.alpha) 33 | actionview (= 7.2.0.alpha) 34 | activesupport (= 7.2.0.alpha) 35 | nokogiri (>= 1.8.5) 36 | racc 37 | rack (>= 2.2.4) 38 | rack-session (>= 1.0.1) 39 | rack-test (>= 0.6.3) 40 | rails-dom-testing (~> 2.2) 41 | rails-html-sanitizer (~> 1.6) 42 | actiontext (7.2.0.alpha) 43 | actionpack (= 7.2.0.alpha) 44 | activerecord (= 7.2.0.alpha) 45 | activestorage (= 7.2.0.alpha) 46 | activesupport (= 7.2.0.alpha) 47 | globalid (>= 0.6.0) 48 | nokogiri (>= 1.8.5) 49 | actionview (7.2.0.alpha) 50 | activesupport (= 7.2.0.alpha) 51 | builder (~> 3.1) 52 | erubi (~> 1.11) 53 | rails-dom-testing (~> 2.2) 54 | rails-html-sanitizer (~> 1.6) 55 | activejob (7.2.0.alpha) 56 | activesupport (= 7.2.0.alpha) 57 | globalid (>= 0.3.6) 58 | activemodel (7.2.0.alpha) 59 | activesupport (= 7.2.0.alpha) 60 | activerecord (7.2.0.alpha) 61 | activemodel (= 7.2.0.alpha) 62 | activesupport (= 7.2.0.alpha) 63 | timeout (>= 0.4.0) 64 | activestorage (7.2.0.alpha) 65 | actionpack (= 7.2.0.alpha) 66 | activejob (= 7.2.0.alpha) 67 | activerecord (= 7.2.0.alpha) 68 | activesupport (= 7.2.0.alpha) 69 | marcel (~> 1.0) 70 | activesupport (7.2.0.alpha) 71 | base64 72 | bigdecimal 73 | concurrent-ruby (~> 1.0, >= 1.0.2) 74 | connection_pool (>= 2.2.5) 75 | drb 76 | i18n (>= 1.6, < 2) 77 | minitest (>= 5.1) 78 | tzinfo (~> 2.0, >= 2.0.5) 79 | rails (7.2.0.alpha) 80 | actioncable (= 7.2.0.alpha) 81 | actionmailbox (= 7.2.0.alpha) 82 | actionmailer (= 7.2.0.alpha) 83 | actionpack (= 7.2.0.alpha) 84 | actiontext (= 7.2.0.alpha) 85 | actionview (= 7.2.0.alpha) 86 | activejob (= 7.2.0.alpha) 87 | activemodel (= 7.2.0.alpha) 88 | activerecord (= 7.2.0.alpha) 89 | activestorage (= 7.2.0.alpha) 90 | activesupport (= 7.2.0.alpha) 91 | bundler (>= 1.15.0) 92 | railties (= 7.2.0.alpha) 93 | railties (7.2.0.alpha) 94 | actionpack (= 7.2.0.alpha) 95 | activesupport (= 7.2.0.alpha) 96 | irb 97 | rackup (>= 1.0.0) 98 | rake (>= 12.2) 99 | thor (~> 1.0, >= 1.2.2) 100 | zeitwerk (~> 2.6) 101 | 102 | GIT 103 | remote: https://github.com/rspec/rspec-core.git 104 | revision: f273314f575ab62092b2ad86addb6a3c93d6041f 105 | branch: main 106 | specs: 107 | rspec-core (3.13.0.pre) 108 | rspec-support (= 3.13.0.pre) 109 | 110 | GIT 111 | remote: https://github.com/rspec/rspec-expectations.git 112 | revision: 2e8e800a0d8b64e7168dd625efe8f7526b7f1262 113 | branch: main 114 | specs: 115 | rspec-expectations (3.13.0.pre) 116 | diff-lcs (>= 1.2.0, < 2.0) 117 | rspec-support (= 3.13.0.pre) 118 | 119 | GIT 120 | remote: https://github.com/rspec/rspec-mocks.git 121 | revision: 868bf98d2aae5f0db15441a41b86b5f9346a12dd 122 | branch: main 123 | specs: 124 | rspec-mocks (3.13.0.pre) 125 | diff-lcs (>= 1.2.0, < 2.0) 126 | rspec-support (= 3.13.0.pre) 127 | 128 | GIT 129 | remote: https://github.com/rspec/rspec-rails.git 130 | revision: d8d1e5ab49fc997dacc503888fe4f459f47649e0 131 | branch: main 132 | specs: 133 | rspec-rails (6.2.0.pre) 134 | actionpack (>= 6.1) 135 | activesupport (>= 6.1) 136 | railties (>= 6.1) 137 | rspec-core (= 3.13.0.pre) 138 | rspec-expectations (= 3.13.0.pre) 139 | rspec-mocks (= 3.13.0.pre) 140 | rspec-support (= 3.13.0.pre) 141 | 142 | GIT 143 | remote: https://github.com/rspec/rspec-support.git 144 | revision: 48c227f93d7b69ce28babb2dce0a9d08ac4d2654 145 | branch: main 146 | specs: 147 | rspec-support (3.13.0.pre) 148 | 149 | GIT 150 | remote: https://github.com/rspec/rspec.git 151 | revision: cfb135a48b343fa75f28841f14874ea05e8e195f 152 | branch: main 153 | specs: 154 | rspec (3.13.0.pre) 155 | rspec-core (= 3.13.0.pre) 156 | rspec-expectations (= 3.13.0.pre) 157 | rspec-mocks (= 3.13.0.pre) 158 | 159 | GIT 160 | remote: https://github.com/thoughtbot/appraisal.git 161 | revision: feb78bcc6177038399bff098cb6c2bd4bca4972a 162 | specs: 163 | appraisal (2.5.0) 164 | bundler 165 | rake 166 | thor (>= 0.14.0) 167 | 168 | PATH 169 | remote: .. 170 | specs: 171 | acts_as_tenant (1.0.1) 172 | rails (>= 6.0) 173 | 174 | GEM 175 | remote: https://rubygems.org/ 176 | specs: 177 | ast (2.4.2) 178 | base64 (0.2.0) 179 | bigdecimal (3.1.4) 180 | builder (3.2.4) 181 | byebug (11.1.3) 182 | concurrent-ruby (1.2.2) 183 | connection_pool (2.4.1) 184 | crass (1.0.6) 185 | date (3.3.4) 186 | diff-lcs (1.5.0) 187 | drb (2.2.0) 188 | ruby2_keywords 189 | erubi (1.12.0) 190 | globalid (1.2.1) 191 | activesupport (>= 6.1) 192 | i18n (1.14.1) 193 | concurrent-ruby (~> 1.0) 194 | io-console (0.6.0) 195 | irb (1.10.1) 196 | rdoc 197 | reline (>= 0.3.8) 198 | json (2.7.1) 199 | language_server-protocol (3.17.0.3) 200 | lint_roller (1.1.0) 201 | loofah (2.22.0) 202 | crass (~> 1.0.2) 203 | nokogiri (>= 1.12.0) 204 | mail (2.8.1) 205 | mini_mime (>= 0.1.1) 206 | net-imap 207 | net-pop 208 | net-smtp 209 | marcel (1.0.2) 210 | mini_mime (1.1.5) 211 | mini_portile2 (2.8.5) 212 | minitest (5.20.0) 213 | net-imap (0.4.7) 214 | date 215 | net-protocol 216 | net-pop (0.1.2) 217 | net-protocol 218 | net-protocol (0.2.2) 219 | timeout 220 | net-smtp (0.4.0) 221 | net-protocol 222 | nio4r (2.7.0) 223 | nokogiri (1.15.5) 224 | mini_portile2 (~> 2.8.2) 225 | racc (~> 1.4) 226 | nokogiri (1.15.5-x86_64-darwin) 227 | racc (~> 1.4) 228 | nokogiri (1.15.5-x86_64-linux) 229 | racc (~> 1.4) 230 | parallel (1.23.0) 231 | parser (3.2.2.4) 232 | ast (~> 2.4.1) 233 | racc 234 | psych (5.1.1.1) 235 | stringio 236 | racc (1.7.3) 237 | rack (3.0.8) 238 | rack-session (2.0.0) 239 | rack (>= 3.0.0) 240 | rack-test (2.1.0) 241 | rack (>= 1.3) 242 | rackup (2.1.0) 243 | rack (>= 3) 244 | webrick (~> 1.8) 245 | rails-dom-testing (2.2.0) 246 | activesupport (>= 5.0.0) 247 | minitest 248 | nokogiri (>= 1.6) 249 | rails-html-sanitizer (1.6.0) 250 | loofah (~> 2.21) 251 | nokogiri (~> 1.14) 252 | rainbow (3.1.1) 253 | rake (13.1.0) 254 | rdoc (6.6.1) 255 | psych (>= 4.0.0) 256 | redis-client (0.18.0) 257 | connection_pool 258 | regexp_parser (2.8.3) 259 | reline (0.4.1) 260 | io-console (~> 0.5) 261 | rexml (3.2.6) 262 | rubocop (1.57.2) 263 | json (~> 2.3) 264 | language_server-protocol (>= 3.17.0) 265 | parallel (~> 1.10) 266 | parser (>= 3.2.2.4) 267 | rainbow (>= 2.2.2, < 4.0) 268 | regexp_parser (>= 1.8, < 3.0) 269 | rexml (>= 3.2.5, < 4.0) 270 | rubocop-ast (>= 1.28.1, < 2.0) 271 | ruby-progressbar (~> 1.7) 272 | unicode-display_width (>= 2.4.0, < 3.0) 273 | rubocop-ast (1.30.0) 274 | parser (>= 3.2.1.0) 275 | rubocop-performance (1.19.1) 276 | rubocop (>= 1.7.0, < 2.0) 277 | rubocop-ast (>= 0.4.0) 278 | ruby-progressbar (1.13.0) 279 | ruby2_keywords (0.0.5) 280 | sidekiq (7.2.0) 281 | concurrent-ruby (< 2) 282 | connection_pool (>= 2.3.0) 283 | rack (>= 2.2.4) 284 | redis-client (>= 0.14.0) 285 | sqlite3 (1.6.9) 286 | mini_portile2 (~> 2.8.0) 287 | sqlite3 (1.6.9-x86_64-darwin) 288 | sqlite3 (1.6.9-x86_64-linux) 289 | standard (1.32.1) 290 | language_server-protocol (~> 3.17.0.2) 291 | lint_roller (~> 1.0) 292 | rubocop (~> 1.57.2) 293 | standard-custom (~> 1.0.0) 294 | standard-performance (~> 1.2) 295 | standard-custom (1.0.2) 296 | lint_roller (~> 1.0) 297 | rubocop (~> 1.50) 298 | standard-performance (1.2.1) 299 | lint_roller (~> 1.1) 300 | rubocop-performance (~> 1.19.1) 301 | stringio (3.1.0) 302 | thor (1.3.0) 303 | timeout (0.4.1) 304 | tzinfo (2.0.6) 305 | concurrent-ruby (~> 1.0) 306 | unicode-display_width (2.5.0) 307 | webrick (1.8.1) 308 | websocket-driver (0.7.6) 309 | websocket-extensions (>= 0.1.0) 310 | websocket-extensions (0.1.5) 311 | zeitwerk (2.6.12) 312 | 313 | PLATFORMS 314 | ruby 315 | x86_64-darwin-20 316 | x86_64-linux 317 | 318 | DEPENDENCIES 319 | acts_as_tenant! 320 | appraisal! 321 | byebug 322 | rails! 323 | rspec! 324 | rspec-core! 325 | rspec-expectations! 326 | rspec-mocks! 327 | rspec-rails! 328 | rspec-support! 329 | sidekiq (~> 7.0) 330 | sqlite3 331 | standard 332 | 333 | BUNDLED WITH 334 | 2.4.22 335 | -------------------------------------------------------------------------------- /gemfiles/sidekiq_6.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rspec", ">=3.0" 6 | gem "rspec-rails" 7 | gem "sqlite3" 8 | gem "standard" 9 | gem "sidekiq", "~> 6.0" 10 | gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git" 11 | gem "byebug", group: [:development, :test] 12 | 13 | gemspec path: "../" 14 | -------------------------------------------------------------------------------- /gemfiles/sidekiq_6.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/thoughtbot/appraisal.git 3 | revision: feb78bcc6177038399bff098cb6c2bd4bca4972a 4 | specs: 5 | appraisal (2.5.0) 6 | bundler 7 | rake 8 | thor (>= 0.14.0) 9 | 10 | PATH 11 | remote: .. 12 | specs: 13 | acts_as_tenant (1.0.1) 14 | rails (>= 6.0) 15 | 16 | GEM 17 | remote: https://rubygems.org/ 18 | specs: 19 | actioncable (7.1.2) 20 | actionpack (= 7.1.2) 21 | activesupport (= 7.1.2) 22 | nio4r (~> 2.0) 23 | websocket-driver (>= 0.6.1) 24 | zeitwerk (~> 2.6) 25 | actionmailbox (7.1.2) 26 | actionpack (= 7.1.2) 27 | activejob (= 7.1.2) 28 | activerecord (= 7.1.2) 29 | activestorage (= 7.1.2) 30 | activesupport (= 7.1.2) 31 | mail (>= 2.7.1) 32 | net-imap 33 | net-pop 34 | net-smtp 35 | actionmailer (7.1.2) 36 | actionpack (= 7.1.2) 37 | actionview (= 7.1.2) 38 | activejob (= 7.1.2) 39 | activesupport (= 7.1.2) 40 | mail (~> 2.5, >= 2.5.4) 41 | net-imap 42 | net-pop 43 | net-smtp 44 | rails-dom-testing (~> 2.2) 45 | actionpack (7.1.2) 46 | actionview (= 7.1.2) 47 | activesupport (= 7.1.2) 48 | nokogiri (>= 1.8.5) 49 | racc 50 | rack (>= 2.2.4) 51 | rack-session (>= 1.0.1) 52 | rack-test (>= 0.6.3) 53 | rails-dom-testing (~> 2.2) 54 | rails-html-sanitizer (~> 1.6) 55 | actiontext (7.1.2) 56 | actionpack (= 7.1.2) 57 | activerecord (= 7.1.2) 58 | activestorage (= 7.1.2) 59 | activesupport (= 7.1.2) 60 | globalid (>= 0.6.0) 61 | nokogiri (>= 1.8.5) 62 | actionview (7.1.2) 63 | activesupport (= 7.1.2) 64 | builder (~> 3.1) 65 | erubi (~> 1.11) 66 | rails-dom-testing (~> 2.2) 67 | rails-html-sanitizer (~> 1.6) 68 | activejob (7.1.2) 69 | activesupport (= 7.1.2) 70 | globalid (>= 0.3.6) 71 | activemodel (7.1.2) 72 | activesupport (= 7.1.2) 73 | activerecord (7.1.2) 74 | activemodel (= 7.1.2) 75 | activesupport (= 7.1.2) 76 | timeout (>= 0.4.0) 77 | activestorage (7.1.2) 78 | actionpack (= 7.1.2) 79 | activejob (= 7.1.2) 80 | activerecord (= 7.1.2) 81 | activesupport (= 7.1.2) 82 | marcel (~> 1.0) 83 | activesupport (7.1.2) 84 | base64 85 | bigdecimal 86 | concurrent-ruby (~> 1.0, >= 1.0.2) 87 | connection_pool (>= 2.2.5) 88 | drb 89 | i18n (>= 1.6, < 2) 90 | minitest (>= 5.1) 91 | mutex_m 92 | tzinfo (~> 2.0) 93 | ast (2.4.2) 94 | base64 (0.2.0) 95 | bigdecimal (3.1.4) 96 | builder (3.2.4) 97 | byebug (11.1.3) 98 | concurrent-ruby (1.2.2) 99 | connection_pool (2.4.1) 100 | crass (1.0.6) 101 | date (3.3.4) 102 | diff-lcs (1.5.0) 103 | drb (2.2.0) 104 | ruby2_keywords 105 | erubi (1.12.0) 106 | globalid (1.2.1) 107 | activesupport (>= 6.1) 108 | i18n (1.14.1) 109 | concurrent-ruby (~> 1.0) 110 | io-console (0.6.0) 111 | irb (1.10.1) 112 | rdoc 113 | reline (>= 0.3.8) 114 | json (2.7.1) 115 | language_server-protocol (3.17.0.3) 116 | lint_roller (1.1.0) 117 | loofah (2.22.0) 118 | crass (~> 1.0.2) 119 | nokogiri (>= 1.12.0) 120 | mail (2.8.1) 121 | mini_mime (>= 0.1.1) 122 | net-imap 123 | net-pop 124 | net-smtp 125 | marcel (1.0.2) 126 | mini_mime (1.1.5) 127 | minitest (5.20.0) 128 | mutex_m (0.2.0) 129 | net-imap (0.4.7) 130 | date 131 | net-protocol 132 | net-pop (0.1.2) 133 | net-protocol 134 | net-protocol (0.2.2) 135 | timeout 136 | net-smtp (0.4.0) 137 | net-protocol 138 | nio4r (2.7.0) 139 | nokogiri (1.15.5-arm64-darwin) 140 | racc (~> 1.4) 141 | nokogiri (1.15.5-x86_64-darwin) 142 | racc (~> 1.4) 143 | nokogiri (1.15.5-x86_64-linux) 144 | racc (~> 1.4) 145 | parallel (1.23.0) 146 | parser (3.2.2.4) 147 | ast (~> 2.4.1) 148 | racc 149 | psych (5.1.1.1) 150 | stringio 151 | racc (1.7.3) 152 | rack (2.2.8) 153 | rack-session (1.0.2) 154 | rack (< 3) 155 | rack-test (2.1.0) 156 | rack (>= 1.3) 157 | rackup (1.0.0) 158 | rack (< 3) 159 | webrick 160 | rails (7.1.2) 161 | actioncable (= 7.1.2) 162 | actionmailbox (= 7.1.2) 163 | actionmailer (= 7.1.2) 164 | actionpack (= 7.1.2) 165 | actiontext (= 7.1.2) 166 | actionview (= 7.1.2) 167 | activejob (= 7.1.2) 168 | activemodel (= 7.1.2) 169 | activerecord (= 7.1.2) 170 | activestorage (= 7.1.2) 171 | activesupport (= 7.1.2) 172 | bundler (>= 1.15.0) 173 | railties (= 7.1.2) 174 | rails-dom-testing (2.2.0) 175 | activesupport (>= 5.0.0) 176 | minitest 177 | nokogiri (>= 1.6) 178 | rails-html-sanitizer (1.6.0) 179 | loofah (~> 2.21) 180 | nokogiri (~> 1.14) 181 | railties (7.1.2) 182 | actionpack (= 7.1.2) 183 | activesupport (= 7.1.2) 184 | irb 185 | rackup (>= 1.0.0) 186 | rake (>= 12.2) 187 | thor (~> 1.0, >= 1.2.2) 188 | zeitwerk (~> 2.6) 189 | rainbow (3.1.1) 190 | rake (13.1.0) 191 | rdoc (6.6.1) 192 | psych (>= 4.0.0) 193 | redis (4.8.1) 194 | regexp_parser (2.8.3) 195 | reline (0.4.1) 196 | io-console (~> 0.5) 197 | rexml (3.2.6) 198 | rspec (3.12.0) 199 | rspec-core (~> 3.12.0) 200 | rspec-expectations (~> 3.12.0) 201 | rspec-mocks (~> 3.12.0) 202 | rspec-core (3.12.2) 203 | rspec-support (~> 3.12.0) 204 | rspec-expectations (3.12.3) 205 | diff-lcs (>= 1.2.0, < 2.0) 206 | rspec-support (~> 3.12.0) 207 | rspec-mocks (3.12.6) 208 | diff-lcs (>= 1.2.0, < 2.0) 209 | rspec-support (~> 3.12.0) 210 | rspec-rails (6.1.0) 211 | actionpack (>= 6.1) 212 | activesupport (>= 6.1) 213 | railties (>= 6.1) 214 | rspec-core (~> 3.12) 215 | rspec-expectations (~> 3.12) 216 | rspec-mocks (~> 3.12) 217 | rspec-support (~> 3.12) 218 | rspec-support (3.12.1) 219 | rubocop (1.57.2) 220 | json (~> 2.3) 221 | language_server-protocol (>= 3.17.0) 222 | parallel (~> 1.10) 223 | parser (>= 3.2.2.4) 224 | rainbow (>= 2.2.2, < 4.0) 225 | regexp_parser (>= 1.8, < 3.0) 226 | rexml (>= 3.2.5, < 4.0) 227 | rubocop-ast (>= 1.28.1, < 2.0) 228 | ruby-progressbar (~> 1.7) 229 | unicode-display_width (>= 2.4.0, < 3.0) 230 | rubocop-ast (1.30.0) 231 | parser (>= 3.2.1.0) 232 | rubocop-performance (1.19.1) 233 | rubocop (>= 1.7.0, < 2.0) 234 | rubocop-ast (>= 0.4.0) 235 | ruby-progressbar (1.13.0) 236 | ruby2_keywords (0.0.5) 237 | sidekiq (6.5.12) 238 | connection_pool (>= 2.2.5, < 3) 239 | rack (~> 2.0) 240 | redis (>= 4.5.0, < 5) 241 | sqlite3 (1.6.9-arm64-darwin) 242 | sqlite3 (1.6.9-x86_64-darwin) 243 | sqlite3 (1.6.9-x86_64-linux) 244 | standard (1.32.1) 245 | language_server-protocol (~> 3.17.0.2) 246 | lint_roller (~> 1.0) 247 | rubocop (~> 1.57.2) 248 | standard-custom (~> 1.0.0) 249 | standard-performance (~> 1.2) 250 | standard-custom (1.0.2) 251 | lint_roller (~> 1.0) 252 | rubocop (~> 1.50) 253 | standard-performance (1.2.1) 254 | lint_roller (~> 1.1) 255 | rubocop-performance (~> 1.19.1) 256 | stringio (3.1.0) 257 | thor (1.3.0) 258 | timeout (0.4.1) 259 | tzinfo (2.0.6) 260 | concurrent-ruby (~> 1.0) 261 | unicode-display_width (2.5.0) 262 | webrick (1.8.1) 263 | websocket-driver (0.7.6) 264 | websocket-extensions (>= 0.1.0) 265 | websocket-extensions (0.1.5) 266 | zeitwerk (2.6.12) 267 | 268 | PLATFORMS 269 | arm64-darwin-22 270 | x86_64-darwin-22 271 | x86_64-linux 272 | 273 | DEPENDENCIES 274 | acts_as_tenant! 275 | appraisal! 276 | byebug 277 | rspec (>= 3.0) 278 | rspec-rails 279 | sidekiq (~> 6.0) 280 | sqlite3 281 | standard 282 | 283 | BUNDLED WITH 284 | 2.4.22 285 | -------------------------------------------------------------------------------- /gemfiles/sidekiq_7.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rspec", ">=3.0" 6 | gem "rspec-rails" 7 | gem "sqlite3" 8 | gem "standard" 9 | gem "sidekiq", "~> 7.0" 10 | gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git" 11 | gem "byebug", group: [:development, :test] 12 | 13 | gemspec path: "../" 14 | -------------------------------------------------------------------------------- /gemfiles/sidekiq_7.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/thoughtbot/appraisal.git 3 | revision: feb78bcc6177038399bff098cb6c2bd4bca4972a 4 | specs: 5 | appraisal (2.5.0) 6 | bundler 7 | rake 8 | thor (>= 0.14.0) 9 | 10 | PATH 11 | remote: .. 12 | specs: 13 | acts_as_tenant (1.0.1) 14 | rails (>= 6.0) 15 | 16 | GEM 17 | remote: https://rubygems.org/ 18 | specs: 19 | actioncable (7.1.2) 20 | actionpack (= 7.1.2) 21 | activesupport (= 7.1.2) 22 | nio4r (~> 2.0) 23 | websocket-driver (>= 0.6.1) 24 | zeitwerk (~> 2.6) 25 | actionmailbox (7.1.2) 26 | actionpack (= 7.1.2) 27 | activejob (= 7.1.2) 28 | activerecord (= 7.1.2) 29 | activestorage (= 7.1.2) 30 | activesupport (= 7.1.2) 31 | mail (>= 2.7.1) 32 | net-imap 33 | net-pop 34 | net-smtp 35 | actionmailer (7.1.2) 36 | actionpack (= 7.1.2) 37 | actionview (= 7.1.2) 38 | activejob (= 7.1.2) 39 | activesupport (= 7.1.2) 40 | mail (~> 2.5, >= 2.5.4) 41 | net-imap 42 | net-pop 43 | net-smtp 44 | rails-dom-testing (~> 2.2) 45 | actionpack (7.1.2) 46 | actionview (= 7.1.2) 47 | activesupport (= 7.1.2) 48 | nokogiri (>= 1.8.5) 49 | racc 50 | rack (>= 2.2.4) 51 | rack-session (>= 1.0.1) 52 | rack-test (>= 0.6.3) 53 | rails-dom-testing (~> 2.2) 54 | rails-html-sanitizer (~> 1.6) 55 | actiontext (7.1.2) 56 | actionpack (= 7.1.2) 57 | activerecord (= 7.1.2) 58 | activestorage (= 7.1.2) 59 | activesupport (= 7.1.2) 60 | globalid (>= 0.6.0) 61 | nokogiri (>= 1.8.5) 62 | actionview (7.1.2) 63 | activesupport (= 7.1.2) 64 | builder (~> 3.1) 65 | erubi (~> 1.11) 66 | rails-dom-testing (~> 2.2) 67 | rails-html-sanitizer (~> 1.6) 68 | activejob (7.1.2) 69 | activesupport (= 7.1.2) 70 | globalid (>= 0.3.6) 71 | activemodel (7.1.2) 72 | activesupport (= 7.1.2) 73 | activerecord (7.1.2) 74 | activemodel (= 7.1.2) 75 | activesupport (= 7.1.2) 76 | timeout (>= 0.4.0) 77 | activestorage (7.1.2) 78 | actionpack (= 7.1.2) 79 | activejob (= 7.1.2) 80 | activerecord (= 7.1.2) 81 | activesupport (= 7.1.2) 82 | marcel (~> 1.0) 83 | activesupport (7.1.2) 84 | base64 85 | bigdecimal 86 | concurrent-ruby (~> 1.0, >= 1.0.2) 87 | connection_pool (>= 2.2.5) 88 | drb 89 | i18n (>= 1.6, < 2) 90 | minitest (>= 5.1) 91 | mutex_m 92 | tzinfo (~> 2.0) 93 | ast (2.4.2) 94 | base64 (0.2.0) 95 | bigdecimal (3.1.4) 96 | builder (3.2.4) 97 | byebug (11.1.3) 98 | concurrent-ruby (1.2.2) 99 | connection_pool (2.4.1) 100 | crass (1.0.6) 101 | date (3.3.4) 102 | diff-lcs (1.5.0) 103 | drb (2.2.0) 104 | ruby2_keywords 105 | erubi (1.12.0) 106 | globalid (1.2.1) 107 | activesupport (>= 6.1) 108 | i18n (1.14.1) 109 | concurrent-ruby (~> 1.0) 110 | io-console (0.6.0) 111 | irb (1.10.1) 112 | rdoc 113 | reline (>= 0.3.8) 114 | json (2.7.1) 115 | language_server-protocol (3.17.0.3) 116 | lint_roller (1.1.0) 117 | loofah (2.22.0) 118 | crass (~> 1.0.2) 119 | nokogiri (>= 1.12.0) 120 | mail (2.8.1) 121 | mini_mime (>= 0.1.1) 122 | net-imap 123 | net-pop 124 | net-smtp 125 | marcel (1.0.2) 126 | mini_mime (1.1.5) 127 | minitest (5.20.0) 128 | mutex_m (0.2.0) 129 | net-imap (0.4.7) 130 | date 131 | net-protocol 132 | net-pop (0.1.2) 133 | net-protocol 134 | net-protocol (0.2.2) 135 | timeout 136 | net-smtp (0.4.0) 137 | net-protocol 138 | nio4r (2.7.0) 139 | nokogiri (1.15.5-arm64-darwin) 140 | racc (~> 1.4) 141 | nokogiri (1.15.5-x86_64-darwin) 142 | racc (~> 1.4) 143 | nokogiri (1.15.5-x86_64-linux) 144 | racc (~> 1.4) 145 | parallel (1.23.0) 146 | parser (3.2.2.4) 147 | ast (~> 2.4.1) 148 | racc 149 | psych (5.1.1.1) 150 | stringio 151 | racc (1.7.3) 152 | rack (3.0.8) 153 | rack-session (2.0.0) 154 | rack (>= 3.0.0) 155 | rack-test (2.1.0) 156 | rack (>= 1.3) 157 | rackup (2.1.0) 158 | rack (>= 3) 159 | webrick (~> 1.8) 160 | rails (7.1.2) 161 | actioncable (= 7.1.2) 162 | actionmailbox (= 7.1.2) 163 | actionmailer (= 7.1.2) 164 | actionpack (= 7.1.2) 165 | actiontext (= 7.1.2) 166 | actionview (= 7.1.2) 167 | activejob (= 7.1.2) 168 | activemodel (= 7.1.2) 169 | activerecord (= 7.1.2) 170 | activestorage (= 7.1.2) 171 | activesupport (= 7.1.2) 172 | bundler (>= 1.15.0) 173 | railties (= 7.1.2) 174 | rails-dom-testing (2.2.0) 175 | activesupport (>= 5.0.0) 176 | minitest 177 | nokogiri (>= 1.6) 178 | rails-html-sanitizer (1.6.0) 179 | loofah (~> 2.21) 180 | nokogiri (~> 1.14) 181 | railties (7.1.2) 182 | actionpack (= 7.1.2) 183 | activesupport (= 7.1.2) 184 | irb 185 | rackup (>= 1.0.0) 186 | rake (>= 12.2) 187 | thor (~> 1.0, >= 1.2.2) 188 | zeitwerk (~> 2.6) 189 | rainbow (3.1.1) 190 | rake (13.1.0) 191 | rdoc (6.6.1) 192 | psych (>= 4.0.0) 193 | redis-client (0.18.0) 194 | connection_pool 195 | regexp_parser (2.8.3) 196 | reline (0.4.1) 197 | io-console (~> 0.5) 198 | rexml (3.2.6) 199 | rspec (3.12.0) 200 | rspec-core (~> 3.12.0) 201 | rspec-expectations (~> 3.12.0) 202 | rspec-mocks (~> 3.12.0) 203 | rspec-core (3.12.2) 204 | rspec-support (~> 3.12.0) 205 | rspec-expectations (3.12.3) 206 | diff-lcs (>= 1.2.0, < 2.0) 207 | rspec-support (~> 3.12.0) 208 | rspec-mocks (3.12.6) 209 | diff-lcs (>= 1.2.0, < 2.0) 210 | rspec-support (~> 3.12.0) 211 | rspec-rails (6.1.0) 212 | actionpack (>= 6.1) 213 | activesupport (>= 6.1) 214 | railties (>= 6.1) 215 | rspec-core (~> 3.12) 216 | rspec-expectations (~> 3.12) 217 | rspec-mocks (~> 3.12) 218 | rspec-support (~> 3.12) 219 | rspec-support (3.12.1) 220 | rubocop (1.57.2) 221 | json (~> 2.3) 222 | language_server-protocol (>= 3.17.0) 223 | parallel (~> 1.10) 224 | parser (>= 3.2.2.4) 225 | rainbow (>= 2.2.2, < 4.0) 226 | regexp_parser (>= 1.8, < 3.0) 227 | rexml (>= 3.2.5, < 4.0) 228 | rubocop-ast (>= 1.28.1, < 2.0) 229 | ruby-progressbar (~> 1.7) 230 | unicode-display_width (>= 2.4.0, < 3.0) 231 | rubocop-ast (1.30.0) 232 | parser (>= 3.2.1.0) 233 | rubocop-performance (1.19.1) 234 | rubocop (>= 1.7.0, < 2.0) 235 | rubocop-ast (>= 0.4.0) 236 | ruby-progressbar (1.13.0) 237 | ruby2_keywords (0.0.5) 238 | sidekiq (7.2.0) 239 | concurrent-ruby (< 2) 240 | connection_pool (>= 2.3.0) 241 | rack (>= 2.2.4) 242 | redis-client (>= 0.14.0) 243 | sqlite3 (1.6.9-arm64-darwin) 244 | sqlite3 (1.6.9-x86_64-darwin) 245 | sqlite3 (1.6.9-x86_64-linux) 246 | standard (1.32.1) 247 | language_server-protocol (~> 3.17.0.2) 248 | lint_roller (~> 1.0) 249 | rubocop (~> 1.57.2) 250 | standard-custom (~> 1.0.0) 251 | standard-performance (~> 1.2) 252 | standard-custom (1.0.2) 253 | lint_roller (~> 1.0) 254 | rubocop (~> 1.50) 255 | standard-performance (1.2.1) 256 | lint_roller (~> 1.1) 257 | rubocop-performance (~> 1.19.1) 258 | stringio (3.1.0) 259 | thor (1.3.0) 260 | timeout (0.4.1) 261 | tzinfo (2.0.6) 262 | concurrent-ruby (~> 1.0) 263 | unicode-display_width (2.5.0) 264 | webrick (1.8.1) 265 | websocket-driver (0.7.6) 266 | websocket-extensions (>= 0.1.0) 267 | websocket-extensions (0.1.5) 268 | zeitwerk (2.6.12) 269 | 270 | PLATFORMS 271 | arm64-darwin-22 272 | x86_64-darwin-22 273 | x86_64-linux 274 | 275 | DEPENDENCIES 276 | acts_as_tenant! 277 | appraisal! 278 | byebug 279 | rspec (>= 3.0) 280 | rspec-rails 281 | sidekiq (~> 7.0) 282 | sqlite3 283 | standard 284 | 285 | BUNDLED WITH 286 | 2.4.22 287 | -------------------------------------------------------------------------------- /lib/acts_as_tenant.rb: -------------------------------------------------------------------------------- 1 | require "active_support/current_attributes" 2 | require "acts_as_tenant/version" 3 | require "acts_as_tenant/errors" 4 | 5 | module ActsAsTenant 6 | autoload :Configuration, "acts_as_tenant/configuration" 7 | autoload :ControllerExtensions, "acts_as_tenant/controller_extensions" 8 | autoload :ModelExtensions, "acts_as_tenant/model_extensions" 9 | autoload :TenantHelper, "acts_as_tenant/tenant_helper" 10 | autoload :ActiveJobExtensions, "acts_as_tenant/active_job_extensions" 11 | 12 | @@configuration = nil 13 | @@tenant_klass = nil 14 | @@models_with_global_records = [] 15 | @@mutable_tenant = false 16 | 17 | class Current < ActiveSupport::CurrentAttributes 18 | attribute :current_tenant, :acts_as_tenant_unscoped 19 | 20 | def current_tenant=(tenant) 21 | super.tap do 22 | configuration.tenant_change_hook.call(tenant) if configuration.tenant_change_hook.present? 23 | end 24 | end 25 | 26 | def configuration 27 | Module.nesting.last.class_variable_get(:@@configuration) 28 | end 29 | end 30 | 31 | class << self 32 | attr_writer :default_tenant 33 | end 34 | 35 | def self.configure 36 | @@configuration = Configuration.new 37 | yield configuration if block_given? 38 | configuration 39 | end 40 | 41 | def self.configuration 42 | @@configuration || configure 43 | end 44 | 45 | def self.set_tenant_klass(klass) 46 | @@tenant_klass = klass 47 | end 48 | 49 | def self.tenant_klass 50 | @@tenant_klass 51 | end 52 | 53 | def self.models_with_global_records 54 | @@models_with_global_records 55 | end 56 | 57 | def self.add_global_record_model model 58 | @@models_with_global_records.push(model) 59 | end 60 | 61 | def self.fkey 62 | "#{@@tenant_klass}_id" 63 | end 64 | 65 | def self.pkey 66 | ActsAsTenant.configuration.pkey 67 | end 68 | 69 | def self.polymorphic_type 70 | "#{@@tenant_klass}_type" 71 | end 72 | 73 | def self.current_tenant=(tenant) 74 | Current.current_tenant = tenant 75 | end 76 | 77 | def self.current_tenant 78 | Current.current_tenant || test_tenant || default_tenant 79 | end 80 | 81 | def self.test_tenant=(tenant) 82 | Thread.current[:test_tenant] = tenant 83 | end 84 | 85 | def self.test_tenant 86 | Thread.current[:test_tenant] 87 | end 88 | 89 | def self.unscoped=(unscoped) 90 | Current.acts_as_tenant_unscoped = unscoped 91 | end 92 | 93 | def self.unscoped 94 | Current.acts_as_tenant_unscoped 95 | end 96 | 97 | def self.unscoped? 98 | !!unscoped 99 | end 100 | 101 | def self.default_tenant 102 | @default_tenant unless unscoped 103 | end 104 | 105 | def self.mutable_tenant!(toggle) 106 | @@mutable_tenant = toggle 107 | end 108 | 109 | def self.mutable_tenant? 110 | @@mutable_tenant 111 | end 112 | 113 | def self.with_tenant(tenant, &block) 114 | if block.nil? 115 | raise ArgumentError, "block required" 116 | end 117 | 118 | old_tenant = current_tenant 119 | self.current_tenant = tenant 120 | value = block.call 121 | value 122 | ensure 123 | self.current_tenant = old_tenant 124 | end 125 | 126 | def self.without_tenant(&block) 127 | if block.nil? 128 | raise ArgumentError, "block required" 129 | end 130 | 131 | old_tenant = current_tenant 132 | old_test_tenant = test_tenant 133 | old_unscoped = unscoped 134 | 135 | self.current_tenant = nil 136 | self.test_tenant = nil 137 | self.unscoped = true 138 | value = block.call 139 | value 140 | ensure 141 | self.current_tenant = old_tenant 142 | self.test_tenant = old_test_tenant 143 | self.unscoped = old_unscoped 144 | end 145 | 146 | def self.with_mutable_tenant(&block) 147 | ActsAsTenant.mutable_tenant!(true) 148 | without_tenant(&block) 149 | ensure 150 | ActsAsTenant.mutable_tenant!(false) 151 | end 152 | 153 | def self.should_require_tenant? 154 | if configuration.require_tenant.respond_to?(:call) 155 | !!configuration.require_tenant.call 156 | else 157 | !!configuration.require_tenant 158 | end 159 | end 160 | end 161 | 162 | ActiveSupport.on_load(:active_record) do |base| 163 | base.include ActsAsTenant::ModelExtensions 164 | end 165 | 166 | ActiveSupport.on_load(:action_controller) do |base| 167 | base.extend ActsAsTenant::ControllerExtensions 168 | base.include ActsAsTenant::TenantHelper 169 | end 170 | 171 | ActiveSupport.on_load(:action_view) do |base| 172 | base.include ActsAsTenant::TenantHelper 173 | end 174 | 175 | ActiveSupport.on_load(:active_job) do |base| 176 | base.prepend ActsAsTenant::ActiveJobExtensions 177 | end 178 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/active_job_extensions.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant 2 | module ActiveJobExtensions 3 | def serialize 4 | super.merge("current_tenant" => ActsAsTenant.current_tenant&.to_global_id&.to_s) 5 | end 6 | 7 | def deserialize(job_data) 8 | tenant_global_id = job_data.delete("current_tenant") 9 | ActsAsTenant.current_tenant = tenant_global_id ? GlobalID::Locator.locate(tenant_global_id) : nil 10 | super 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/configuration.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant 2 | class Configuration 3 | attr_writer :require_tenant, :pkey 4 | attr_reader :tenant_change_hook 5 | 6 | def require_tenant 7 | @require_tenant ||= false 8 | end 9 | 10 | def pkey 11 | @pkey ||= :id 12 | end 13 | 14 | def job_scope 15 | @job_scope || ->(relation) { relation.all } 16 | end 17 | 18 | # Used for looking job tenants in background jobs 19 | # 20 | # Format matches Rails scopes 21 | # 22 | # job_scope = ->(relation) {} 23 | # job_scope = -> {} 24 | def job_scope=(scope) 25 | @job_scope = if scope && scope.arity == 0 26 | proc { instance_exec(&scope) } 27 | else 28 | scope 29 | end 30 | end 31 | 32 | def tenant_change_hook=(hook) 33 | raise(ArgumentError, "tenant_change_hook must be a Proc") unless hook.is_a?(Proc) 34 | @tenant_change_hook = hook 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/controller_extensions.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant 2 | module ControllerExtensions 3 | autoload :Filter, "acts_as_tenant/controller_extensions/filter" 4 | autoload :Subdomain, "acts_as_tenant/controller_extensions/subdomain" 5 | autoload :SubdomainOrDomain, "acts_as_tenant/controller_extensions/subdomain_or_domain" 6 | 7 | # this method allows setting the current_tenant by reading the subdomain and looking 8 | # it up in the tenant-model passed to the method. The method will look for the subdomain 9 | # in a column referenced by the second argument. 10 | def set_current_tenant_by_subdomain(tenant = :account, column = :subdomain, subdomain_lookup: :last) 11 | include Subdomain 12 | 13 | self.tenant_class = tenant.to_s.camelcase.constantize 14 | self.tenant_column = column.to_sym 15 | self.subdomain_lookup = subdomain_lookup 16 | end 17 | 18 | # 01/27/2014 Christian Yerena / @preth00nker 19 | # this method adds the possibility of use the domain as a possible second argument to find 20 | # the current_tenant. 21 | def set_current_tenant_by_subdomain_or_domain(tenant = :account, primary_column = :subdomain, second_column = :domain, subdomain_lookup: :last) 22 | include SubdomainOrDomain 23 | 24 | self.tenant_class = tenant.to_s.camelcase.constantize 25 | self.tenant_primary_column = primary_column.to_sym 26 | self.tenant_second_column = second_column.to_sym 27 | self.subdomain_lookup = subdomain_lookup 28 | end 29 | 30 | # This method sets up a method that allows manual setting of the current_tenant. This method should 31 | # be used in a before_action. In addition, a helper is setup that returns the current_tenant 32 | def set_current_tenant_through_filter 33 | include Filter 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/controller_extensions/filter.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant 2 | module ControllerExtensions 3 | module Filter 4 | extend ActiveSupport::Concern 5 | 6 | private 7 | 8 | def set_current_tenant(current_tenant_object) 9 | ActsAsTenant.current_tenant = current_tenant_object 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/controller_extensions/subdomain.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant 2 | module ControllerExtensions 3 | module Subdomain 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | cattr_accessor :tenant_class, :tenant_column, :subdomain_lookup 8 | before_action :find_tenant_by_subdomain 9 | end 10 | 11 | private 12 | 13 | def find_tenant_by_subdomain 14 | if (subdomain = request.subdomains.send(subdomain_lookup)) 15 | ActsAsTenant.current_tenant = tenant_class.where(tenant_column => subdomain.downcase).first 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/controller_extensions/subdomain_or_domain.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant 2 | module ControllerExtensions 3 | module SubdomainOrDomain 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | cattr_accessor :tenant_class, :tenant_primary_column, :tenant_second_column, :subdomain_lookup 8 | before_action :find_tenant_by_subdomain_or_domain 9 | end 10 | 11 | private 12 | 13 | def find_tenant_by_subdomain_or_domain 14 | subdomain = request.subdomains.send(subdomain_lookup) 15 | query = subdomain.present? ? {tenant_primary_column => subdomain.downcase} : {tenant_second_column => request.domain.downcase} 16 | ActsAsTenant.current_tenant = tenant_class.where(query).first 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/errors.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant 2 | class Error < StandardError 3 | end 4 | 5 | module Errors 6 | class ModelNotScopedByTenant < ActsAsTenant::Error 7 | end 8 | 9 | class NoTenantSet < ActsAsTenant::Error 10 | end 11 | 12 | class TenantIsImmutable < ActsAsTenant::Error 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/model_extensions.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant 2 | module ModelExtensions 3 | extend ActiveSupport::Concern 4 | 5 | class_methods do 6 | def acts_as_tenant(tenant = :account, scope = nil, **options) 7 | ActsAsTenant.set_tenant_klass(tenant) 8 | ActsAsTenant.mutable_tenant!(false) 9 | 10 | ActsAsTenant.add_global_record_model(self) if options[:has_global_records] 11 | 12 | # Create the association 13 | valid_options = options.slice(:foreign_key, :class_name, :inverse_of, :optional, :primary_key, :counter_cache, :polymorphic, :touch) 14 | fkey = valid_options[:foreign_key] || ActsAsTenant.fkey 15 | pkey = valid_options[:primary_key] || ActsAsTenant.pkey 16 | polymorphic_type = valid_options[:foreign_type] || ActsAsTenant.polymorphic_type 17 | belongs_to tenant, scope, **valid_options 18 | 19 | default_scope lambda { 20 | if ActsAsTenant.should_require_tenant? && ActsAsTenant.current_tenant.nil? && !ActsAsTenant.unscoped? 21 | raise ActsAsTenant::Errors::NoTenantSet 22 | end 23 | 24 | if ActsAsTenant.current_tenant 25 | keys = [ActsAsTenant.current_tenant.send(pkey)].compact 26 | keys.push(nil) if options[:has_global_records] 27 | 28 | if options[:through] 29 | query_criteria = {options[:through] => {fkey.to_sym => keys}} 30 | query_criteria[polymorphic_type.to_sym] = ActsAsTenant.current_tenant.class.to_s if options[:polymorphic] 31 | joins(options[:through]).where(query_criteria) 32 | else 33 | query_criteria = {fkey.to_sym => keys} 34 | query_criteria[polymorphic_type.to_sym] = ActsAsTenant.current_tenant.class.to_s if options[:polymorphic] 35 | where(query_criteria) 36 | end 37 | else 38 | all 39 | end 40 | } 41 | 42 | # Add the following validations to the receiving model: 43 | # - new instances should have the tenant set 44 | # - validate that associations belong to the tenant, currently only for belongs_to 45 | # 46 | before_validation proc { |m| 47 | if ActsAsTenant.current_tenant 48 | if options[:polymorphic] 49 | m.send("#{fkey}=".to_sym, ActsAsTenant.current_tenant.class.to_s) if m.send(fkey.to_s).nil? 50 | m.send("#{polymorphic_type}=".to_sym, ActsAsTenant.current_tenant.class.to_s) if m.send(polymorphic_type.to_s).nil? 51 | else 52 | m.send "#{fkey}=".to_sym, ActsAsTenant.current_tenant.send(pkey) 53 | end 54 | end 55 | }, on: :create 56 | 57 | polymorphic_foreign_keys = reflect_on_all_associations(:belongs_to).select { |a| 58 | a.options[:polymorphic] 59 | }.map { |a| a.foreign_key } 60 | 61 | reflect_on_all_associations(:belongs_to).each do |a| 62 | unless a == reflect_on_association(tenant) || polymorphic_foreign_keys.include?(a.foreign_key) 63 | validates_each a.foreign_key.to_sym do |record, attr, value| 64 | next if value.nil? 65 | next unless record.will_save_change_to_attribute?(attr) 66 | 67 | primary_key = if a.respond_to?(:active_record_primary_key) 68 | a.active_record_primary_key 69 | else 70 | a.primary_key 71 | end.to_sym 72 | scope = a.scope || ->(relation) { relation } 73 | record.errors.add attr, "association is invalid [ActsAsTenant]" unless a.klass.class_eval(&scope).where(primary_key => value).any? 74 | end 75 | end 76 | end 77 | 78 | # Dynamically generate the following methods: 79 | # - Rewrite the accessors to make tenant immutable 80 | # - Add an override to prevent unnecessary db hits 81 | # - Add a helper method to verify if a model has been scoped by AaT 82 | to_include = Module.new { 83 | define_method "#{fkey}=" do |integer| 84 | write_attribute(fkey.to_s, integer) 85 | raise ActsAsTenant::Errors::TenantIsImmutable if !ActsAsTenant.mutable_tenant? && tenant_modified? 86 | integer 87 | end 88 | 89 | define_method "#{ActsAsTenant.tenant_klass}=" do |model| 90 | super(model) 91 | raise ActsAsTenant::Errors::TenantIsImmutable if !ActsAsTenant.mutable_tenant? && tenant_modified? 92 | model 93 | end 94 | 95 | define_method :tenant_modified? do 96 | will_save_change_to_attribute?(fkey) && persisted? && attribute_in_database(fkey).present? 97 | end 98 | } 99 | include to_include 100 | 101 | class << self 102 | def scoped_by_tenant? 103 | true 104 | end 105 | end 106 | end 107 | 108 | def validates_uniqueness_to_tenant(fields, args = {}) 109 | raise ActsAsTenant::Errors::ModelNotScopedByTenant unless respond_to?(:scoped_by_tenant?) 110 | 111 | fkey = reflect_on_association(ActsAsTenant.tenant_klass).foreign_key 112 | 113 | validation_args = args.deep_dup 114 | validation_args[:scope] = if args[:scope] 115 | Array(args[:scope]) + [fkey] 116 | else 117 | fkey 118 | end 119 | 120 | # validating within tenant scope 121 | validates_uniqueness_of(fields, validation_args) 122 | 123 | if ActsAsTenant.models_with_global_records.include?(self) 124 | arg_if = args.delete(:if) 125 | arg_condition = args.delete(:conditions) 126 | 127 | # if tenant is not set (instance is global) - validating globally 128 | global_validation_args = args.merge( 129 | if: ->(instance) { instance[fkey].blank? && (arg_if.blank? || arg_if.call(instance)) } 130 | ) 131 | validates_uniqueness_of(fields, global_validation_args) 132 | 133 | # if tenant is set (instance is not global) and records can be global - validating within records with blank tenant 134 | blank_tenant_validation_args = args.merge({ 135 | conditions: -> { arg_condition.blank? ? where(fkey => nil) : arg_condition.call.where(fkey => nil) }, 136 | if: ->(instance) { instance[fkey].present? && (arg_if.blank? || arg_if.call(instance)) } 137 | }) 138 | 139 | validates_uniqueness_of(fields, blank_tenant_validation_args) 140 | end 141 | end 142 | end 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/sidekiq.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant::Sidekiq 2 | class BaseMiddleware 3 | def self.sidekiq_7_and_up? 4 | Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new("7") 5 | end 6 | end 7 | 8 | # Get the current tenant and store in the message to be sent to Sidekiq. 9 | class Client < BaseMiddleware 10 | include Sidekiq::ClientMiddleware if sidekiq_7_and_up? 11 | 12 | def call(worker_class, msg, queue, redis_pool) 13 | if ActsAsTenant.current_tenant.present? 14 | msg["acts_as_tenant"] ||= 15 | { 16 | "class" => ActsAsTenant.current_tenant.class.name, 17 | "id" => ActsAsTenant.current_tenant.id 18 | } 19 | end 20 | 21 | yield 22 | end 23 | end 24 | 25 | # Pull the tenant out and run the current thread with it. 26 | class Server < BaseMiddleware 27 | include Sidekiq::ServerMiddleware if sidekiq_7_and_up? 28 | 29 | def call(worker_class, msg, queue) 30 | if msg.has_key?("acts_as_tenant") 31 | klass = msg["acts_as_tenant"]["class"].constantize 32 | id = msg["acts_as_tenant"]["id"] 33 | account = klass.class_eval(&ActsAsTenant.configuration.job_scope).find(id) 34 | ActsAsTenant.with_tenant account do 35 | yield 36 | end 37 | else 38 | yield 39 | end 40 | end 41 | end 42 | end 43 | 44 | Sidekiq.configure_client do |config| 45 | config.client_middleware do |chain| 46 | chain.add ActsAsTenant::Sidekiq::Client 47 | end 48 | end 49 | 50 | Sidekiq.configure_server do |config| 51 | config.client_middleware do |chain| 52 | chain.add ActsAsTenant::Sidekiq::Client 53 | end 54 | config.server_middleware do |chain| 55 | if defined?(Sidekiq::Middleware::Server::RetryJobs) 56 | chain.insert_before Sidekiq::Middleware::Server::RetryJobs, ActsAsTenant::Sidekiq::Server 57 | elsif defined?(Sidekiq::Batch::Server) 58 | chain.insert_before Sidekiq::Batch::Server, ActsAsTenant::Sidekiq::Server 59 | else 60 | chain.add ActsAsTenant::Sidekiq::Server 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/tenant_helper.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant 2 | module TenantHelper 3 | def current_tenant 4 | ActsAsTenant.current_tenant 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/test_tenant_middleware.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant 2 | class TestTenantMiddleware 3 | def initialize(app) 4 | @app = app 5 | end 6 | 7 | def call(env) 8 | previously_set_test_tenant = ActsAsTenant.test_tenant 9 | ActsAsTenant.test_tenant = nil 10 | @app.call(env) 11 | ensure 12 | ActsAsTenant.test_tenant = previously_set_test_tenant 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/acts_as_tenant/version.rb: -------------------------------------------------------------------------------- 1 | module ActsAsTenant 2 | VERSION = "1.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /spec/acts_as_tenant/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe ActsAsTenant::Configuration do 4 | after { ActsAsTenant.configure } 5 | 6 | it "provides defaults" do 7 | expect(ActsAsTenant.configuration.require_tenant).not_to be_truthy 8 | end 9 | 10 | it "stores config" do 11 | ActsAsTenant.configure do |config| 12 | config.require_tenant = true 13 | end 14 | 15 | expect(ActsAsTenant.configuration.require_tenant).to eq(true) 16 | end 17 | 18 | describe "#should_require_tenant?" do 19 | it "evaluates lambda" do 20 | ActsAsTenant.configure do |config| 21 | config.require_tenant = lambda { true } 22 | end 23 | 24 | expect(ActsAsTenant.should_require_tenant?).to eq(true) 25 | 26 | ActsAsTenant.configure do |config| 27 | config.require_tenant = lambda { false } 28 | end 29 | 30 | expect(ActsAsTenant.should_require_tenant?).to eq(false) 31 | end 32 | 33 | it "evaluates boolean" do 34 | ActsAsTenant.configure do |config| 35 | config.require_tenant = true 36 | end 37 | 38 | expect(ActsAsTenant.should_require_tenant?).to eq(true) 39 | 40 | ActsAsTenant.configure do |config| 41 | config.require_tenant = false 42 | end 43 | 44 | expect(ActsAsTenant.should_require_tenant?).to eq(false) 45 | end 46 | 47 | it "evaluates truthy" do 48 | ActsAsTenant.configure do |config| 49 | config.require_tenant = "foobar" 50 | end 51 | 52 | expect(ActsAsTenant.should_require_tenant?).to eq(true) 53 | end 54 | 55 | it "evaluates falsy" do 56 | ActsAsTenant.configure do |config| 57 | config.require_tenant = nil 58 | end 59 | 60 | expect(ActsAsTenant.should_require_tenant?).to eq(false) 61 | end 62 | 63 | it "runs a hook on current_tenant" do 64 | truthy = false 65 | ActsAsTenant.configure do |config| 66 | config.tenant_change_hook = lambda do |tenant| 67 | truthy = true 68 | end 69 | end 70 | 71 | ActsAsTenant.current_tenant = "foobar" 72 | 73 | expect(truthy).to eq(true) 74 | end 75 | 76 | it "runs a hook on with_tenant" do 77 | truthy = false 78 | ActsAsTenant.configure do |config| 79 | config.tenant_change_hook = lambda do |tenant| 80 | truthy = true 81 | end 82 | end 83 | 84 | ActsAsTenant.with_tenant("foobar") do 85 | # do nothing 86 | end 87 | 88 | expect(truthy).to eq(true) 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/acts_as_tenant/sidekiq_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "acts_as_tenant/sidekiq" 3 | 4 | describe "ActsAsTenant::Sidekiq" do 5 | let(:account) { Account.new(id: 1234) } 6 | let(:message) { {"acts_as_tenant" => {"class" => "Account", "id" => 1234}} } 7 | 8 | describe "ActsAsTenant::Sidekiq::Client" do 9 | subject { ActsAsTenant::Sidekiq::Client.new } 10 | 11 | it "saves tenant if present" do 12 | ActsAsTenant.current_tenant = account 13 | 14 | msg = {} 15 | subject.call(nil, msg, nil, nil) {} 16 | expect(msg).to eq message 17 | end 18 | 19 | it "does not set tenant if not present" do 20 | expect(ActsAsTenant.current_tenant).to be_nil 21 | 22 | msg = {} 23 | subject.call(nil, msg, nil, nil) {} 24 | expect(msg).not_to eq message 25 | end 26 | end 27 | 28 | describe "ActsAsTenant::Sidekiq::Server" do 29 | subject { ActsAsTenant::Sidekiq::Server.new } 30 | 31 | it "restores tenant if tenant saved" do 32 | Account.create!(id: 1234) 33 | msg = message 34 | subject.call(nil, msg, nil) do 35 | expect(ActsAsTenant.current_tenant).to be_a_kind_of Account 36 | end 37 | expect(ActsAsTenant.current_tenant).to be_nil 38 | end 39 | 40 | it "runs without tenant if no tenant saved" do 41 | expect(Account).not_to receive(:find) 42 | 43 | msg = {} 44 | subject.call(nil, msg, nil) do 45 | expect(ActsAsTenant.current_tenant).to be_nil 46 | end 47 | expect(ActsAsTenant.current_tenant).to be_nil 48 | end 49 | 50 | it "restores tenant with custom scope" do 51 | original_job_scope = ActsAsTenant.configuration.job_scope 52 | ActsAsTenant.configuration.job_scope = -> { unscope(where: :deleted_at) } 53 | 54 | Account.create!(id: 1234, deleted_at: 1.day.ago) 55 | msg = message 56 | subject.call(nil, msg, nil) do 57 | expect(ActsAsTenant.current_tenant).to be_a_kind_of Account 58 | end 59 | expect(ActsAsTenant.current_tenant).to be_nil 60 | ensure 61 | ActsAsTenant.configuration.job_scope = original_job_scope 62 | end 63 | end 64 | 65 | it "includes ActsAsTenant client middleware" do 66 | if ActsAsTenant::Sidekiq::BaseMiddleware.sidekiq_7_and_up? 67 | expect(Sidekiq.default_configuration.client_middleware.exists?(ActsAsTenant::Sidekiq::Client)).to eq(true) 68 | else 69 | expect(Sidekiq.client_middleware.exists?(ActsAsTenant::Sidekiq::Client)).to eq(true) 70 | end 71 | end 72 | 73 | # unable to test server configuration 74 | end 75 | -------------------------------------------------------------------------------- /spec/controllers/filter_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | class ApplicationController2 < ActionController::Base 4 | include Rails.application.routes.url_helpers 5 | set_current_tenant_through_filter 6 | before_action :your_method_that_finds_the_current_tenant 7 | 8 | def your_method_that_finds_the_current_tenant 9 | current_account = Account.new(name: "account1") 10 | set_current_tenant(current_account) 11 | end 12 | end 13 | 14 | # Start testing 15 | describe ApplicationController2, type: :controller do 16 | controller do 17 | def index 18 | # Exercise current_tenant helper method 19 | render plain: current_tenant.name 20 | end 21 | end 22 | 23 | it "Finds the correct tenant using the filter command" do 24 | get :index 25 | expect(ActsAsTenant.current_tenant.name).to eq "account1" 26 | expect(response.body).to eq "account1" 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/controllers/subdomain_or_domain_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | class DomainController < ActionController::Base 4 | include Rails.application.routes.url_helpers 5 | set_current_tenant_by_subdomain_or_domain 6 | end 7 | 8 | describe DomainController, type: :controller do 9 | let(:account) { accounts(:with_domain) } 10 | 11 | controller do 12 | def index 13 | # Exercise current_tenant helper method 14 | render plain: current_tenant.name 15 | end 16 | end 17 | 18 | it "finds the correct tenant with a example1.com" do 19 | @request.host = account.domain 20 | get :index 21 | expect(ActsAsTenant.current_tenant).to eq account 22 | expect(response.body).to eq account.name 23 | end 24 | 25 | it "finds the correct tenant with a subdomain.example.com" do 26 | @request.host = "#{account.subdomain}.example.com" 27 | get :index 28 | expect(ActsAsTenant.current_tenant).to eq account 29 | expect(response.body).to eq account.name 30 | end 31 | 32 | it "finds the correct tenant with a www.subdomain.example.com" do 33 | @request.host = "www.#{account.subdomain}.example.com" 34 | get :index 35 | expect(ActsAsTenant.current_tenant).to eq account 36 | end 37 | 38 | it "ignores case when finding tenant by subdomain" do 39 | @request.host = "#{account.subdomain.upcase}.example.com" 40 | get :index 41 | expect(ActsAsTenant.current_tenant).to eq account 42 | end 43 | 44 | context "overriding subdomain lookup" do 45 | after { controller.subdomain_lookup = :last } 46 | 47 | it "allows overriding the subdomain lookup" do 48 | controller.subdomain_lookup = :first 49 | @request.host = "#{account.subdomain}.another.example.com" 50 | get :index 51 | expect(ActsAsTenant.current_tenant).to eq account 52 | expect(response.body).to eq(account.subdomain) 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/controllers/subdomain_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | class SubdomainController < ActionController::Base 4 | include Rails.application.routes.url_helpers 5 | set_current_tenant_by_subdomain 6 | end 7 | 8 | describe SubdomainController, type: :controller do 9 | let(:account) { accounts(:with_domain) } 10 | 11 | controller(SubdomainController) do 12 | def index 13 | # Exercise current_tenant helper method 14 | render plain: current_tenant.name 15 | end 16 | end 17 | 18 | it "finds the correct tenant with a subdomain.example.com" do 19 | @request.host = "#{account.subdomain}.example.com" 20 | get :index 21 | expect(ActsAsTenant.current_tenant).to eq account 22 | expect(response.body).to eq(account.subdomain) 23 | end 24 | 25 | it "finds the correct tenant with a www.subdomain.example.com" do 26 | @request.host = "www.#{account.subdomain}.example.com" 27 | get :index 28 | expect(ActsAsTenant.current_tenant).to eq account 29 | expect(response.body).to eq(account.subdomain) 30 | end 31 | 32 | it "ignores case when finding tenant by subdomain" do 33 | @request.host = "#{account.subdomain.upcase}.example.com" 34 | get :index 35 | expect(ActsAsTenant.current_tenant).to eq account 36 | end 37 | 38 | context "overriding subdomain lookup" do 39 | after { controller.subdomain_lookup = :last } 40 | 41 | it "allows overriding the subdomain lookup" do 42 | controller.subdomain_lookup = :first 43 | @request.host = "#{account.subdomain}.another.example.com" 44 | get :index 45 | expect(ActsAsTenant.current_tenant).to eq account 46 | expect(response.body).to eq(account.subdomain) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/dummy/.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.7.1 2 | -------------------------------------------------------------------------------- /spec/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErwinM/acts_as_tenant/e235e06ab1f3d82fc0c9e05ebe8ef447350d119a/spec/dummy/app/assets/images/.keep -------------------------------------------------------------------------------- /spec/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /spec/dummy/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErwinM/acts_as_tenant/e235e06ab1f3d82fc0c9e05ebe8ef447350d119a/spec/dummy/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /spec/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require rails-ujs 14 | //= require activestorage 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /spec/dummy/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com", to: "to@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/app/mailers/user_mailer.rb: -------------------------------------------------------------------------------- 1 | class UserMailer < ApplicationMailer 2 | def comment_notification 3 | mail(body: "") 4 | end 5 | 6 | def welcome_email 7 | mail to: params[:user].email 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/dummy/app/models/account.rb: -------------------------------------------------------------------------------- 1 | class Account < ActiveRecord::Base 2 | has_many :projects 3 | has_many :global_projects 4 | has_many :users_accounts 5 | has_many :users, through: :users_accounts 6 | 7 | default_scope -> { where(deleted_at: nil) } 8 | end 9 | -------------------------------------------------------------------------------- /spec/dummy/app/models/aliased_task.rb: -------------------------------------------------------------------------------- 1 | class AliasedTask < ActiveRecord::Base 2 | belongs_to :project_alias, class_name: "Project" 3 | acts_as_tenant(:account) 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/app/models/article.rb: -------------------------------------------------------------------------------- 1 | class Article < ActiveRecord::Base 2 | has_many :polymorphic_tenant_comments, as: :polymorphic_tenant_commentable 3 | end 4 | -------------------------------------------------------------------------------- /spec/dummy/app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ActiveRecord::Base 2 | belongs_to :commentable, polymorphic: true 3 | belongs_to :task, -> { where(comments: {commentable_type: "Task"}) }, foreign_key: "commentable_id" 4 | acts_as_tenant :account 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErwinM/acts_as_tenant/e235e06ab1f3d82fc0c9e05ebe8ef447350d119a/spec/dummy/app/models/concerns/.keep -------------------------------------------------------------------------------- /spec/dummy/app/models/custom_counter_cache_task.rb: -------------------------------------------------------------------------------- 1 | class CustomCounterCacheTask < ActiveRecord::Base 2 | self.table_name = "projects" 3 | acts_as_tenant(:account, counter_cache: "projects_count") 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/app/models/custom_foreign_key_task.rb: -------------------------------------------------------------------------------- 1 | class CustomForeignKeyTask < ActiveRecord::Base 2 | acts_as_tenant(:account, foreign_key: "accountID") 3 | validates_uniqueness_to_tenant :name 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/app/models/custom_primary_key_task.rb: -------------------------------------------------------------------------------- 1 | class CustomPrimaryKeyTask < ActiveRecord::Base 2 | acts_as_tenant(:account, foreign_key: "name", primary_key: "name") 3 | validates_presence_of :name 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/app/models/global_project.rb: -------------------------------------------------------------------------------- 1 | class GlobalProject < ActiveRecord::Base 2 | self.table_name = "projects" 3 | 4 | acts_as_tenant :account, has_global_records: true 5 | validates_uniqueness_to_tenant :name 6 | end 7 | -------------------------------------------------------------------------------- /spec/dummy/app/models/global_project_with_conditions.rb: -------------------------------------------------------------------------------- 1 | class GlobalProjectWithConditions < ActiveRecord::Base 2 | self.table_name = "projects" 3 | 4 | acts_as_tenant :account, has_global_records: true 5 | validates_uniqueness_to_tenant :name, conditions: -> { where(name: "foo") } 6 | end 7 | -------------------------------------------------------------------------------- /spec/dummy/app/models/global_project_with_if.rb: -------------------------------------------------------------------------------- 1 | class GlobalProjectWithIf < ActiveRecord::Base 2 | self.table_name = "projects" 3 | 4 | acts_as_tenant :account, has_global_records: true 5 | validates_uniqueness_to_tenant :name, if: ->(instance) { instance.name == "foo" } 6 | end 7 | -------------------------------------------------------------------------------- /spec/dummy/app/models/global_project_with_scope.rb: -------------------------------------------------------------------------------- 1 | class GlobalProjectWithScope < ActiveRecord::Base 2 | self.table_name = "projects" 3 | 4 | acts_as_tenant :account, has_global_records: true 5 | validates_uniqueness_to_tenant :name, scope: [:user_defined_scope] 6 | end 7 | -------------------------------------------------------------------------------- /spec/dummy/app/models/manager.rb: -------------------------------------------------------------------------------- 1 | class Manager < ActiveRecord::Base 2 | belongs_to :project, -> { unscope(where: :deleted_at) } 3 | acts_as_tenant :account, -> { unscope(where: :deleted_at) } 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/app/models/polymorphic_tenant_comment.rb: -------------------------------------------------------------------------------- 1 | class PolymorphicTenantComment < ActiveRecord::Base 2 | belongs_to :polymorphic_tenant_commentable, polymorphic: true 3 | belongs_to :account 4 | acts_as_tenant :polymorphic_tenant_commentable, polymorphic: true 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/app/models/project.rb: -------------------------------------------------------------------------------- 1 | class Project < ActiveRecord::Base 2 | has_one :manager 3 | has_many :tasks 4 | has_many :polymorphic_tenant_comments, as: :polymorphic_tenant_commentable 5 | acts_as_tenant :account 6 | 7 | validates_uniqueness_to_tenant :name 8 | 9 | default_scope -> { where(deleted_at: nil) } 10 | end 11 | -------------------------------------------------------------------------------- /spec/dummy/app/models/task.rb: -------------------------------------------------------------------------------- 1 | class Task < ActiveRecord::Base 2 | belongs_to :project 3 | default_scope -> { where(completed: nil).order("name") } 4 | 5 | acts_as_tenant :account 6 | validates_uniqueness_of :name 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/app/models/unique_task.rb: -------------------------------------------------------------------------------- 1 | class UniqueTask < ActiveRecord::Base 2 | belongs_to :project 3 | acts_as_tenant(:account) 4 | validates_uniqueness_to_tenant :name, scope: :user_defined_scope 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/app/models/unscoped_model.rb: -------------------------------------------------------------------------------- 1 | class UnscopedModel < ActiveRecord::Base 2 | validates_uniqueness_of :name 3 | end 4 | -------------------------------------------------------------------------------- /spec/dummy/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | has_many :users_accounts 3 | has_many :accounts, through: :users_accounts 4 | 5 | acts_as_tenant :account, through: :users_accounts 6 | end 7 | -------------------------------------------------------------------------------- /spec/dummy/app/models/users_account.rb: -------------------------------------------------------------------------------- 1 | class UsersAccount < ActiveRecord::Base 2 | belongs_to :user 3 | acts_as_tenant :account 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |You may have mistyped the address or the page may have moved.
63 |If you are the application owner check the logs for more information.
65 |Maybe you tried to change something you didn't have access to.
63 |If you are the application owner check the logs for more information.
65 |If you are the application owner check the logs for more information.
64 |