Set up a basic multi-tenant architecture in Rails without gem - 2
Tags: ruby, rails, architecture
Published: 2023-02-03 12:56:14

Please go through this article
[Multi tenancy with Apartment Gem](https://dev.to/harsh_u115/set-up-a-basic-multi-tenant-architecture-in-rails-7-4445)

A simple multi-tenancy architecture in Rails without using the apartment gem can be implemented using a subdomain-based approach. Here's an outline of the steps:

Set up your Rails app with subdomains routing:

```ruby
# config/routes.rb
Rails.application.routes.draw do
  constraints(subdomain: /.+/) do
    resources :tenants, only: [:show]
    resources :posts, only: [:index, :show]
    root to: 'tenants#show'
  end
  root to: 'home#index'
end
```

Create a Tenant model to store information about each tenant:

```ruby
# app/models/tenant.rb
class Tenant < ApplicationRecord
  validates :subdomain, presence: true, uniqueness: true
  has_many :posts
end
```
Create a middleware to set the current tenant based on the subdomain:

```ruby
# app/middleware/current_tenant.rb
class CurrentTenant
  def initialize(app)
    @app = app
  end

  def call(env)
    tenant = Tenant.find_by(subdomain: request.subdomain)
    if tenant
      RequestStore.store[:tenant] = tenant
      @app.call(env)
    else
      [404, { 'Content-Type' => 'text/plain' }, ['Tenant not found.']]
    end
  end

  private

  def request
    @request ||= Rack::Request.new(env)
  end
end
```
Use the middleware in your Rails app:

```ruby
# config/application.rb
config.middleware.use CurrentTenant
```

Scope your models to the current tenant:

```ruby
# app/models/post.rb
class Post < ApplicationRecord
  belongs_to :tenant

  default_scope -> { where(tenant_id: RequestStore[:tenant].id) }
end
```

Use the tenant data in your controllers and views:

```ruby
# app/controllers/tenants_controller.rb
class TenantsController < ApplicationController
  def show
    @tenant = RequestStore[:tenant]
  end
end
```

```ruby
# app/views/tenants/show.html.erb
<h1><%= @tenant.name %></h1>
<%= link_to 'Posts', posts_path %>
```

This implementation provides a basic setup for a multi-tenancy architecture in Rails, with the tenant information being set based on the subdomain, and the models being scoped to the current tenant. You can further add error handling and security measures as required.

Example Request: **GET http://tenant1.example.com/posts**

Example Response:

```ruby
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...

<h1>Tenant 1</h1>
<a href="/posts">Posts</a>
```

**2nd Example**

```ruby
class Tenant < ApplicationRecord
  has_many :users
end

class User < ApplicationRecord
  belongs_to :tenant
end

```

```ruby
class TenantMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    tenant = Tenant.find_by(subdomain: request.subdomain)
    if tenant
      Current.tenant = tenant
    else
      raise ActiveRecord::RecordNotFound
    end

    @app.call(env)
  end
end

```


Add a scope to your User model:

```ruby
class User < ApplicationRecord
  belongs_to :tenant
  default_scope -> { where(tenant: Current.tenant) }
end
```

Configure your database connection:

```ruby
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  establish_connection(ENV.fetch("#{Rails.env}_DATABASE_URL"))
end

class Tenant < ApplicationRecord
  establish_connection("#{Rails.env}_tenant_#{id}")
  has_many :users
end

class User < ApplicationRecord
  belongs_to :tenant
  default_scope -> { where(tenant: Current.tenant) }
end
```

Now, the application will automatically switch database connections based on the subdomain in the request URL. This way, each tenant will have their own isolated data.

Example request-response:

Request:
`GET http://tenant1.example.com/users`

Response -:

```ruby
[
  {
    "id": 1,
    "name": "User 1",
    "email": "user1@example.com"
  },
  {
    "id": 2,
    "name": "User 2",
    "email": "user2@example.com"
  }
]
```

**3rd Example**

1. First, you'll need to create a model to represent the tenant. For example:

```ruby
class Tenant < ApplicationRecord
  has_many :photos

  validates :name, presence: true, uniqueness: true
end
```
2. Next, you'll need to create a model to represent the photos that are uploaded by each tenant. For example:

```ruby
class Photo < ApplicationRecord
  belongs_to :tenant

  has_one_attached :image

  validates :image, presence: true
end
```
3. You'll need to create a mechanism to set the tenant context for each request. One way to do this is to use a `before_action `in your `ApplicationController`. For example:

```ruby
class ApplicationController < ActionController::Base
  before_action :set_tenant

  private

  def set_tenant
    tenant = Tenant.find_by(subdomain: request.subdomain)
    Tenant.set_current_tenant(tenant)
  end
end
```

4.In your controller, you'll need to handle the file attachment and save it to the database. For example:

```ruby
class PhotosController < ApplicationController
  def new
    @photo = Photo.new
  end

  def create
    @photo = Tenant.current_tenant.photos.new(photo_params)

    if @photo.save
      redirect_to @photo, notice: "Photo was successfully uploaded."
    else
      render :new
    end
  end

  private

  def photo_params
    params.require(:photo).permit(:image)
  end
end
```
5. In your routes, you'll need to specify the tenant_id in the URL to ensure that each photo is associated with the correct tenant. For example:

```ruby
constraints subdomain: /^[\w-]+/ do
  resources :photos, only: [:new, :create]
end

```

6. Finally, you'll need to add a form field for the file attachment in your view. For example:

```ruby
<%= form_with model: @photo, local: true do |form| %>
  <% if @photo.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@photo.errors.count, "error") %> prohibited this photo from being saved:</h2>

      <ul>
        <% @photo.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :image %>
    <%= form.file_field :image %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
```

Here's an example of an HTTP request and response for uploading a photo for a tenant with subdomain "tenant1":

Request -:
```ruby
POST http://tenant1.yourdomain.com/photos
Content-Type: multipart/form-data

photo[image]: (binary data for the photo image file)

```

Response -:
```ruby
HTTP/1.1 201 Created
Content-Type: application/json

{
  "status": "success",
  "message": "Photo was successfully uploaded."
}

```

Here's an example of an HTTP request and response for uploading a photo:

```ruby
#Request -:

POST /tenants/1/photos
Content-Type: multipart/form-data

photo[image]: (binary data for the photo image file)
```

```ruby
#Response -:

HTTP/1.1 201 Created
Content-Type: application/json

{
  "status": "success",
  "message": "Photo was successfully uploaded."
}
```