{"id":13463475,"url":"https://github.com/ErwinM/acts_as_tenant","last_synced_at":"2025-03-25T06:32:08.786Z","repository":{"id":1601705,"uuid":"2175854","full_name":"ErwinM/acts_as_tenant","owner":"ErwinM","description":"Easy multi-tenancy for Rails in a shared database setup.","archived":false,"fork":false,"pushed_at":"2024-09-06T16:48:06.000Z","size":480,"stargazers_count":1596,"open_issues_count":41,"forks_count":267,"subscribers_count":44,"default_branch":"master","last_synced_at":"2025-03-18T18:18:50.612Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ErwinM.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["excid3"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2011-08-08T20:51:14.000Z","updated_at":"2025-03-17T11:26:59.000Z","dependencies_parsed_at":"2023-07-05T15:46:14.977Z","dependency_job_id":"8e0c3e52-ffb1-4921-9bd1-0fc7adf23c45","html_url":"https://github.com/ErwinM/acts_as_tenant","commit_stats":{"total_commits":312,"total_committers":76,"mean_commits":4.105263157894737,"dds":0.6923076923076923,"last_synced_commit":"944e4991e7529e267da67fbd4389f4a3d0d2d5d1"},"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ErwinM%2Facts_as_tenant","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ErwinM%2Facts_as_tenant/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ErwinM%2Facts_as_tenant/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ErwinM%2Facts_as_tenant/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ErwinM","download_url":"https://codeload.github.com/ErwinM/acts_as_tenant/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245414432,"owners_count":20611360,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T13:00:54.085Z","updated_at":"2025-03-25T06:32:08.443Z","avatar_url":"https://github.com/ErwinM.png","language":"Ruby","funding_links":["https://github.com/sponsors/excid3"],"categories":["Web Apps, Services \u0026 Interaction","Gems","ORM/ODM Extensions","Ruby","Multi-tenancy"],"sub_categories":["Multitenancy","Multi-Tenancy"],"readme":"# Acts As Tenant\n\n[![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)\n\nRow-level multitenancy for Ruby on Rails apps.\n\nThis 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.\n\nacts_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.\n\nIn addition, acts_as_tenant:\n\n* sets the current tenant using the subdomain or allows you to pass in the current tenant yourself\n* protects against various types of nastiness directed at circumventing the tenant scoping\n* adds a method to validate uniqueness to a tenant, `validates_uniqueness_to_tenant`\n* sets up a helper method containing the current tenant\n\n**Note**: acts_as_tenant was introduced in this [blog post](https://github.com/ErwinM/acts_as_tenant/blob/master/docs/blog_post.md).\n\n**Row-level vs schema multitenancy**\n\nWhat's the difference?\n\nRow-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.\n\nSchema 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.\n\n#### 🎬 Walkthrough\n\nWant to see how it works? Check out [the ActsAsTenant walkthrough video](https://www.youtube.com/watch?v=BIyxM9f8Jus):\n\n\u003ca href=\"https://www.youtube.com/watch?v=BIyxM9f8Jus\"\u003e\n\u003cimg src=\"https://i.imgur.com/DLRPzhv.png\" width=\"300\" height=\"auto\" alt=\"ActsAsTenant Walkthrough Video\"\u003e\n\u003c/a\u003e\n\nInstallation\n------------\n\nTo use it, add it to your Gemfile:\n\n```ruby\ngem 'acts_as_tenant'\n```\n\nGetting started\n===============\n\nThere are two steps in adding multi-tenancy to your app with acts_as_tenant:\n\n1. setting the current tenant and\n2. scoping your models.\n\nSetting the current tenant\n--------------------------\n\nThere are three ways to set the current tenant:\n\n1. by using the subdomain to lookup the current tenant,\n2. by setting  the current tenant in the controller, and\n3. by setting the current tenant for a block.\n\n### Looking Up Tenants\n\n#### By Subdomain to lookup the current tenant\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  set_current_tenant_by_subdomain(:account, :subdomain)\nend\n```\n\nThis 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.\n\nBy default, the *last* subdomain will be used for lookup. Pass in `subdomain_lookup: :first` to use the first subdomain instead.\n\n#### By Domain to lookup the current tenant\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  set_current_tenant_by_subdomain_or_domain(:account, :subdomain, :domain)\nend\n```\n\nYou 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.\n\nBy default, the *last* subdomain will be used for lookup. Pass in `subdomain_lookup: :first` to use the first subdomain instead.\n\n#### Manually using before_action\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  set_current_tenant_through_filter\n  before_action :your_method_that_finds_the_current_tenant\n\n  def your_method_that_finds_the_current_tenant\n    current_account = Account.find_it\n    set_current_tenant(current_account)\n  end\nend\n```\n\nSetting 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.\n\nIf you are setting the tenant in a specific controller (except `application_controller`), it should to be included **AT THE TOP** of the file.\n\n```ruby\nclass MembersController \u003c ActionController::Base\n  set_current_tenant_through_filter\n  before_action :set_tenant\n  before_action :set_member, only: [:show, :edit, :update, :destroy]\n\n  def set_tenant\n    set_current_tenant(current_user.account)\n  end\nend\n```\n\nThis allows the tenant to be set before any other code runs so everything is within the current tenant.\n\n### Setting the current tenant for a block\n\n```ruby\nActsAsTenant.with_tenant(current_account) do\n  # Current tenant is set for all code in this block\nend\n```\n\nThis approach is useful when running background processes for a specified tenant. For example, by putting this in your worker's run method,\nany code in this block will be scoped to the current tenant. All methods that set the current tenant are thread safe.\n\n**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.\n\n### Disabling tenant checking for a block\n\n```ruby\nActsAsTenant.without_tenant do\n  # Tenant checking is disabled for all code in this block\nend\n```\n\nThis is useful in shared routes such as admin panels or internal dashboards when `require_tenant` option is enabled throughout the app.\n\n### Allowing tenant updating for a block\n\n```ruby\nActsAsTenant.with_mutable_tenant do\n  # Tenant updating is enabled for all code in this block\nend\n```\n\nThis 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.\n\n### Require tenant to be set always\n\nIf 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.\n\nScoping your models\n-------------------\n\n```ruby\nclass AddAccountToUsers \u003c ActiveRecord::Migration\n  def up\n    add_column :users, :account_id, :integer\n    add_index  :users, :account_id\n  end\nend\n\nclass User \u003c ActiveRecord::Base\n  acts_as_tenant(:account)\nend\n```\n\n`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**.\n\nSome examples to illustrate this behavior:\n\n```ruby\n# This manually sets the current tenant for testing purposes. In your app this is handled by the gem.\nActsAsTenant.current_tenant = Account.find(3)\n\n# All searches are scoped by the tenant, the following searches will only return objects\n# where account_id == 3\nProject.all =\u003e  # all projects with account_id =\u003e 3\nProject.tasks.all #  =\u003e all tasks with account_id =\u003e 3\n\n# New objects are scoped to the current tenant\n@project = Project.new(:name =\u003e 'big project')    # =\u003e \u003c#Project id: nil, name: 'big project', :account_id: 3\u003e\n\n# It will not allow the creation of objects outside the current_tenant scope\n@project.account_id = 2\n@project.save                                     # =\u003e false\n\n# It will not allow association with objects outside the current tenant scope\n# Assuming the Project with ID: 2 does not belong to Account with ID: 3\n@task = Task.new  # =\u003e \u003c#Task id: nil, name: nil, project_id: nil, :account_id: 3\u003e\n```\n\nActs_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`.\n\n### Validating attribute uniqueness\n\nIf you need to validate for uniqueness, chances are that you want to scope this validation to a tenant. You can do so by using:\n\n```ruby\nvalidates_uniqueness_to_tenant :name, :email\n```\n\nAll options available to Rails' own `validates_uniqueness_of` are also available to this method.\n\n### Custom foreign_key\n\nYou can explicitly specifiy a foreign_key for AaT to use should the key differ from the default:\n\n```ruby\nacts_as_tenant(:account, :foreign_key =\u003e 'accountID') # by default AaT expects account_id\n```\n\n### Custom primary_key\n\nYou can also explicitly specifiy a primary_key for AaT to use should the key differ from the default:\n\n```ruby\nacts_as_tenant(:account, :primary_key =\u003e 'primaryID') # by default AaT expects id\n```\n\n### Has and belongs to many\n\nYou can scope a model that is part of a HABTM relationship by using the `through` option.\n\n```ruby\nclass Organisation \u003c ActiveRecord::Base\n  has_many :organisations_users\n  has_many :users, through: :organisations_users\nend\n\nclass User \u003c ActiveRecord::Base\n  has_many :organisations_users\n  acts_as_tenant :organisation, through: :organisations_users\nend\n\nclass OrganisationsUser \u003c ActiveRecord::Base\n  belongs_to :user\n  acts_as_tenant :organisation\nend\n```\n\nConfiguration options\n---------------------\n\nAn initializer can be created to control (currently one) option in ActsAsTenant. Defaults\nare shown below with sample overrides following. In `config/initializers/acts_as_tenant.rb`:\n\n```ruby\nActsAsTenant.configure do |config|\n  config.require_tenant = false # true\n\n  # Customize the query for loading the tenant in background jobs\n  config.job_scope = -\u003e{ all }\nend\n```\n\n* `config.require_tenant` when set to true will raise an ActsAsTenant::NoTenant error whenever a query is made without a tenant set.\n\n`config.require_tenant` can also be assigned a lambda that is evaluated at run time. For example:\n\n```ruby\nActsAsTenant.configure do |config|\n  config.require_tenant = lambda do\n    if $request_env.present?\n      return false if $request_env[\"REQUEST_PATH\"].start_with?(\"/admin/\")\n    end\n  end\nend\n```\n\n`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`.\n\nWhen 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`:\n\n```ruby\nSET_TENANT_PROC = lambda do\n  if defined?(Rails::Console)\n    puts \"\u003e ActsAsTenant.current_tenant = Account.first\"\n    ActsAsTenant.current_tenant = Account.first\n  end\nend\n\nRails.application.configure do\n  if Rails.env.development?\n    # Set the tenant to the first account in development on load\n    config.after_initialize do\n      SET_TENANT_PROC.call\n    end\n\n    # Reset the tenant after calling 'reload!' in the console\n    ActiveSupport::Reloader.to_complete do\n      SET_TENANT_PROC.call\n    end\n  end\nend\n```\n\nbelongs_to options\n------------------\n\n`acts_as_tenant :account` includes the belongs_to relationship.\nSo when using acts_as_tenant on a model, do not add `belongs_to :account` alongside `acts_as_tenant :account`:\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  acts_as_tenant(:account) # YES\n  belongs_to :account # REDUNDANT\nend\n```\n\nYou can add the following `belongs_to` options to `acts_as_tenant`:\n`:foreign_key, :class_name, :inverse_of, :optional, :primary_key, :counter_cache, :polymorphic, :touch`\n\nExample: `acts_as_tenant(:account, counter_cache: true)`\n\nBackground Processing libraries\n-------------------------------\n\nActsAsTenant supports\n\n- ActiveJob - ActsAsTenant will automatically save the current tenant in ActiveJob arguments and set it when the job runs.\n\n- [Sidekiq](//sidekiq.org/)\nAdd the following code to `config/initializers/acts_as_tenant.rb`:\n\n```ruby\nrequire 'acts_as_tenant/sidekiq'\n```\n\n- DelayedJob - [acts_as_tenant-delayed_job](https://github.com/nunommc/acts_as_tenant-delayed_job)\n\nTesting\n---------------\n\nIf 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.\n\nTo 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:\n\n```ruby\n# test.rb\nrequire_dependency 'acts_as_tenant/test_tenant_middleware'\n\nRails.application.configure do\n  config.middleware.use ActsAsTenant::TestTenantMiddleware\nend\n```\n```ruby\n# spec_helper.rb\nconfig.before(:suite) do |example|\n  # Make the default tenant globally available to the tests\n  $default_account = Account.create!\nend\n\nconfig.before(:each) do |example|\n  if example.metadata[:type] == :request\n    # Set the `test_tenant` value for integration tests\n    ActsAsTenant.test_tenant = $default_account\n  else\n    # Otherwise just use current_tenant\n    ActsAsTenant.current_tenant = $default_account\n  end\nend\n\nconfig.after(:each) do |example|\n  # Clear any tenancy that might have been set\n  ActsAsTenant.current_tenant = nil\n  ActsAsTenant.test_tenant = nil\nend\n```\nBug reports \u0026 suggested improvements\n------------------------------------\n\nIf you have found a bug or want to suggest an improvement, please use our issue tracked at:\n\n[github.com/ErwinM/acts_as_tenant/issues](http://github.com/ErwinM/acts_as_tenant/issues)\n\nIf 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.\n\nContributing to this gem\n------------------------\n\nWe 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.\n\n1. Fork the repo\n2. Make changes\n3. Run test suite with `bundle exec appraisal`\n4. Run `bundle exec standardrb` to standardize code formatting\n5. Submit a PR\n\nAuthor \u0026 Credits\n----------------\n\nacts_as_tenant is written by Erwin Matthijssen \u0026 Chris Oliver.\n\nThis gem was inspired by Ryan Sonnek's [Multitenant](https://github.com/wireframe/multitenant) gem and its use of default_scope.\n\nLicense\n-------\n\nCopyright (c) 2011 Erwin Matthijssen, released under the MIT license\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FErwinM%2Facts_as_tenant","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FErwinM%2Facts_as_tenant","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FErwinM%2Facts_as_tenant/lists"}