A production-ready Ruby on Rails boilerplate for building multi-tenant SaaS applications. Built with best practices, modern tooling, and a focus on developer experience.
- Multi-tenant Architecture: Route-based organization management
- Authentication & Authorization: Built with Devise and Pundit
- Subscription Management: Integrated Stripe payments via Pay gem
- Team Management: Organization creation, member invitations, and role management
- Modern UI: Clean, responsive design that you can easily extend
- Dark mode & Themes
π‘ Teams as MVP: Teams should be an MVP feature! - Learn why implementing teams early is crucial for SaaS applications.
- Complete Test Coverage
- Nested Resource Generation: Fast development with nested scaffold generators
Unlike traditional approaches (subdomains, user.organization_id), Moneygun uses route-based multi-tenancy which offers several advantages:
- Support for multiple organizations in different browser tabs
- No complex subdomain configuration required
-
Row-level Route-based Multitenancy Learn how to implement row-level route-based multitenancy in Ruby on Rails
-
Multitenancy & Teams Boilerplate Learn how to implement teams and multitenancy in your Rails application
-
Add ActsAsTenant to Existing Application Step-by-step guide to adding ActsAsTenant to your existing Rails application
-
Build Your Next B2B SaaS Enable Subscriptions with Stripe and launch your B2B SaaS application with Moneygun
Resources are organized in a logical hierarchy:
/organizations/:id/projects/:id/tasks/:id
This structure provides:
- Clear resource ownership
- Intuitive navigation
- Easy access control
- Simplified querying
- Clone the repository:
git clone git@github.com:yshmarov/moneygun.git your_project_name
cd your_project_name
- Set up the application:
bundle install
rails db:create db:migrate
- Start the development server:
bin/dev
Moneygun uses the Pay gem for handling Stripe subscriptions. Here's how to set it up:
Add your Stripe credentials to your Rails credentials:
rails credentials:edit
Add the following structure:
stripe:
private_key: sk_
public_key: pk_
webhook_receive_test_events: true
signing_secret:
- whsec_
You can create the required Stripe products and prices in two ways:
-
Automatically via seeds:
rails db:seed
This will create a "Pro plan" product with monthly ($99) and yearly ($999) prices.
-
Manually in Stripe Dashboard:
- Create a product named "Pro plan"
- Add two prices:
- Monthly: $99/month
- Yearly: $999/year
Add your Stripe price IDs to config/settings.yml
:
shared:
plans:
- id: price_xxx # Monthly price ID
unit_amount: 9900
currency: USD
interval: month
- id: price_yyy # Yearly price ID
unit_amount: 99900
currency: USD
interval: year
For development, Stripe webhook listener is already configured in Procfile.dev
stripe listen --forward-to localhost:3000/pay/webhooks/stripe
To enable webhooks:
- Development: Click here to create a new Stripe webhook with all the events pre-filled.
- Production: Click here to create a new Stripe webhook with all the events pre-filled.
Example production webhook url: https://moneygun.com/pay/webhooks/stripe
You can use the require_subscription
before_action to protect routes:
before_action :require_subscription
private
def require_subscription
unless current_organization.payment_processor.subscribed?
flash[:alert] = "You need to subscribe to access this page."
redirect_to organization_subscriptions_url(current_organization)
end
end
Use the subscription status helper to show subscription state:
subscription_status_label(organization)
# Returns:
# π΄ - No subscription
# π - Subscription cancelled (on grace period)
# π’ - Active subscription
Generate nested resources quickly using the nested_scaffold gem:
rails generate nested_scaffold organization/project name
Generate Pundit policies for your resources:
rails g pundit:policy project
Always associate resources with membership
instead of user
:
# β
Correct
class Project < ApplicationRecord
belongs_to :organization
belongs_to :membership
end
# β Avoid
class Project < ApplicationRecord
belongs_to :user
end
Scope downstream models to organization for easier querying:
class Task < ApplicationRecord
belongs_to :organization
belongs_to :project
end
Run the test suite:
rails test:all
# ERB linting
bundle exec erb_lint --lint-all -a
# Ruby linting
bundle exec rubocop -A
# Alphabetically sort i18n keys
i18n-tasks normalize
This project is licensed under the MIT License - see the LICENSE file for details.
- Design inspiration from Basecamp, Linear, Trello, Discord, and Slack
- Bullet Train for SaaS patterns and inspiration (obfuscates_id, super scaffolding, teams architecture)
- Jumpstart Pro & co for maintaining the magnificent gems pay, acts_as_tenant