13 KiB
description
| description |
|---|
| Scaffold new Sinatra applications with modern structure, best practices, testing setup, and deployment configuration |
Sinatra Scaffold Command
Scaffolds a new Sinatra application with modern project structure, testing framework, and deployment configuration.
Arguments
- $1: project-name (required) - Name of the project/application
- $2: type (optional) - Application type:
classic,modular, orapi(default:modular) - $3: options (optional) - JSON string with configuration options:
testing:rspecorminitest(default:rspec)database:sequel,activerecord, ornone(default:sequel)frontend:none,erb, orhaml(default:erb)
Usage Examples
# Basic modular app with defaults
/sinatra-scaffold my-app
# Classic app with RSpec and no database
/sinatra-scaffold simple-app classic '{"testing":"rspec","database":"none","frontend":"erb"}'
# API-only app with Minitest and ActiveRecord
/sinatra-scaffold api-service api '{"testing":"minitest","database":"activerecord","frontend":"none"}'
# Full-featured modular app
/sinatra-scaffold webapp modular '{"testing":"rspec","database":"sequel","frontend":"haml"}'
Workflow
Step 1: Validate and Initialize
Actions:
- Validate project name format (alphanumeric, hyphens, underscores)
- Check if directory already exists
- Parse and validate options JSON
- Create project directory structure
Validation:
# Check project name
if [[ ! "$PROJECT_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "Error: Invalid project name. Use alphanumeric characters, hyphens, or underscores."
exit 1
fi
# Check if directory exists
if [ -d "$PROJECT_NAME" ]; then
echo "Error: Directory '$PROJECT_NAME' already exists."
exit 1
fi
Step 2: Create Directory Structure
Classic Structure:
project-name/
├── app.rb
├── config.ru
├── Gemfile
├── Rakefile
├── config/
│ └── environment.rb
├── public/
│ ├── css/
│ ├── js/
│ └── images/
├── views/
│ ├── layout.erb
│ └── index.erb
├── spec/ or test/
└── README.md
Modular Structure:
project-name/
├── app/
│ ├── controllers/
│ │ ├── application_controller.rb
│ │ └── base_controller.rb
│ ├── models/
│ ├── services/
│ └── helpers/
├── config/
│ ├── environment.rb
│ ├── database.yml (if database selected)
│ └── puma.rb
├── config.ru
├── db/
│ └── migrations/
├── lib/
│ └── tasks/
├── public/
│ ├── css/
│ ├── js/
│ └── images/
├── views/
│ ├── layout.erb
│ └── index.erb
├── spec/ or test/
│ ├── spec_helper.rb
│ └── controllers/
├── Gemfile
├── Rakefile
├── .env.example
├── .gitignore
└── README.md
API Structure:
project-name/
├── app/
│ ├── controllers/
│ │ ├── api_controller.rb
│ │ └── base_controller.rb
│ ├── models/
│ ├── services/
│ └── serializers/
├── config/
│ ├── environment.rb
│ ├── database.yml
│ └── puma.rb
├── config.ru
├── db/
│ └── migrations/
├── lib/
├── spec/ or test/
│ ├── spec_helper.rb
│ ├── requests/
│ └── support/
├── Gemfile
├── Rakefile
├── .env.example
├── .gitignore
└── README.md
Step 3: Generate Gemfile
Base Dependencies (All Types):
source 'https://rubygems.org'
ruby '~> 3.2'
gem 'sinatra', '~> 3.0'
gem 'sinatra-contrib', '~> 3.0'
gem 'puma', '~> 6.0'
gem 'rake', '~> 13.0'
gem 'dotenv', '~> 2.8'
# Add database gems if selected
# gem 'sequel', '~> 5.0' or gem 'activerecord', '~> 7.0'
# gem 'pg', '~> 1.5' # PostgreSQL
# Add frontend gems if not API
# gem 'haml', '~> 6.0' if haml selected
group :development, :test do
gem 'rspec', '~> 3.12' # or minitest
gem 'rack-test', '~> 2.0'
gem 'rerun', '~> 0.14'
end
group :development do
gem 'pry', '~> 0.14'
end
group :test do
gem 'simplecov', '~> 0.22', require: false
gem 'database_cleaner-sequel', '~> 2.0' # if using Sequel
end
Additional Dependencies by Type:
For modular/API:
gem 'rack-cors', '~> 2.0' # For API
gem 'multi_json', '~> 1.15'
For database options:
# Sequel
gem 'sequel', '~> 5.0'
gem 'pg', '~> 1.5'
# ActiveRecord
gem 'activerecord', '~> 7.0'
gem 'pg', '~> 1.5'
gem 'sinatra-activerecord', '~> 2.0'
Step 4: Generate Application Files
Classic App (app.rb):
require 'sinatra'
require 'sinatra/reloader' if development?
require_relative 'config/environment'
get '/' do
erb :index
end
Modular Base Controller (app/controllers/base_controller.rb):
require 'sinatra/base'
require 'sinatra/json'
class BaseController < Sinatra::Base
configure do
set :root, File.expand_path('../..', __dir__)
set :views, Proc.new { File.join(root, 'views') }
set :public_folder, Proc.new { File.join(root, 'public') }
set :show_exceptions, false
set :raise_errors, false
end
configure :development do
require 'sinatra/reloader'
register Sinatra::Reloader
end
helpers do
def json_response(data, status = 200)
halt status, { 'Content-Type' => 'application/json' }, data.to_json
end
end
error do
error = env['sinatra.error']
status 500
json_response({ error: error.message })
end
not_found do
json_response({ error: 'Not found' }, 404)
end
end
Application Controller (app/controllers/application_controller.rb):
require_relative 'base_controller'
class ApplicationController < BaseController
get '/' do
erb :index
end
get '/health' do
json_response({ status: 'ok', timestamp: Time.now.to_i })
end
end
API Controller (for API type):
require_relative 'base_controller'
class ApiController < BaseController
before do
content_type :json
end
# CORS for development
configure :development do
before do
headers 'Access-Control-Allow-Origin' => '*'
end
options '*' do
headers 'Access-Control-Allow-Methods' => 'GET, POST, PUT, PATCH, DELETE, OPTIONS'
headers 'Access-Control-Allow-Headers' => 'Content-Type, Authorization'
200
end
end
get '/' do
json_response({
name: 'API',
version: '1.0',
endpoints: [
{ path: '/health', method: 'GET' }
]
})
end
get '/health' do
json_response({ status: 'healthy', timestamp: Time.now.to_i })
end
end
Step 5: Create Configuration Files
config.ru:
require_relative 'config/environment'
# Modular
map '/' do
run ApplicationController
end
# API
# map '/api/v1' do
# run ApiController
# end
config/environment.rb:
ENV['RACK_ENV'] ||= 'development'
require 'bundler'
Bundler.require(:default, ENV['RACK_ENV'])
# Load environment variables
require 'dotenv'
Dotenv.load(".env.#{ENV['RACK_ENV']}", '.env')
# Database setup (if selected)
# require_relative 'database'
# Load application files
Dir[File.join(__dir__, '../app/**/*.rb')].sort.each { |file| require file }
config/database.yml (if database selected):
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("DB_POOL", 5) %>
host: <%= ENV.fetch("DB_HOST", "localhost") %>
development:
<<: *default
database: <%= ENV.fetch("PROJECT_NAME") %>_development
test:
<<: *default
database: <%= ENV.fetch("PROJECT_NAME") %>_test
production:
<<: *default
database: <%= ENV.fetch("DB_NAME") %>
username: <%= ENV.fetch("DB_USER") %>
password: <%= ENV.fetch("DB_PASSWORD") %>
config/puma.rb:
workers ENV.fetch('WEB_CONCURRENCY', 2)
threads_count = ENV.fetch('MAX_THREADS', 5)
threads threads_count, threads_count
preload_app!
port ENV.fetch('PORT', 3000)
environment ENV.fetch('RACK_ENV', 'development')
on_worker_boot do
# Database reconnection if using ActiveRecord
# ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end
Step 6: Set Up Testing Framework
RSpec spec/spec_helper.rb:
ENV['RACK_ENV'] = 'test'
require 'simplecov'
SimpleCov.start
require_relative '../config/environment'
require 'rack/test'
require 'rspec'
# Database cleaner setup (if database)
# require 'database_cleaner/sequel'
RSpec.configure do |config|
config.include Rack::Test::Methods
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
# Database cleaner (if database)
# config.before(:suite) do
# DatabaseCleaner.strategy = :transaction
# DatabaseCleaner.clean_with(:truncation)
# end
#
# config.around(:each) do |example|
# DatabaseCleaner.cleaning do
# example.run
# end
# end
end
Example spec/controllers/application_controller_spec.rb:
require_relative '../spec_helper'
RSpec.describe ApplicationController do
def app
ApplicationController
end
describe 'GET /' do
it 'returns success' do
get '/'
expect(last_response).to be_ok
end
end
describe 'GET /health' do
it 'returns health status' do
get '/health'
expect(last_response).to be_ok
json = JSON.parse(last_response.body)
expect(json['status']).to eq('ok')
end
end
end
Step 7: Create Supporting Files
.env.example:
RACK_ENV=development
PORT=3000
# Database (if selected)
DB_HOST=localhost
DB_NAME=project_name_development
DB_USER=postgres
DB_PASSWORD=
# Session
SESSION_SECRET=your-secret-key-here
# External services
# API_KEY=
.gitignore:
*.gem
*.rbc
/.config
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/spec/examples.txt
/test/tmp/
/test/version_tmp/
/tmp/
# Environment files
.env
.env.local
# Database
*.sqlite3
*.db
# Logs
*.log
# Editor files
.vscode/
.idea/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
Rakefile:
require_relative 'config/environment'
# Database tasks (if using Sequel)
if defined?(Sequel)
require 'sequel/core'
namespace :db do
desc 'Run migrations'
task :migrate, [:version] do |t, args|
Sequel.extension :migration
db = Sequel.connect(ENV['DATABASE_URL'])
if args[:version]
puts "Migrating to version #{args[:version]}"
Sequel::Migrator.run(db, 'db/migrations', target: args[:version].to_i)
else
puts 'Migrating to latest'
Sequel::Migrator.run(db, 'db/migrations')
end
puts 'Migration complete'
end
end
end
# Testing tasks
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task default: :spec
README.md:
# [Project Name]
[Brief description of the project]
## Setup
1. Install dependencies:
```bash
bundle install
-
Set up environment variables:
cp .env.example .env # Edit .env with your configuration -
Set up database (if applicable):
rake db:migrate
Development
Run the application:
bundle exec rerun 'rackup -p 3000'
Or with Puma:
bundle exec puma -C config/puma.rb
Testing
Run tests:
bundle exec rspec
Deployment
[Add deployment instructions]
API Documentation
[Add API documentation if applicable]
### Step 8: Initialize Git Repository
**Actions:**
```bash
cd project-name
git init
git add .
git commit -m "Initial commit: Sinatra application scaffold"
Step 9: Install Dependencies
Actions:
bundle install
Verification:
- Confirm all gems installed successfully
- Check for any dependency conflicts
- Display next steps to user
Expected Output
Creating Sinatra application: my-app
Type: modular
Options: {"testing":"rspec","database":"sequel","frontend":"erb"}
✓ Created directory structure
✓ Generated Gemfile
✓ Created application files
✓ Set up configuration files
✓ Configured RSpec testing
✓ Created supporting files
✓ Initialized git repository
✓ Installed dependencies
Application created successfully!
Next steps:
cd my-app
bundle exec rerun 'rackup -p 3000'
Visit: http://localhost:3000
Tests: bundle exec rspec
Error Handling
- Invalid project name format
- Directory already exists
- Invalid JSON options
- Bundle install failures
- File creation permission errors
Notes
- All generated code follows Ruby and Sinatra best practices
- Testing framework is fully configured and ready to use
- Development tools (rerun, pry) included for better DX
- Production-ready configuration provided
- Database migrations directory created if database selected
- CORS configured for API applications