├── .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 | [![Build Status](https://github.com/ErwinM/acts_as_tenant/workflows/Tests/badge.svg)](https://github.com/ErwinM/acts_as_tenant/actions) [![Gem Version](https://badge.fury.io/rb/acts_as_tenant.svg)](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 | ActsAsTenant Walkthrough Video 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 |

Adding multi-tenancy to your Rails app: acts_as_tenant

2 | Roll Call is implemented as a multi-tenant application: each user gets their own instance of the app, content is strictly scoped to a user’s instance. In Rails, this can be achieved in various ways. Guy Naor did a great job of diving into the pros and cons of each option in his 2009 Acts As Conference talk. If you are doing multi-tenancy in Rails, you should watch his video.

3 |

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 |

Using a Shared Database strategy is alot of work!

5 |

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 |

First, how does multi-tenancy with a shared database strategy work

7 |

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.

8 |

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).

9 |

Enter acts_as_tenant

10 |

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 |
    12 |
  1. scoping all searches to the current Account
  2. 13 |
  3. scoping the uniqueness validator to the current Account
  4. 14 |
  5. protecting against various nastiness trying to circumvent the scoping.
  6. 15 |
16 |

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.

17 |

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:

18 | 19 | ```ruby 20 | class Addaccounttoproject < ActiveRecord::Migration 21 | def change 22 | add_column :projects, :account_id, :integer 23 | end 24 | end 25 | 26 | class Project < ActiveRecord::Base 27 | acts_as_tenant(:account) 28 | validates_uniqueness_to_tenant :name 29 | end 30 | ``` 31 | What does adding these two methods accomplish: 32 |
    33 |
  1. it ensures every search on the project model will be scoped to the current tenant,
  2. 34 |
  3. it adds validation for every association confirming the associated object does indeed belong to the current tenant,
  4. 35 |
  5. it validates the uniqueness of `:name` to the current tenant,
  6. 36 |
  7. it implements a bunch of safeguards preventing all kinds of nastiness from exposing other tenants data (mainly form-injection attacks).
  8. 37 |
38 |

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 |
    48 |
  1. the current tenant should be found based on the subdomain (e.g. account1.myappdomain.com),
  2. 49 |
  3. tenants are represented by the `Account`-model and
  4. 50 |
  5. the `Account` model has a column named `subdomain` that should be used the lookup the current account, using the current subdomain.
  6. 51 |
52 |

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 |

Great! Anything else I should know? A few caveats:

62 | 67 |

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 | Dummy 5 | <%= csrf_meta_tags %> 6 | <%= csp_meta_tag %> 7 | 8 | <%= stylesheet_link_tag 'application', media: 'all' %> 9 | 10 | 11 | 12 | <%= yield %> 13 | 14 | 15 | -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /spec/dummy/app/views/user_mailer/welcome_email.html.erb: -------------------------------------------------------------------------------- 1 | Hello world 2 | -------------------------------------------------------------------------------- /spec/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /spec/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /spec/dummy/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to setup or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?('config/database.yml') 22 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails/all" 4 | 5 | Bundler.require(*Rails.groups) 6 | require "acts_as_tenant" 7 | 8 | module Dummy 9 | class Application < Rails::Application 10 | # Settings in config/environments/* take precedence over those specified here. 11 | # Application configuration can go into files in config/initializers 12 | # -- all .rb files in that directory are automatically loaded after loading 13 | # the framework and any gems in your application. 14 | 15 | if Rails.gem_version < Gem::Version.new("6.0") && config.active_record.sqlite3 16 | config.active_record.sqlite3.represent_boolean_as_integer = true 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) 3 | 4 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 5 | $LOAD_PATH.unshift File.expand_path("../../../lib", __dir__) 6 | -------------------------------------------------------------------------------- /spec/dummy/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: dummy_production 11 | -------------------------------------------------------------------------------- /spec/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | 8 | development: 9 | adapter: sqlite3 10 | database: db/acts_as_tenant_dev.sqlite3 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/acts_as_tenant_test.sqlite3 18 | -------------------------------------------------------------------------------- /spec/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | # Run rails dev:cache to toggle caching. 17 | if Rails.root.join("tmp", "caching-dev.txt").exist? 18 | config.action_controller.perform_caching = true 19 | config.action_controller.enable_fragment_cache_logging = true 20 | 21 | config.cache_store = :memory_store 22 | config.public_file_server.headers = { 23 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 24 | } 25 | else 26 | config.action_controller.perform_caching = false 27 | 28 | config.cache_store = :null_store 29 | end 30 | 31 | # Store uploaded files on the local file system (see config/storage.yml for options). 32 | config.active_storage.service = :local 33 | 34 | # Don't care if the mailer can't send. 35 | config.action_mailer.raise_delivery_errors = false 36 | 37 | config.action_mailer.perform_caching = false 38 | 39 | # Print deprecation notices to the Rails logger. 40 | config.active_support.deprecation = :log 41 | 42 | # Raise an error on page load if there are pending migrations. 43 | config.active_record.migration_error = :page_load 44 | 45 | # Highlight code that triggered database queries in logs. 46 | config.active_record.verbose_query_logs = true 47 | 48 | # Debug mode disables concatenation and preprocessing of assets. 49 | # This option may cause significant delays in view rendering with a large 50 | # number of complex assets. 51 | config.assets.debug = true 52 | 53 | # Suppress logger output for asset requests. 54 | config.assets.quiet = true 55 | 56 | # Raises error for missing translations. 57 | # config.action_view.raise_on_missing_translations = true 58 | 59 | # Use an evented file watcher to asynchronously detect changes in source code, 60 | # routes, locales, etc. This feature depends on the listen gem. 61 | # config.file_watcher = ActiveSupport::EventedFileUpdateChecker 62 | end 63 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 19 | # config.require_master_key = true 20 | 21 | # Disable serving static files from the `/public` folder by default since 22 | # Apache or NGINX already handles this. 23 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 24 | 25 | # Compress CSS using a preprocessor. 26 | # config.assets.css_compressor = :sass 27 | 28 | # Do not fallback to assets pipeline if a precompiled asset is missed. 29 | config.assets.compile = false 30 | 31 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 32 | # config.action_controller.asset_host = 'http://assets.example.com' 33 | 34 | # Specifies the header that your server uses for sending files. 35 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 36 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 37 | 38 | # Store uploaded files on the local file system (see config/storage.yml for options). 39 | config.active_storage.service = :local 40 | 41 | # Mount Action Cable outside main process or domain. 42 | # config.action_cable.mount_path = nil 43 | # config.action_cable.url = 'wss://example.com/cable' 44 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 45 | 46 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 47 | # config.force_ssl = true 48 | 49 | # Use the lowest log level to ensure availability of diagnostic information 50 | # when problems arise. 51 | config.log_level = :debug 52 | 53 | # Prepend all log lines with the following tags. 54 | config.log_tags = [:request_id] 55 | 56 | # Use a different cache store in production. 57 | # config.cache_store = :mem_cache_store 58 | 59 | # Use a real queuing backend for Active Job (and separate queues per environment). 60 | # config.active_job.queue_adapter = :resque 61 | # config.active_job.queue_name_prefix = "dummy_production" 62 | 63 | config.action_mailer.perform_caching = false 64 | 65 | # Ignore bad email addresses and do not raise email delivery errors. 66 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 67 | # config.action_mailer.raise_delivery_errors = false 68 | 69 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 70 | # the I18n.default_locale when a translation cannot be found). 71 | config.i18n.fallbacks = true 72 | 73 | # Send deprecation notices to registered listeners. 74 | config.active_support.deprecation = :notify 75 | 76 | # Use default logging formatter so that PID and timestamp are not suppressed. 77 | config.log_formatter = ::Logger::Formatter.new 78 | 79 | # Use a different logger for distributed setups. 80 | # require 'syslog/logger' 81 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 82 | 83 | if ENV["RAILS_LOG_TO_STDOUT"].present? 84 | logger = ActiveSupport::Logger.new($stdout) 85 | logger.formatter = config.log_formatter 86 | config.logger = ActiveSupport::TaggedLogging.new(logger) 87 | end 88 | 89 | # Do not dump schema after migrations. 90 | config.active_record.dump_schema_after_migration = false 91 | 92 | # Inserts middleware to perform automatic connection switching. 93 | # The `database_selector` hash is used to pass options to the DatabaseSelector 94 | # middleware. The `delay` is used to determine how long to wait after a write 95 | # to send a subsequent read to the primary. 96 | # 97 | # The `database_resolver` class is used by the middleware to determine which 98 | # database is appropriate to use based on the time delay. 99 | # 100 | # The `database_resolver_context` class is used by the middleware to set 101 | # timestamps for the last write to the primary. The resolver uses the context 102 | # class timestamps to determine how long to wait before reading from the 103 | # replica. 104 | # 105 | # By default Rails will store a last write timestamp in the session. The 106 | # DatabaseSelector middleware is designed as such you can define your own 107 | # strategy for connection switching and pass that into the middleware through 108 | # these configuration options. 109 | # config.active_record.database_selector = { delay: 2.seconds } 110 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver 111 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session 112 | end 113 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # The test environment is used exclusively to run your application's 2 | # test suite. You never need to work with it otherwise. Remember that 3 | # your test database is "scratch space" for the test suite and is wiped 4 | # and recreated between test runs. Don't rely on the data there! 5 | 6 | Rails.application.configure do 7 | # Settings specified here will take precedence over those in config/application.rb. 8 | 9 | config.cache_classes = false 10 | config.action_view.cache_template_loading = true 11 | 12 | # Do not eager load code on boot. This avoids loading your whole application 13 | # just for the purpose of running a single test. If you are using a tool that 14 | # preloads Rails for running tests, you may have to set it to true. 15 | config.eager_load = false 16 | 17 | # Configure public file server for tests with Cache-Control for performance. 18 | config.public_file_server.enabled = true 19 | config.public_file_server.headers = { 20 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 21 | } 22 | 23 | # Show full error reports and disable caching. 24 | config.consider_all_requests_local = true 25 | config.action_controller.perform_caching = false 26 | config.cache_store = :null_store 27 | 28 | # Raise exceptions instead of rendering exception templates. 29 | config.action_dispatch.show_exceptions = false 30 | 31 | # Disable request forgery protection in test environment. 32 | config.action_controller.allow_forgery_protection = false 33 | 34 | # Store uploaded files on the local file system in a temporary directory. 35 | config.active_storage.service = :test 36 | 37 | config.action_mailer.perform_caching = false 38 | 39 | # Tell Action Mailer not to deliver emails to the real world. 40 | # The :test delivery method accumulates sent emails in the 41 | # ActionMailer::Base.deliveries array. 42 | config.action_mailer.delivery_method = :test 43 | 44 | # Print deprecation notices to the stderr. 45 | config.active_support.deprecation = :stderr 46 | 47 | # Raises error for missing translations. 48 | # config.action_view.raise_on_missing_translations = true 49 | end 50 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" if Rails.application.config.respond_to?(:assets) 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | 19 | # If you are using UJS then enable automatic nonce generation 20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 21 | 22 | # Set the nonce only to specific directives 23 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src) 24 | 25 | # Report CSP violations to a specified URI 26 | # For further information see the following documentation: 27 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 28 | # Rails.application.config.content_security_policy_report_only = true 29 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /spec/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | notifications: 35 | noticed/i18n_example: 36 | message: "This is a notification" 37 | noticed: 38 | scoped_i18n_example: 39 | message: "This is a custom scoped trnaslation" 40 | 41 | -------------------------------------------------------------------------------- /spec/dummy/config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5) 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS", max_threads_count) 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 12 | # 13 | port ENV.fetch("PORT", 3000) 14 | 15 | # Specifies the `environment` that Puma will run in. 16 | # 17 | environment ENV.fetch("RAILS_ENV", "development") 18 | 19 | # Specifies the `pidfile` that Puma will use. 20 | pidfile ENV.fetch("PIDFILE", "tmp/pids/server.pid") 21 | 22 | # Specifies the number of `workers` to boot in clustered mode. 23 | # Workers are forked web server processes. If using threads and workers together 24 | # the concurrency of the application would be max `threads` * `workers`. 25 | # Workers do not work on JRuby or Windows (both of which do not support 26 | # processes). 27 | # 28 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 29 | 30 | # Use the `preload_app!` method when specifying a `workers` number. 31 | # This directive tells Puma to first boot the application and load code 32 | # before forking the application. This takes advantage of Copy On Write 33 | # process behavior so workers use less memory. 34 | # 35 | # preload_app! 36 | 37 | # Allow puma to be restarted by `rails restart` command. 38 | plugin :tmp_restart 39 | -------------------------------------------------------------------------------- /spec/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html 3 | root to: "main#index" 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/config/spring.rb: -------------------------------------------------------------------------------- 1 | Spring.watch( 2 | ".ruby-version", 3 | ".rbenv-vars", 4 | "tmp/restart.txt", 5 | "tmp/caching-dev.txt" 6 | ) 7 | -------------------------------------------------------------------------------- /spec/dummy/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /spec/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `rails 6 | # db:schema:load`. When creating a new database, `rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 1) do 14 | create_table :accounts, force: true do |t| 15 | t.column :name, :string 16 | t.column :subdomain, :string 17 | t.column :domain, :string 18 | t.column :deleted_at, :timestamp 19 | t.column :projects_count, :integer, default: 0 20 | end 21 | 22 | create_table :projects, force: true do |t| 23 | t.column :name, :string 24 | t.column :account_id, :integer 25 | t.column :user_defined_scope, :string 26 | t.column :deleted_at, :timestamp 27 | end 28 | 29 | create_table :managers, force: true do |t| 30 | t.column :name, :string 31 | t.column :project_id, :integer 32 | t.column :account_id, :integer 33 | end 34 | 35 | create_table :tasks, force: true do |t| 36 | t.column :name, :string 37 | t.column :account_id, :integer 38 | t.column :project_id, :integer 39 | t.column :completed, :boolean 40 | end 41 | 42 | create_table :countries, force: true do |t| 43 | t.column :name, :string 44 | end 45 | 46 | create_table :unscoped_models, force: true do |t| 47 | t.column :name, :string 48 | end 49 | 50 | create_table :aliased_tasks, force: true do |t| 51 | t.column :name, :string 52 | t.column :project_alias_id, :integer 53 | t.column :account_id, :integer 54 | end 55 | 56 | create_table :unique_tasks, force: true do |t| 57 | t.column :name, :string 58 | t.column :user_defined_scope, :string 59 | t.column :project_id, :integer 60 | t.column :account_id, :integer 61 | end 62 | 63 | create_table :custom_foreign_key_tasks, force: true do |t| 64 | t.column :name, :string 65 | t.column :accountID, :integer 66 | end 67 | 68 | create_table :custom_primary_key_tasks, force: true do |t| 69 | t.column :name, :string 70 | end 71 | 72 | create_table :articles, force: true do |t| 73 | t.column :title, :string 74 | end 75 | 76 | create_table :comments, force: true do |t| 77 | t.column :commentable_id, :integer 78 | t.column :commentable_type, :string 79 | t.column :account_id, :integer 80 | end 81 | 82 | create_table :polymorphic_tenant_comments, force: true do |t| 83 | t.column :polymorphic_tenant_commentable_id, :integer 84 | t.column :polymorphic_tenant_commentable_type, :string 85 | t.column :account_id, :integer 86 | end 87 | 88 | create_table :users, force: true do |t| 89 | t.column :email, :string 90 | t.column :name, :string 91 | end 92 | 93 | create_table :users_accounts, force: true do |t| 94 | t.column :user_id, :integer 95 | t.column :account_id, :integer 96 | t.index [:user_id, :account_id], name: :index_users_accounts_on_user_id_and_account_id, unique: true 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErwinM/acts_as_tenant/e235e06ab1f3d82fc0c9e05ebe8ef447350d119a/spec/dummy/lib/assets/.keep -------------------------------------------------------------------------------- /spec/dummy/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErwinM/acts_as_tenant/e235e06ab1f3d82fc0c9e05ebe8ef447350d119a/spec/dummy/log/.keep -------------------------------------------------------------------------------- /spec/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /spec/dummy/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErwinM/acts_as_tenant/e235e06ab1f3d82fc0c9e05ebe8ef447350d119a/spec/dummy/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /spec/dummy/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErwinM/acts_as_tenant/e235e06ab1f3d82fc0c9e05ebe8ef447350d119a/spec/dummy/public/apple-touch-icon.png -------------------------------------------------------------------------------- /spec/dummy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErwinM/acts_as_tenant/e235e06ab1f3d82fc0c9e05ebe8ef447350d119a/spec/dummy/public/favicon.ico -------------------------------------------------------------------------------- /spec/dummy/test/mailers/previews/user_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | class UserMailerPreview < ActionMailer::Preview 2 | def welcome_email 3 | ActsAsTenant.with_tenant(Account.first) do 4 | UserMailer.with(user: User.first).welcome_email 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/tmp/development_secret.txt: -------------------------------------------------------------------------------- 1 | f1e8981c98a3cd825ba410f2080160547945e8c380645e0dfaeba509fa55291dcd4e317d6c2ba08bc94a4110b6c567987a66f616f67e3b5f62e11a4b216820e6 -------------------------------------------------------------------------------- /spec/fixtures/accounts.yml: -------------------------------------------------------------------------------- 1 | foo: 2 | name: foo 3 | 4 | bar: 5 | name: bar 6 | 7 | with_domain: 8 | name: domain 9 | subdomain: domain 10 | domain: domain.com 11 | 12 | abc: 13 | name: abc 14 | users: john, bob # habtm relation 15 | 16 | def: 17 | name: def 18 | users: john, alice # habtm relation -------------------------------------------------------------------------------- /spec/fixtures/custom_primary_key_tasks.yml: -------------------------------------------------------------------------------- 1 | without_account: 2 | name: "bar" 3 | -------------------------------------------------------------------------------- /spec/fixtures/global_projects.yml: -------------------------------------------------------------------------------- 1 | global: 2 | name: "global" 3 | 4 | global2: 5 | name: "global 2" 6 | 7 | global_foo: 8 | account: foo 9 | name: "global foo" 10 | 11 | global_bar: 12 | account: bar 13 | name: "global bar" 14 | 15 | global_scope: 16 | name: "global scope" 17 | user_defined_scope: abc 18 | -------------------------------------------------------------------------------- /spec/fixtures/projects.yml: -------------------------------------------------------------------------------- 1 | without_account: 2 | name: without_account 3 | 4 | foo: 5 | account: foo 6 | name: foo 7 | 8 | bar: 9 | account: bar 10 | name: bar 11 | -------------------------------------------------------------------------------- /spec/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | john: 2 | email: john@example.org 3 | name: john 4 | 5 | bob: 6 | email: bob@example.org 7 | name: bob 8 | 9 | alice: 10 | email: alice@example.org 11 | name: alice 12 | -------------------------------------------------------------------------------- /spec/helpers/tenant_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe ActionView::Base, type: :helper do 4 | it "responds ot current_tenant" do 5 | expect(helper).to respond_to(:current_tenant) 6 | end 7 | 8 | it "returns nil if no tenant set" do 9 | expect(helper.current_tenant).to be_nil 10 | end 11 | 12 | it "returns the current tenant" do 13 | ActsAsTenant.current_tenant = accounts(:foo) 14 | expect(helper.current_tenant).to eq(accounts(:foo)) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/jobs/active_job_extensions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | class ApplicationTestJob < ApplicationJob 4 | def perform(expected_tenant:) 5 | raise ApplicationTestJobTenantError unless ActsAsTenant.current_tenant == expected_tenant 6 | Project.all 7 | end 8 | end 9 | 10 | class ApplicationTestJobTenantError < StandardError; end 11 | 12 | RSpec.describe ApplicationTestJob, type: :job do 13 | include ActiveJob::TestHelper 14 | 15 | let(:account) { accounts(:foo) } 16 | 17 | describe "#perform_later" do 18 | context "when tenant is required" do 19 | before { allow(ActsAsTenant.configuration).to receive_messages(require_tenant: true) } 20 | 21 | it "raises ApplicationTestJobTenantError when expected_tenant does not match current_tenant" do 22 | ActsAsTenant.current_tenant = account 23 | expect { described_class.perform_later(expected_tenant: nil) }.to have_enqueued_job.on_queue("default") 24 | expect { perform_enqueued_jobs }.to raise_error(ApplicationTestJobTenantError) 25 | end 26 | 27 | it "when tenant is set, successfully queues and performs job" do 28 | ActsAsTenant.current_tenant = account 29 | expect { described_class.perform_later(expected_tenant: account) }.to have_enqueued_job.on_queue("default") 30 | expect { perform_enqueued_jobs }.not_to raise_error 31 | end 32 | 33 | it "when tenant is not set, successfully queues but fails to perform job" do 34 | ActsAsTenant.current_tenant = nil 35 | expect { described_class.perform_later(expected_tenant: nil) }.to have_enqueued_job.on_queue("default") 36 | expect { perform_enqueued_jobs }.to raise_error(ActsAsTenant::Errors::NoTenantSet) 37 | end 38 | end 39 | 40 | context "when tenant is not required" do 41 | before { allow(ActsAsTenant.configuration).to receive_messages(require_tenant: false) } 42 | it "when tenant is not set, queues and performs job" do 43 | ActsAsTenant.current_tenant = nil 44 | expect { described_class.perform_later(expected_tenant: nil) }.to have_enqueued_job.on_queue("default") 45 | expect { perform_enqueued_jobs }.not_to raise_error 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/middlewares/test_tenant_middleware_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "acts_as_tenant/test_tenant_middleware" 3 | 4 | class TestRackApp1 5 | def call(_env) 6 | ActsAsTenant.current_tenant = Account.first 7 | TestReceiver.assert_current_id(ActsAsTenant.current_tenant.id) 8 | ActsAsTenant.current_tenant = nil 9 | [200, {}, ["OK"]] 10 | end 11 | end 12 | 13 | class TestRackApp2 14 | def call(_env) 15 | TestReceiver.assert_current_id(ActsAsTenant.current_tenant.try(:id)) 16 | [200, {}, ["OK"]] 17 | end 18 | end 19 | 20 | class TestReceiver 21 | def self.assert_current_id(id) 22 | end 23 | end 24 | 25 | describe ActsAsTenant::TestTenantMiddleware do 26 | fixtures :accounts 27 | after { ActsAsTenant.test_tenant = nil } 28 | 29 | subject { request.get("/some/path") } 30 | 31 | let(:middleware) { described_class.new(app) } 32 | let(:request) { Rack::MockRequest.new(middleware) } 33 | 34 | let!(:account1) { accounts(:foo) } 35 | let!(:account2) { accounts(:bar) } 36 | 37 | context "when test_tenant is nil before processing" do 38 | context "that switches tenancies" do 39 | let(:app) { TestRackApp1.new } 40 | 41 | it "should remain nil after processing" do 42 | expect(ActsAsTenant.current_tenant).to be_nil 43 | expect(TestReceiver).to receive(:assert_current_id).with(account1.id) 44 | expect(subject.status).to eq 200 45 | expect(ActsAsTenant.current_tenant).to be_nil 46 | end 47 | end 48 | 49 | context "that does not switch tenancies" do 50 | let(:app) { TestRackApp2.new } 51 | 52 | it "should remain nil after processing" do 53 | expect(ActsAsTenant.current_tenant).to be_nil 54 | expect(TestReceiver).to receive(:assert_current_id).with(nil) 55 | expect(subject.status).to eq 200 56 | expect(ActsAsTenant.current_tenant).to be_nil 57 | end 58 | end 59 | end 60 | 61 | context "when test_tenant is assigned before processing" do 62 | before { ActsAsTenant.test_tenant = account2 } 63 | 64 | context "that switches tenancies" do 65 | let(:app) { TestRackApp1.new } 66 | 67 | it "should remain assigned after processing" do 68 | expect(ActsAsTenant.current_tenant).to eq account2 69 | expect(TestReceiver).to receive(:assert_current_id).with(account1.id) 70 | expect(subject.status).to eq 200 71 | expect(ActsAsTenant.current_tenant).to eq account2 72 | end 73 | end 74 | 75 | context "that does not switch tenancies" do 76 | let(:app) { TestRackApp2.new } 77 | 78 | it "should remain assigned after processing" do 79 | expect(ActsAsTenant.current_tenant).to eq account2 80 | expect(TestReceiver).to receive(:assert_current_id).with(nil) 81 | expect(subject.status).to eq 200 82 | expect(ActsAsTenant.current_tenant).to eq account2 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /spec/models/model_extensions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe ActsAsTenant do 4 | let(:account) { accounts(:foo) } 5 | 6 | it "can set the current tenant" do 7 | ActsAsTenant.current_tenant = :foo 8 | expect(ActsAsTenant.current_tenant).to eq(:foo) 9 | end 10 | 11 | it "is_scoped_as_tenant should return the correct value when true" do 12 | expect(Project.respond_to?(:scoped_by_tenant?)).to eq(true) 13 | end 14 | 15 | it "is_scoped_as_tenant should return the correct value when false" do 16 | expect(UnscopedModel.respond_to?(:scoped_by_tenant?)).to eq(false) 17 | end 18 | 19 | it "tenant_id should be immutable, if already set" do 20 | project = account.projects.create!(name: "bar") 21 | expect { project.account_id = account.id + 1 }.to raise_error(ActsAsTenant::Errors::TenantIsImmutable) 22 | end 23 | 24 | it "setting tenant_id to the same value should not error" do 25 | project = account.projects.create!(name: "bar") 26 | expect { project.account_id = account.id }.not_to raise_error 27 | end 28 | 29 | it "setting tenant_id to a string with same to_i value should not error" do 30 | project = account.projects.create!(name: "bar") 31 | expect { project.account_id = account.id.to_s }.not_to raise_error 32 | end 33 | 34 | it "setting tenant_id to nil should throw error" do 35 | project = account.projects.create!(name: "bar") 36 | expect { project.account_id = nil }.to raise_error(ActsAsTenant::Errors::TenantIsImmutable) 37 | end 38 | 39 | it "tenant_id should be mutable, if not already set" do 40 | project = projects(:without_account) 41 | expect(project.account_id).to be_nil 42 | expect { project.account = account }.not_to raise_error 43 | end 44 | 45 | it "tenant_id should auto populate after initialization" do 46 | ActsAsTenant.current_tenant = account 47 | expect(Project.new.account_id).to eq(account.id) 48 | end 49 | 50 | it "handles custom foreign_key on tenant model" do 51 | ActsAsTenant.current_tenant = account 52 | custom_foreign_key_task = CustomForeignKeyTask.create!(name: "foo") 53 | expect(custom_foreign_key_task.account).to eq(account) 54 | end 55 | 56 | it "handles custom primary_key on tenant model" do 57 | ActsAsTenant.current_tenant = account 58 | custom_primary_key_task = CustomPrimaryKeyTask.create! 59 | expect(custom_primary_key_task.account).to eq(account) 60 | expect(CustomPrimaryKeyTask.count).to eq(1) 61 | end 62 | 63 | it "should correctly increment and decrement the tenants counter_cache column" do 64 | ActsAsTenant.current_tenant = account 65 | project = CustomCounterCacheTask.create!(name: "bar") 66 | expect(account.reload.projects_count).to eq(1) 67 | project.destroy 68 | expect(account.reload.projects_count).to eq(0) 69 | end 70 | 71 | it "does not cache account association" do 72 | project = account.projects.first 73 | ActsAsTenant.current_tenant = account 74 | expect(project.account.name).to eq(account.name) 75 | account.update!(name: "Acme") 76 | expect(project.account.name).to eq("Acme") 77 | end 78 | 79 | it "Querying the tenant from a scoped model without a tenant set" do 80 | expect(projects(:foo).account).to_not be_nil 81 | end 82 | 83 | it "Querying the tenant from a scoped model with a tenant set" do 84 | ActsAsTenant.current_tenant = account 85 | expect(projects(:foo).account).to eq(accounts(:foo)) 86 | expect(projects(:bar).account).to eq(accounts(:bar)) 87 | end 88 | 89 | describe "scoping models" do 90 | it "should scope Project.all to the current tenant if set" do 91 | ActsAsTenant.current_tenant = account 92 | expect(Project.count).to eq(account.projects.count) 93 | expect(Project.all).to eq(account.projects) 94 | end 95 | 96 | it "should allow unscoping" do 97 | ActsAsTenant.current_tenant = account 98 | expect(Project.unscoped.count).to be > account.projects.count 99 | end 100 | 101 | it "returns nothing with unsaved tenant" do 102 | ActsAsTenant.current_tenant = Account.new 103 | expect(Project.all.count).to eq(0) 104 | end 105 | end 106 | 107 | describe "acts_as_tenant :through" do 108 | let(:account) { accounts(:abc) } 109 | 110 | it "should scope User.all to the current tenant if set" do 111 | ActsAsTenant.current_tenant = account 112 | expect(User.count).to eq(account.users.count) 113 | expect(User.all).to eq(account.users) 114 | end 115 | 116 | it "should return all users when no current tenant is set" do 117 | expect(User.count).to eq(3) 118 | end 119 | 120 | it "should allow unscoping" do 121 | ActsAsTenant.current_tenant = account 122 | expect(User.unscoped.count).to be > account.users.count 123 | end 124 | end 125 | 126 | describe "A tenant model with global records" do 127 | before do 128 | ActsAsTenant.current_tenant = account 129 | end 130 | 131 | it "should return global and tenant projects" do 132 | expect(GlobalProject.count).to eq(GlobalProject.unscoped.where(account: [nil, account]).count) 133 | end 134 | 135 | it "returns global records with unsaved tenant" do 136 | ActsAsTenant.current_tenant = Account.new 137 | expect(GlobalProject.all.count).to eq(GlobalProject.unscoped.where(account: [nil]).count) 138 | end 139 | 140 | it "should add the model to ActsAsTenant.models_with_global_records" do 141 | expect(ActsAsTenant.models_with_global_records.include?(GlobalProject)).to be_truthy 142 | expect(ActsAsTenant.models_with_global_records.include?(Project)).to be_falsy 143 | end 144 | 145 | context "should validate tenant records against global & tenant records" do 146 | it "global records are valid" do 147 | expect(global_projects(:global).valid?).to be(true) 148 | end 149 | 150 | it "allows separate global and tenant records" do 151 | expect(GlobalProject.new(name: "foo new").valid?).to be(true) 152 | end 153 | 154 | it "is valid if tenant is different" do 155 | ActsAsTenant.current_tenant = accounts(:bar) 156 | 157 | expect(GlobalProject.new(name: "global foo").valid?).to be(true) 158 | end 159 | 160 | it "is invalid with duplicate tenant records" do 161 | expect(GlobalProject.new(name: "global foo").valid?).to be(false) 162 | end 163 | 164 | it "is invalid if tenant record conflicts with global record" do 165 | expect(GlobalProject.new(name: "global").valid?).to be(false) 166 | end 167 | 168 | it "is invalid if tenant record conflicts with global record with scope" do 169 | duplicate = GlobalProjectWithScope.new( 170 | name: "global scope", 171 | user_defined_scope: "abc" 172 | ) 173 | expect(duplicate.valid?).to be(false) 174 | end 175 | end 176 | 177 | context "should validate global records against global & tenant records" do 178 | before do 179 | ActsAsTenant.current_tenant = nil 180 | end 181 | 182 | it "is invalid if global record conflicts with tenant record" do 183 | expect(GlobalProject.new(name: "global foo").valid?).to be(false) 184 | end 185 | end 186 | 187 | context "with conditions in args" do 188 | it "respects conditions" do 189 | expect(GlobalProjectWithConditions.new(name: "foo").valid?).to be(false) 190 | expect(GlobalProjectWithConditions.new(name: "global foo").valid?).to be(true) 191 | end 192 | end 193 | 194 | context "with if in args" do 195 | it "respects if" do 196 | expect(GlobalProjectWithIf.new(name: "foo").valid?).to be(false) 197 | expect(GlobalProjectWithIf.new(name: "global foo").valid?).to be(true) 198 | end 199 | end 200 | end 201 | 202 | # Associations 203 | context "Associations should be correctly scoped by current tenant" do 204 | before do 205 | @project = account.projects.create!(name: "foobar") 206 | 207 | # the next line should normally be (nearly) impossible: a task assigned to a tenant project, 208 | # but the task has no tenant assigned 209 | @task1 = Task.create!(name: "no_tenant", project: @project) 210 | 211 | ActsAsTenant.current_tenant = account 212 | @task2 = @project.tasks.create!(name: "baz") 213 | 214 | @project.reload 215 | end 216 | 217 | it "should correctly set the tenant on the task created with current_tenant set" do 218 | expect(@task2.account).to eq(account) 219 | end 220 | 221 | it "should filter out the non-tenant task from the project" do 222 | expect(@project.tasks.length).to eq(1) 223 | end 224 | end 225 | 226 | it "associations can only be made with in-scope objects" do 227 | project1 = accounts(:bar).projects.create!(name: "inaccessible_project") 228 | ActsAsTenant.current_tenant = account 229 | 230 | project2 = Project.create!(name: "accessible_project") 231 | task = project2.tasks.create!(name: "bar") 232 | 233 | expect(task.update(project_id: project1.id)).to eq(false) 234 | end 235 | 236 | it "can create and save an AaT-enabled child without it having a parent" do 237 | ActsAsTenant.current_tenant = account 238 | expect(Task.new(name: "bar").valid?).to eq(true) 239 | end 240 | 241 | it "should be possible to use aliased associations" do 242 | expect(AliasedTask.create(name: "foo", project_alias: @project2).valid?).to eq(true) 243 | end 244 | 245 | it "uses the scope passed to acts_as_tenant" do 246 | account.update!(deleted_at: Time.now) 247 | manager = Manager.create!(account_id: account.id) 248 | 249 | expect(manager.valid?).to eq(true) 250 | expect(manager.account).to eq(account) 251 | end 252 | 253 | it "uses the scope passed to belongs_to when validating" do 254 | project = account.projects.create!(name: "foobar", deleted_at: Time.now) 255 | manager = Manager.new(account: account, project: project) 256 | 257 | expect(manager.valid?).to eq(true) 258 | end 259 | 260 | describe "It should be possible to use associations with foreign_key from polymorphic" do 261 | it "tenanted objects have a polymorphic association" do 262 | ActsAsTenant.current_tenant = account 263 | expect { Comment.create!(commentable: account.projects.first) }.not_to raise_error 264 | end 265 | 266 | context "tenant is polymorphic" do 267 | before do 268 | @project = Project.create!(name: "polymorphic project") 269 | ActsAsTenant.current_tenant = @project 270 | @comment = PolymorphicTenantComment.new(account: account) 271 | end 272 | 273 | it "populates commentable_type with the current tenant" do 274 | expect(@comment.polymorphic_tenant_commentable_id).to eql(@project.id) 275 | expect(@comment.polymorphic_tenant_commentable_type).to eql(@project.class.to_s) 276 | end 277 | 278 | context "with another type of tenant, same id" do 279 | before do 280 | @comment.save! 281 | @article = Article.create!(id: @project.id, title: "article title") 282 | @comment_on_article = @article.polymorphic_tenant_comments.create! 283 | end 284 | 285 | it "correctly scopes to the current tenant type" do 286 | expect(@comment_on_article).to be_persisted 287 | expect(@comment).to be_persisted 288 | expect(PolymorphicTenantComment.count).to eql(1) 289 | expect(PolymorphicTenantComment.all.first.attributes).to eql(@comment.attributes) 290 | end 291 | end 292 | end 293 | end 294 | 295 | # Additional default_scopes 296 | it "should apply both the tenant scope and the user defined default_scope, including :order" do 297 | project1 = Project.create!(name: "inaccessible") 298 | Task.create!(name: "no_tenant", project: project1) 299 | 300 | ActsAsTenant.current_tenant = account 301 | project2 = Project.create!(name: "accessible") 302 | task2 = project2.tasks.create!(name: "bar") 303 | task3 = project2.tasks.create!(name: "baz") 304 | task4 = project2.tasks.create!(name: "foo") 305 | project2.tasks.create!(name: "foobar", completed: true) 306 | 307 | tasks = Task.all 308 | 309 | expect(tasks.length).to eq(3) 310 | expect(tasks).to eq([task2, task3, task4]) 311 | end 312 | 313 | # Validates_uniqueness 314 | context "When using validates_uniqueness_to_tenant in a aat model" do 315 | before do 316 | @name = "existing_name" 317 | ActsAsTenant.current_tenant = account 318 | Project.create!(name: @name) 319 | end 320 | 321 | it "should not be possible to create a duplicate within the same tenant" do 322 | expect(Project.new(name: @name).valid?).to eq(false) 323 | end 324 | 325 | it "should be possible to create a duplicate in another tenant" do 326 | ActsAsTenant.current_tenant = accounts(:bar) 327 | expect(Project.create(name: @name).valid?).to eq(true) 328 | end 329 | end 330 | 331 | it "handles user defined scopes" do 332 | UniqueTask.create!(name: "foo", user_defined_scope: "unique_scope") 333 | expect(UniqueTask.create(name: "foo", user_defined_scope: "another_scope")).to be_valid 334 | expect(UniqueTask.create(name: "foo", user_defined_scope: "unique_scope")).not_to be_valid 335 | end 336 | 337 | context "When using validates_uniqueness_of in a NON-aat model" do 338 | it "should not be possible to create duplicates" do 339 | UnscopedModel.create!(name: "foo") 340 | expect(UnscopedModel.create(name: "foo").valid?).to eq(false) 341 | end 342 | end 343 | 344 | # ::with_tenant 345 | describe "::with_tenant" do 346 | it "should set current_tenant to the specified tenant inside the block" do 347 | ActsAsTenant.with_tenant(account) do 348 | expect(ActsAsTenant.current_tenant).to eq(account) 349 | end 350 | end 351 | 352 | it "should reset current_tenant to the previous tenant once exiting the block" do 353 | ActsAsTenant.current_tenant = account 354 | ActsAsTenant.with_tenant(accounts(:bar)) {} 355 | expect(ActsAsTenant.current_tenant).to eq(account) 356 | end 357 | 358 | it "should return the value of the block" do 359 | ActsAsTenant.current_tenant = account 360 | value = ActsAsTenant.with_tenant(accounts(:bar)) { "something" } 361 | expect(value).to eq "something" 362 | end 363 | 364 | it "should raise an error when no block is provided" do 365 | expect { ActsAsTenant.with_tenant(nil) }.to raise_error(ArgumentError, /block required/) 366 | end 367 | end 368 | 369 | describe "::without_tenant" do 370 | it "should set current_tenant to nil inside the block" do 371 | ActsAsTenant.without_tenant do 372 | expect(ActsAsTenant.current_tenant).to be_nil 373 | end 374 | end 375 | 376 | it "should set current_tenant to nil even if default_tenant is set" do 377 | old_default_tenant = ActsAsTenant.default_tenant 378 | ActsAsTenant.default_tenant = Account.create!(name: "foo") 379 | ActsAsTenant.without_tenant do 380 | expect(ActsAsTenant.current_tenant).to be_nil 381 | end 382 | ensure 383 | ActsAsTenant.default_tenant = old_default_tenant 384 | end 385 | 386 | it "should reset current_tenant to the previous tenant once exiting the block" do 387 | ActsAsTenant.current_tenant = account 388 | ActsAsTenant.without_tenant {} 389 | expect(ActsAsTenant.current_tenant).to eq(account) 390 | end 391 | 392 | it "should set test_tenant to nil inside the block" do 393 | ActsAsTenant.test_tenant = account 394 | ActsAsTenant.without_tenant do 395 | expect(ActsAsTenant.test_tenant).to be_nil 396 | end 397 | end 398 | 399 | it "should set test_tenant to nil even if default_tenant is set" do 400 | old_default_tenant = ActsAsTenant.default_tenant 401 | ActsAsTenant.default_tenant = Account.create!(name: "foo") 402 | ActsAsTenant.without_tenant do 403 | expect(ActsAsTenant.test_tenant).to be_nil 404 | end 405 | ensure 406 | ActsAsTenant.default_tenant = old_default_tenant 407 | end 408 | 409 | it "should reset test_tenant to the previous tenant once exiting the block" do 410 | ActsAsTenant.test_tenant = account 411 | ActsAsTenant.without_tenant {} 412 | expect(ActsAsTenant.test_tenant).to eq(account) 413 | end 414 | 415 | it "should return the value of the block" do 416 | value = ActsAsTenant.without_tenant { "something" } 417 | expect(value).to eq "something" 418 | end 419 | 420 | it "should raise an error when no block is provided" do 421 | expect { ActsAsTenant.without_tenant }.to raise_error(ArgumentError, /block required/) 422 | end 423 | end 424 | 425 | describe "::with_mutable_tenant" do 426 | it "should return the value of the block" do 427 | value = ActsAsTenant.with_mutable_tenant { "something" } 428 | expect(value).to eq "something" 429 | end 430 | 431 | it "should raise an error when no block is provided" do 432 | expect { ActsAsTenant.with_mutable_tenant }.to raise_error(ArgumentError, /block required/) 433 | end 434 | 435 | it "should set tenant back to immutable after the block" do 436 | ActsAsTenant.with_mutable_tenant do 437 | "something" 438 | end 439 | expect(ActsAsTenant.mutable_tenant?).to eq false 440 | end 441 | 442 | describe "mutability" do 443 | before do 444 | @account = Account.create!(name: "foo") 445 | @project = @account.projects.create!(name: "bar") 446 | end 447 | 448 | it "should allow tenant_id to change inside the block" do 449 | new_account_id = @account.id + 1 450 | expect { ActsAsTenant.with_mutable_tenant { @project.account_id = new_account_id } }.to_not raise_error 451 | expect(@project.account_id).to eq new_account_id 452 | end 453 | end 454 | end 455 | 456 | # Tenant required 457 | context "tenant required" do 458 | before do 459 | account.projects.create!(name: "foobar") 460 | allow(ActsAsTenant.configuration).to receive_messages(require_tenant: true) 461 | end 462 | 463 | it "should raise an error when no tenant is provided" do 464 | expect { Project.all }.to raise_error(ActsAsTenant::Errors::NoTenantSet) 465 | end 466 | 467 | it "should not raise an error when no tenant is provided" do 468 | expect { ActsAsTenant.without_tenant { Project.all } }.to_not raise_error 469 | end 470 | end 471 | 472 | context "no tenant required" do 473 | it "should not raise an error when no tenant is provided" do 474 | expect { Project.all }.to_not raise_error 475 | end 476 | end 477 | 478 | describe "ActsAsTenant.default_tenant=" do 479 | after(:each) do 480 | ActsAsTenant.default_tenant = nil 481 | end 482 | 483 | it "provides current_tenant" do 484 | ActsAsTenant.default_tenant = account 485 | expect(ActsAsTenant.current_tenant).to eq(account) 486 | end 487 | 488 | it "can be overridden by assignment" do 489 | ActsAsTenant.default_tenant = account 490 | ActsAsTenant.current_tenant = accounts(:bar) 491 | expect(ActsAsTenant.current_tenant).to eq(accounts(:bar)) 492 | end 493 | 494 | it "can be overridden by with_tenant" do 495 | ActsAsTenant.default_tenant = account 496 | ActsAsTenant.with_tenant accounts(:bar) do 497 | expect(ActsAsTenant.current_tenant).to eq(accounts(:bar)) 498 | end 499 | expect(ActsAsTenant.current_tenant).to eq(account) 500 | end 501 | 502 | it "doesn't override existing current_tenant" do 503 | ActsAsTenant.current_tenant = accounts(:bar) 504 | ActsAsTenant.default_tenant = account 505 | expect(ActsAsTenant.current_tenant).to eq(accounts(:bar)) 506 | end 507 | end 508 | end 509 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Environment 2 | ENV["RAILS_ENV"] = "test" 3 | 4 | require_relative "../spec/dummy/config/environment" 5 | ActiveRecord::Migrator.migrations_paths = [File.expand_path("../spec/dummy/db/migrate", __dir__)] 6 | ActiveRecord::Migration.maintain_test_schema! 7 | 8 | require "rspec/rails" 9 | 10 | RSpec.configure do |config| 11 | config.after(:each) do 12 | ActsAsTenant.current_tenant = nil 13 | ActsAsTenant.test_tenant = nil 14 | end 15 | 16 | config.fixture_path = "spec/fixtures" 17 | config.global_fixtures = :all 18 | config.use_transactional_fixtures = true 19 | config.infer_base_class_for_anonymous_controllers = true 20 | end 21 | --------------------------------------------------------------------------------