Set up a basic multi-tenant architecture in Rails without gem – 2

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:

# config/routes.rb
Rails.application.ro…


This content originally appeared on DEV Community 👩‍💻👨‍💻 and was authored by Harsh patel

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:

# 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:

# 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:

# 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:

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

Scope your models to the current tenant:

# 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:

# app/controllers/tenants_controller.rb
class TenantsController < ApplicationController
  def show
    @tenant = RequestStore[:tenant]
  end
end
# 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:

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

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

2nd Example
Add a scope to your User model:

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

Configure your database connection:

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 -:

[
  {
    "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:
class Tenant < ApplicationRecord
  has_many :photos

  validates :name, presence: true, uniqueness: true
end
  1. Next, you'll need to create a model to represent the photos that are uploaded by each tenant. For example:
class Photo < ApplicationRecord
  belongs_to :tenant

  has_one_attached :image

  validates :image, presence: true
end
  1. 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_actionin your ApplicationController. For example:
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:

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
  1. 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:
constraints subdomain: /^[\w-]+/ do
  resources :photos, only: [:new, :create]
end

  1. Finally, you'll need to add a form field for the file attachment in your view. For example:
<%= 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 -:

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

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

Response -:

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:

#Request -:

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

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

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

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


This content originally appeared on DEV Community 👩‍💻👨‍💻 and was authored by Harsh patel


Print Share Comment Cite Upload Translate Updates
APA

Harsh patel | Sciencx (2023-02-03T12:56:14+00:00) Set up a basic multi-tenant architecture in Rails without gem – 2. Retrieved from https://www.scien.cx/2023/02/03/set-up-a-basic-multi-tenant-architecture-in-rails-without-gem-2/

MLA
" » Set up a basic multi-tenant architecture in Rails without gem – 2." Harsh patel | Sciencx - Friday February 3, 2023, https://www.scien.cx/2023/02/03/set-up-a-basic-multi-tenant-architecture-in-rails-without-gem-2/
HARVARD
Harsh patel | Sciencx Friday February 3, 2023 » Set up a basic multi-tenant architecture in Rails without gem – 2., viewed ,<https://www.scien.cx/2023/02/03/set-up-a-basic-multi-tenant-architecture-in-rails-without-gem-2/>
VANCOUVER
Harsh patel | Sciencx - » Set up a basic multi-tenant architecture in Rails without gem – 2. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2023/02/03/set-up-a-basic-multi-tenant-architecture-in-rails-without-gem-2/
CHICAGO
" » Set up a basic multi-tenant architecture in Rails without gem – 2." Harsh patel | Sciencx - Accessed . https://www.scien.cx/2023/02/03/set-up-a-basic-multi-tenant-architecture-in-rails-without-gem-2/
IEEE
" » Set up a basic multi-tenant architecture in Rails without gem – 2." Harsh patel | Sciencx [Online]. Available: https://www.scien.cx/2023/02/03/set-up-a-basic-multi-tenant-architecture-in-rails-without-gem-2/. [Accessed: ]
rf:citation
» Set up a basic multi-tenant architecture in Rails without gem – 2 | Harsh patel | Sciencx | https://www.scien.cx/2023/02/03/set-up-a-basic-multi-tenant-architecture-in-rails-without-gem-2/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.