sinatra-patterns
Common Sinatra patterns, routing strategies, error handling, and application organization. Use when building Sinatra applications or designing routes.
What this skill does
# Sinatra Patterns Skill
## Tier 1: Quick Reference
### Common Routing Patterns
**Basic Routes:**
```ruby
get '/' do
'Hello World'
end
post '/users' do
# Create user
end
put '/users/:id' do
# Update user
end
delete '/users/:id' do
# Delete user
end
```
**Route Parameters:**
```ruby
# Named parameters
get '/users/:id' do
User.find(params[:id])
end
# Parameter constraints
get '/users/:id', :id => /\d+/ do
# Only matches numeric IDs
end
# Wildcard
get '/files/*.*' do
# params['splat'] contains matched segments
end
```
**Query Parameters:**
```ruby
get '/search' do
query = params[:q]
page = params[:page] || 1
results = search(query, page: page)
end
```
### Basic Middleware
```ruby
# Session middleware
use Rack::Session::Cookie, secret: ENV['SESSION_SECRET']
# Security middleware
use Rack::Protection
# Logging
use Rack::CommonLogger
# Compression
use Rack::Deflater
```
### Simple Error Handling
```ruby
not_found do
'Page not found'
end
error do
'Internal server error'
end
error 401 do
'Unauthorized'
end
```
### Helpers
```ruby
helpers do
def logged_in?
!session[:user_id].nil?
end
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
end
```
---
## Tier 2: Detailed Instructions
### Advanced Routing
**Modular Applications:**
```ruby
# app/controllers/base_controller.rb
class BaseController < Sinatra::Base
configure do
set :views, Proc.new { File.join(root, '../views') }
set :public_folder, Proc.new { File.join(root, '../public') }
end
helpers do
def json_response(data, status = 200)
content_type :json
halt status, data.to_json
end
end
end
# app/controllers/users_controller.rb
class UsersController < BaseController
get '/' do
users = User.all
json_response(users.map(&:to_hash))
end
get '/:id' do
user = User.find(params[:id]) || halt(404)
json_response(user.to_hash)
end
post '/' do
user = User.create(params[:user])
if user.persisted?
json_response(user.to_hash, 201)
else
json_response({ errors: user.errors }, 422)
end
end
end
# config.ru
map '/users' do
run UsersController
end
```
**Namespaces:**
```ruby
require 'sinatra/namespace'
class App < Sinatra::Base
register Sinatra::Namespace
namespace '/api' do
namespace '/v1' do
get '/users' do
# GET /api/v1/users
end
namespace '/admin' do
before do
authenticate_admin!
end
get '/stats' do
# GET /api/v1/admin/stats
end
end
end
end
end
```
**Route Conditions:**
```ruby
# User agent condition
get '/', :agent => /iPhone/ do
# Mobile version
end
# Custom conditions
set(:auth) do |role|
condition do
unless current_user && current_user.has_role?(role)
halt 403
end
end
end
get '/admin', :auth => :admin do
# Only accessible to admins
end
# Host-based routing
get '/', :host => 'admin.example.com' do
# Admin subdomain
end
```
**Content Negotiation:**
```ruby
get '/users/:id', :provides => [:json, :xml, :html] do
user = User.find(params[:id])
case request.accept.first.to_s
when 'application/json'
json user.to_json
when 'application/xml'
xml user.to_xml
else
erb :user, locals: { user: user }
end
end
# Or using provides helper
get '/users/:id' do
user = User.find(params[:id])
respond_to do |format|
format.json { json user.to_json }
format.xml { xml user.to_xml }
format.html { erb :user, locals: { user: user } }
end
end
```
### Middleware Composition
**Custom Middleware:**
```ruby
class RequestLogger
def initialize(app)
@app = app
end
def call(env)
start_time = Time.now
status, headers, body = @app.call(env)
duration = Time.now - start_time
puts "#{env['REQUEST_METHOD']} #{env['PATH_INFO']} - #{status} (#{duration}s)"
[status, headers, body]
end
end
use RequestLogger
```
**Middleware Ordering:**
```ruby
# config.ru
use Rack::Deflater # Compression first
use Rack::Static # Static files
use Rack::CommonLogger # Logging
use Rack::Session::Cookie # Sessions
use Rack::Protection # Security
use CustomAuthentication # Auth
run Application
```
### Template Integration
**ERB Templates:**
```ruby
# views/layout.erb
<!DOCTYPE html>
<html>
<head>
<title><%= @title || 'My App' %></title>
</head>
<body>
<%= yield %>
</body>
</html>
# views/users/index.erb
<h1>Users</h1>
<ul>
<% @users.each do |user| %>
<li><%= user.name %></li>
<% end %>
</ul>
# Controller
get '/users' do
@users = User.all
@title = 'User List'
erb :'users/index'
end
```
**Inline Templates:**
```ruby
get '/' do
erb :index
end
__END__
@@layout
<!DOCTYPE html>
<html>
<body><%= yield %></body>
</html>
@@index
<h1>Welcome</h1>
```
**Template Engines:**
```ruby
# Haml
get '/' do
haml :index
end
# Slim
get '/' do
slim :index
end
# Liquid (safe for user content)
get '/' do
liquid :index, locals: { user: current_user }
end
```
### Error Handling Patterns
**Comprehensive Error Handling:**
```ruby
class Application < Sinatra::Base
# Development configuration
configure :development do
set :show_exceptions, :after_handler
set :dump_errors, true
end
# Production configuration
configure :production do
set :show_exceptions, false
set :dump_errors, false
end
# Specific exception handlers
error ActiveRecord::RecordNotFound do
status 404
json({ error: 'Resource not found' })
end
error ActiveRecord::RecordInvalid do
status 422
json({ error: 'Validation failed', details: env['sinatra.error'].message })
end
error Sequel::NoMatchingRow do
status 404
json({ error: 'Not found' })
end
# HTTP status handlers
not_found do
json({ error: 'Endpoint not found' })
end
error 401 do
json({ error: 'Unauthorized' })
end
error 403 do
json({ error: 'Forbidden' })
end
error 422 do
json({ error: 'Unprocessable entity' })
end
# Catch-all error handler
error do
error = env['sinatra.error']
logger.error("Error: #{error.message}")
logger.error(error.backtrace.join("\n"))
status 500
json({ error: 'Internal server error' })
end
end
```
### Before/After Filters
**Request Filters:**
```ruby
# Global before filter
before do
content_type :json
end
# Path-specific filters
before '/admin/*' do
authenticate_admin!
end
# Conditional filters
before do
pass unless request.path.start_with?('/api')
authenticate_api_user!
end
# After filters
after do
# Add CORS headers
headers 'Access-Control-Allow-Origin' => '*'
end
# Modify response
after do
response.body = response.body.map(&:upcase) if params[:uppercase]
end
```
### Session Management
**Cookie Sessions:**
```ruby
use Rack::Session::Cookie,
key: 'app.session',
secret: ENV['SESSION_SECRET'],
expire_after: 86400, # 1 day
secure: production?,
httponly: true,
same_site: :strict
helpers do
def login(user)
session[:user_id] = user.id
session[:logged_in_at] = Time.now.to_i
end
def logout
session.clear
end
def current_user
return nil unless session[:user_id]
@current_user ||= User.find_by(id: session[:user_id])
end
end
```
---
## Tier 3: Resources & Examples
### Full Application Example
See `assets/modular-app-template/` for complete modular application structure.
### Performance Patterns
**Caching:**
```ruby
# HTTP caching
get '/public/data' do
cache_control :public, max_age: 3600
etag calculate_etag
last_modified last_update_time
json PublicData.all.map(&:to_hash)
end
# Fragment caching with Redis
require 'redis'
helpers do
def cache_fetch(key, expires_in: 300, &block)
cached = REDIS.get(key)
return JSON.parse(cached) if cached
data = block.call
REDIS.setex(key, expires_in, data.to_json)
data
end
end
get '/Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.