655 lines
13 KiB
Markdown
655 lines
13 KiB
Markdown
---
|
|
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`, or `api` (default: `modular`)
|
|
- **$3: options** (optional) - JSON string with configuration options:
|
|
- `testing`: `rspec` or `minitest` (default: `rspec`)
|
|
- `database`: `sequel`, `activerecord`, or `none` (default: `sequel`)
|
|
- `frontend`: `none`, `erb`, or `haml` (default: `erb`)
|
|
|
|
## Usage Examples
|
|
|
|
```bash
|
|
# 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:**
|
|
1. Validate project name format (alphanumeric, hyphens, underscores)
|
|
2. Check if directory already exists
|
|
3. Parse and validate options JSON
|
|
4. Create project directory structure
|
|
|
|
**Validation:**
|
|
```bash
|
|
# 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):**
|
|
```ruby
|
|
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:
|
|
```ruby
|
|
gem 'rack-cors', '~> 2.0' # For API
|
|
gem 'multi_json', '~> 1.15'
|
|
```
|
|
|
|
For database options:
|
|
```ruby
|
|
# 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):**
|
|
```ruby
|
|
require 'sinatra'
|
|
require 'sinatra/reloader' if development?
|
|
require_relative 'config/environment'
|
|
|
|
get '/' do
|
|
erb :index
|
|
end
|
|
```
|
|
|
|
**Modular Base Controller (app/controllers/base_controller.rb):**
|
|
```ruby
|
|
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):**
|
|
```ruby
|
|
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):**
|
|
```ruby
|
|
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:**
|
|
```ruby
|
|
require_relative 'config/environment'
|
|
|
|
# Modular
|
|
map '/' do
|
|
run ApplicationController
|
|
end
|
|
|
|
# API
|
|
# map '/api/v1' do
|
|
# run ApiController
|
|
# end
|
|
```
|
|
|
|
**config/environment.rb:**
|
|
```ruby
|
|
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):**
|
|
```yaml
|
|
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:**
|
|
```ruby
|
|
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:**
|
|
```ruby
|
|
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:**
|
|
```ruby
|
|
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:**
|
|
```bash
|
|
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:**
|
|
```ruby
|
|
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:**
|
|
```markdown
|
|
# [Project Name]
|
|
|
|
[Brief description of the project]
|
|
|
|
## Setup
|
|
|
|
1. Install dependencies:
|
|
```bash
|
|
bundle install
|
|
```
|
|
|
|
2. Set up environment variables:
|
|
```bash
|
|
cp .env.example .env
|
|
# Edit .env with your configuration
|
|
```
|
|
|
|
3. Set up database (if applicable):
|
|
```bash
|
|
rake db:migrate
|
|
```
|
|
|
|
## Development
|
|
|
|
Run the application:
|
|
```bash
|
|
bundle exec rerun 'rackup -p 3000'
|
|
```
|
|
|
|
Or with Puma:
|
|
```bash
|
|
bundle exec puma -C config/puma.rb
|
|
```
|
|
|
|
## Testing
|
|
|
|
Run tests:
|
|
```bash
|
|
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:**
|
|
```bash
|
|
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
|