{"id":13484349,"url":"https://github.com/jekuno/milia","last_synced_at":"2025-12-18T04:02:51.567Z","repository":{"id":56883697,"uuid":"2541562","full_name":"jekuno/milia","owner":"jekuno","description":"Easy multi-tenanting for Rails5 (or Rails4) + Devise","archived":false,"fork":false,"pushed_at":"2017-07-26T15:23:36.000Z","size":2978,"stargazers_count":340,"open_issues_count":13,"forks_count":67,"subscribers_count":18,"default_branch":"master","last_synced_at":"2025-12-17T08:04:40.554Z","etag":null,"topics":["devise","gem","milia","multi-tenanting","rails","ruby"],"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/jekuno.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2011-10-09T06:21:43.000Z","updated_at":"2025-12-03T07:32:36.000Z","dependencies_parsed_at":"2022-08-21T00:20:16.613Z","dependency_job_id":null,"html_url":"https://github.com/jekuno/milia","commit_stats":null,"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"purl":"pkg:github/jekuno/milia","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jekuno%2Fmilia","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jekuno%2Fmilia/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jekuno%2Fmilia/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jekuno%2Fmilia/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jekuno","download_url":"https://codeload.github.com/jekuno/milia/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jekuno%2Fmilia/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27790846,"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","status":"online","status_checked_at":"2025-12-18T02:00:09.725Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["devise","gem","milia","multi-tenanting","rails","ruby"],"created_at":"2024-07-31T17:01:22.911Z","updated_at":"2025-12-18T04:02:51.522Z","avatar_url":"https://github.com/jekuno.png","language":"Ruby","funding_links":[],"categories":["Ruby","ORM/ODM Extensions"],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/jekuno/milia.svg?branch=master)](https://travis-ci.org/jekuno/milia) [![Dependency Status](https://www.versioneye.com/user/projects/589071e96a0b7c003d32536e/badge.svg?style=flat-square)](https://www.versioneye.com/user/projects/589071e96a0b7c003d32536e)\n\n# milia\n\nMilia is a multi-tenanting gem for Ruby on Rails applications. Milia supports Devise.\n\nYou are viewing the documentation for using milia with **Rails 5.x** applications.  \nIf you want to use Rails 4.2.x instead please switch to [the Rails 4.x branch](https://github.com/jekuno/milia/tree/rails4-support).\n\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [Intro](#intro)\n- [Milia highlights](#milia-highlights)\n- [Basic concepts](#basic-concepts)\n  - [Tenants == Organizations with Users / Members](#tenants--organizations-with-users--members)\n  - [Tenanted models](#tenanted-models)\n  - [Universal models](#universal-models)\n  - [Join tables](#join-tables)\n- [Tutorials + Documentation](#tutorials--documentation)\n- [Sample app](#sample-app)\n- [Installation](#installation)\n  - [Adding milia to a new application](#adding-milia-to-a-new-application)\n  - [Add milia to an existing application](#add-milia-to-an-existing-application)\n    - [Go step by Step](#go-step-by-step)\n  - [Bare minimal manual setup](#bare-minimal-manual-setup)\n    - [Application controller](#application-controller)\n    - [Setup base models](#setup-base-models)\n      - [Designate which model determines the account](#designate-which-model-determines-the-account)\n      - [Designate which model determines the tenant](#designate-which-model-determines-the-tenant)\n      - [Clean up tenant references](#clean-up-tenant-references)\n    - [Setup your custom models](#setup-your-custom-models)\n      - [Designate tenanted models](#designate-tenanted-models)\n      - [Designate universal models](#designate-universal-models)\n- [Role based authorization](#role-based-authorization)\n- [Milia API Reference Manual](#milia-api-reference-manual)\n  - [Get current tenant](#get-current-tenant)\n  - [Change current tenant](#change-current-tenant)\n    - [Iterate over tenants](#iterate-over-tenants)\n    - [Rails Console](#rails-console)\n  - [Milia callbacks](#milia-callbacks)\n- [Security / Caution](#security--caution)\n- [Contributing to milia](#contributing-to-milia)\n  - [Testing milia](#testing-milia)\n- [Changelog](#changelog)\n- [License](#license)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Intro\n* Milia is a solid choice for (SaaS) applications which are used by more than one tenant (i.e. companies or organizations) and is tailored for common use cases of multi-tenanted applications.\n* Milia allows to save the data of all tenants in the same database and enforces row based separation of the tenant data.\n* Milia uses the [devise gem](https://github.com/plataformatec/devise) for user authentication and registration.\n\n## Milia highlights\n* Transparent to the main application code\n* Symbiotic with user authentication (supports [devise](https://github.com/plataformatec/devise) out of the box)\n* Raises exceptions upon attempted illegal access\n* Enforces tenanting (not allow sloppy access to all tenant records)\n* Allows application flexibility upon new tenant sign-up, usage of eula information, etc\n* As non-invasive (as possible) to Rails code\n* Uses row-based tenanting (for [good reasons](README_DETAILS.md#row-based-vs-schema-based-tenanting))\n* Uses default_scope to enforce tenanting\n* **See Milia in action in the [Sample App](#sample-app)**\n\n## Basic concepts\n\n### Tenants == Organizations with Users / Members\nA tenant is an organization with many members (users).\nInitially a user creates a new organization (tenant) and becomes its first member (and usually admin).\nThen he invites further members who can then login and join the tenant.\nMilia ensures that users can only access data of their own tenant (organization).\n\n\n### Tenanted models\nModels which belong to a certain tenant (organization).  \nAdd \u003ci\u003eacts_as_tenant\u003c/i\u003e to the model body to activate tenanting for this model.    \nMost of your tables (except for pure join tables, users, and tenants) should be tenanted.\nEvery record of a tenanted table needs to have a `tenant_id` set. Milia takes care of this.\n\n### Universal models\nModels which aren't specific to a tenant (organization) but have system wide relevance.\nAdd \u003ci\u003eacts_as_universal\u003c/i\u003e to the model body to mark them as universal models.  \nUniversal tables \u003ci\u003enever\u003c/i\u003e contain critical user/company information.\nThe devise user table \u003ci\u003emust\u003c/i\u003e be universal and should only contain email, encrypted password, and devise-required data.\nAll other user data (name, phone, address, etc) should be broken out into a tenanted table called `members` (`Member belongs_to :user`, `User has_one :member`).\nThe same applies for organization (account or company) information.\nA record of a universal table must have `tenant_id` set to nil. Milia takes care of this.\n\n### Join tables\nPure join tables (has_and_belongs_to_many HABTM associations) are neither Universal nor Tenanted.\n\n\n## Tutorials + Documentation\n* Up to date starting point is the README you're currently viewing.\n* For more details on token authentication, exceptions, callbacks, devise setup etc. have a look at the [additional README_DETAILS](README_DETAILS.md).\n* Tutorial: There's a good [Milia tutorial](http://myrailscraft.blogspot.com/2013/05/multi-tenanting-ruby-on-rails_3982.html) at myrailscraft.\n* Check out the general [three-part blog post]((http://myrailscraft.blogspot.com/2013/05/multi-tenanting-ruby-on-rails.html).) about _Multi-tenanting Ruby on Rails Applications on Heroku_.\n\n\n## Sample app\nYou can get a sample app up and running yourself using an easy, interactive RailsApp generator and an according Milia generator. If desired the generator can also prepare everything for you to push your app to **Heroku**.\nThe sample app uses devise with the invite_member capability (and optionally recaptcha for new account sign-ups).\nIt creates skeleton user, tenant and member models.\n\nSimply follow the following steps:\n\n```\nmkdir milia-sample-app\ncd milia-sample-app\nrvm use ruby-2.3.1@milia-sample-app --ruby-version --create\ngem install rails\nrails new . -m https://raw.github.com/RailsApps/rails-composer/master/composer.rb\n```\n\nAn interactive setup starts which asks you some questions.\n* Choose \"Build a RailsApps example application\"\n* Choose \"rails-devise\" as the example template\n* Choose Template engine \"HAML\"\n* Choose \"Devise with default modules\"\n* Choose the other options depending on your needs\n\nAfter the setup finished add to your `Gemfile`:  \n`gem 'milia'`\n\nInstall milia:\n`bundle install`\n\nIn `app/controllers/application_controller.rb` add the following line immediately after `protect_from_forgery`:  \n`  before_action :authenticate_tenant!`\n\nRun the following commands:\n```\nspring stop\nrails g milia:install --org_email='mail@your-provider.de' --skip_devise_generators=true\n```\n\n* Remove lower line \"before_action :authenticate_tenant!\" which has been added to `app/controllers/application_controller.rb` by the milia generator.  \n* Remove the lines `@extend .text-xs-center;` (if any) from the file `1st_load_framework.css.scss`.\n* Remove the file `app/views/devise/registrations/new.html.erb`\n\nSetup the database:\n`rake db:drop db:create db:migrate`\n\nStart the server:\n`rails server`\n\nOpen http://127.0.0.1:3000/users/sign_up in your browser.\nYou're ready to go!\n\n### Previous sample app\nFor your reference: An outdated milia+devise sample app can be found at [https://github.com/dsaronin/sample-milia-app](https://github.com/dsaronin/sample-milia-app)\nand is live on Heroku: http://sample-milia.herokuapp.com  \nThe according instructions on how to generate this sample app can be found at [doc/sample.sh](doc/sample.sh).\n\nThere are also outdated step-by-step instructions for setting this sample app up manually at [doc/manual_sample.sh](doc/manual_sample.sh).\n  - Step 1: Sample with simple devise only\n  - Step 2: Add milia for complete tenanting\n  - Step 3: Add invite_member capability\n\n\n## Installation\n### Adding milia to a new application\nThe quickest way:\nFollow the simple instructions of the chapter [Sample App](#sample-app) to generate a new app which uses devise+milia.\n\n\n### Add milia to an existing application\nThe recommended way to add multi-tenanting with milia to an existing app\nis to bring up the [Sample App](#sample-app), get it working and then graft your app onto it.\nThis ensures that the Rails+Devise setup works correctly.\n\n#### Go step by Step\nDon't try to change everything at once!\nDon't be a perfectionist and try to bring up a fully written app at once!\n\nJust follow the instructions for creating the sample, exactly, step-by-step.\nGet the basics working. Then change, adapt, and spice to taste.\n\n\n### Bare minimal manual setup\n(If you generated a [Sample App](Sample App) all of the following steps have been done already.)\n\nAdd to your Gemfile:\n\n```ruby\n  gem 'milia', '~\u003e1.3'\n```\n\nThen run the milia generator:\n```\n  $ bundle install\n  $ rails g milia:install --org_email='\u003cyour smtp email for dev work\u003e'\n```\n\nNote: The milia generator has an option to specify an email address to be used for sending emails for\nconfirmation and account activation.\n\nFor an in depth explanation of what the generator does have a look at \n[README_DETAILS](README_DETAILS.md).\n\nMake any changes required to the generated migrations, then:\n```\n  $ rake db:create\n  $ rake db:migrate\n```\n\n#### Application controller\n\n\u003ci\u003eapp/controllers/application_controller.rb\u003c/i\u003e\nadd the following line IMMEDIATELY AFTER line 4 protect_from_forgery\n\n```\n  before_action :authenticate_tenant!   # authenticate user and sets up tenant\n\n  rescue_from ::Milia::Control::MaxTenantExceeded,   :with =\u003e :max_tenants\n  rescue_from ::Milia::Control::InvalidTenantAccess, :with =\u003e :invalid_tenant\n```\n\n\n\n#### Setup base models\n\n* Necessary models: `User`, `Tenant`\n* Necessary migrations: `user`, `tenant`, `tenants_users` (join table)\n\n\nGenerate the tenant migration\n\n```\n  $ rails g model tenant tenant:references name:string:index\n```\n\nGenerate the tenants_users join table migration\n\n```\n  $ rails g migration CreateTenantsUsersJoinTable tenants users\n```\n\nEDIT: \u003ci\u003edb/migrate/20131119092046_create_tenants_users_join_table.rb\u003c/i\u003e\nthen uncomment the first index line as follows:\n`t.index [:tenant_id, :user_id]`\n\n*ALL* models require a tenanting field, whether they are to be universal or to\nbe tenanted. So make sure you have migrations for all models which add the following:\n\n\u003ci\u003edb/migrate/xxxxxxx_create_model_xyz.rb\u003c/i\u003e\n\n```\n  t.references :tenant\n```\n\nTenanted models also require indexes for the tenant field.\n\n```\n  add_index :\u003ctablename\u003e, :tenant_id\n```\n\nBUT: Do not add any \u003ci\u003ebelongs_to  :tenant\u003c/i\u003e statements into any of your\nmodels. milia will do that for all. However it makes sense to add\ninto your \u003ci\u003eapp/models/tenant.rb\u003c/i\u003e file one line per tenanted model\nsuch as the following (replacing \u003cmodel\u003e with your model's name):\n\n```\n  has_many  :\u003cmodel\u003es, dependent: :destroy\n```\n\nThe reason for this is that if you wish to have a master destroy tenant action,\nit will also remove all related tenanted tables and records automatically.\n\nDo NOT add a reference to the user model such as\n```\n  has_many  :users, dependent: :destroy\n```\nbecause it produces errors.\n\n\n\n##### Designate which model determines the account\n\nAdd the following acts_as_... to designate which model will be used as the key\ninto tenants_users to find the tenant for a given user.\nOnly designate one model in this manner e.g.:\n\n\u003ci\u003eapp/models/user.rb\u003c/i\u003e\n\n```ruby\n  class User \u003c ActiveRecord::Base\n\n    acts_as_universal_and_determines_account\n\n  end\n```\n\n##### Designate which model determines the tenant\n\nAdd `acts_as_universal_and_determines_tenant` to designate which model will be used as the\ntenant model. It is this id field which designates the tenant for an entire \ngroup of users which exist within a single tenanted domain.\nOnly designate one model in this manner.\n\n\u003ci\u003eapp/models/tenant.rb\u003c/i\u003e\n\n```ruby\n  class Tenant \u003c ActiveRecord::Base\n    \n    acts_as_universal_and_determines_tenant\n    \n  end \n```\n\n##### Clean up tenant references\n\nClean up any generated belongs_to tenant references in all models which the generator might have generated \n(both \u003ci\u003eacts_as_tenant\u003c/i\u003e and \u003ci\u003eacts_as_universal\u003c/i\u003e).\n\n#### Setup your custom models\n##### Designate tenanted models\n\nAdd `acts_as_tenant` to *ALL* models which are to be tenanted.\nExample for a Post model:\n  \n\u003ci\u003eapp/models/post.rb\u003c/i\u003e\n\n```ruby\n  class Post \u003c ActiveRecord::Base\n    \n    acts_as_tenant\n  \n  end\n```\n\n##### Designate universal models\n\nAdd `acts_as_universal` to *ALL* models which are to be universal.\n\n\n## Role based authorization\nYou can use any role based authorization you like, e.g. the [rolify gem](https://github.com/RolifyCommunity/rolify)\nwith [cancancan](https://github.com/CanCanCommunity/cancancan), [authority](https://github.com/nathanl/authority)\nor [pundit](https://github.com/elabs/pundit).\n\n\n\n## Milia API Reference Manual\n\n### Get current tenant\nFrom models call `Tenant.current_tenant` or `Tenant.current_tenant_id` to get\nthe current tenant.\n\n### Change current tenant\nCall `set_current_tenant( tenant_id )` from controllers.\n(for example, if a member can belong to multiple tenants and wants to switch between them).\nNOTE: you will normally NEVER do this manually at the beginning of a session.\nMilia does this automatically during `authorize_tenant!`.\n\nFrom background job, migration, rake task or console you can use `Tenant.set_current_tenant(tenant)`.\n`tenant` can either be a tenant object or an integer tenant_id; anything else will raise\nan exception.\n\n**Use with caution!** Normally tenants should never be changed from within models.\nIt is only useful and safe when performed at the start of a background job (DelayedJob#perform), rake task, migration or start of rails console.\n\n#### Iterate over tenants\nTo iterate over all instances of a certain model for all tenants do the following:  \n```ruby\nTenant.find_each do |tenant|\n  Tenant.set_current_tenant(tenant)\n  Animal.update_all alive: true\nend\n```\n\n#### Rails Console\nNote that even when running the console, (`rails console`) it will be run in \nmulti-tenanting mode. Call `Tenant.set_current_tenant(tenant_id)` accordingly.\n\n### Milia callbacks\nIn some applications, you will want to set up commonly used\nvariables used throughout your application, after a user and a \ntenant have been established and authenticated.\nThis is optional and if the callback is missing, nothing will happen.\n\n\u003ci\u003eapp/controllers/application_controller.rb\u003c/i\u003e\n\n```ruby\n  def callback_authenticate_tenant\n    # set_environment or whatever else you need for each valid session\n  end\n```\n\n\n## Security / Caution\n* Milia designates a default_scope for all models (both universal and tenanted). Rails merges default_scopes if you use multiple default_scope declarations in your model, see [ActiveRecord Docs](http://api.rubyonrails.org/classes/ActiveRecord/Scoping/Default/ClassMethods.html#method-i-default_scope). However by unscoping via [unscoped](http://apidock.com/rails/ActiveRecord/Scoping/Default/ClassMethods/unscoped) you can accidentally remove tenant scoping from records. Therefore we strongly recommend to **NOT USE default_scope** at all.\n* Milia uses Thread.current[:tenant_id] to hold the current tenant for the existing Action request in the application.\n* SQL statements executed outside the context of ActiveRecord pose a potential danger; the current milia implementation does not extend to the DB connection level and so cannot enforce tenanting at this point.\n* The tenant_id of a universal model will always be forced to nil.\n* The tenant_id of a tenanted model will be set to the current_tenant of the current_user upon creation.\n* HABTM (has_and_belongs_to_many) associations don't have models; they shouldn't have id fields\n  (setup as below) nor any field other than the joined references; they don't have a tenant_id field;\n  rails will invoke the default_scope of the appropriate joined table which does have a tenant_id field.\n* Your code should never try to change or set the `tenant_id` of a record manually.\n   * milia will not allow it\n   * milia will check for deviance\n   * milia will raise exceptions if it's wrong and\n   * milia will override it to maintain integrity.\n* **You use milia solely at your own risk!** \n  * When working with multi-tenanted applications you handle lots of data of several organizations/companies which means a special responsibility for protecting the data as well. Do in-depth security tests prior to publishing your application.\n\n\n\n## Contributing to milia\n\n* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet\n* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it\n* Fork the project\n* Start a feature/bugfix branch\n* Commit and push until you are happy with your contribution\n* Make sure to add tests for it. This is important so we don't break the feature in a future version unintentionally.\n* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so we can cherry-pick around it.\n\n### Testing milia\nFor instructions on how to run and write tests for milia please consider the [README for testing](./test/README.md)\n\n## Changelog\nSee [CHANGELOG.md](CHANGELOG.md) for changes and upgrade instructions.\n\n\n## License\nSee LICENSE.txt for further details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjekuno%2Fmilia","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjekuno%2Fmilia","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjekuno%2Fmilia/lists"}