Ruby Routes Gem

A lightweight, flexible routing system for Ruby that provides a Rails-like DSL for defining and matching HTTP routes.

Features

  • Rails-like DSL: Familiar syntax for defining routes
  • HTTP Method Support: GET, POST, PUT, PATCH, DELETE, and custom methods
  • RESTful Resources: Automatic generation of RESTful routes
  • Nested Routes: Support for nested resources and namespaces
  • Secure Route Constraints: Powerful constraint system with built-in security (see CONSTRAINTS.md)
  • Named Routes: Generate URLs from route names
  • Path Generation: Build URLs with parameters
  • Scope Support: Group routes with common options
  • Concerns: Reusable route groups
  • Lightweight: Minimal dependencies, fast performance

Installation

Add this line to your application's Gemfile:

gem 'ruby_routes'

And then execute:

bundle install

Or install it yourself as:

gem install ruby_routes

Basic Usage

Simple Routes

require 'ruby_routes'

router = RubyRoutes.draw do
  get '/', to: 'home#index'
  get '/about', to: 'pages#about'
  post '/users', to: 'users#create'
  put '/users/:id', to: 'users#update'
  delete '/users/:id', to: 'users#destroy'
end

RESTful Resources

router = RubyRoutes.draw do
  resources :users
  resources :posts
  resources :comments
end

This creates the following routes:

  • GET /usersusers#index
  • GET /users/newusers#new
  • POST /usersusers#create
  • GET /users/:idusers#show
  • GET /users/:id/editusers#edit
  • PUT /users/:idusers#update
  • PATCH /users/:idusers#update
  • DELETE /users/:idusers#destroy

Named Routes

router = RubyRoutes.draw do
  get '/users', as: :users, to: 'users#index'
  get '/users/:id', as: :user, to: 'users#show'
  post '/users', as: :create_user, to: 'users#create'
end

# Generate paths
router.route_set.generate_path(:users)           # => "/users"
router.route_set.generate_path(:user, id: '123') # => "/users/123"

Namespaces

router = RubyRoutes.draw do
  namespace :admin do
    resources :users
    resources :posts
  end
end

# Creates routes like:
# GET /admin/users → admin/users#index
# GET /admin/users/:id → admin/users#show
# etc.

Nested Resources

router = RubyRoutes.draw do
  resources :categories do
    resources :products
  end
end

# Creates routes like:
# GET /categories/:category_id/products → products#index
# GET /categories/:category_id/products/:id → products#show
# etc.

Route Constraints

Ruby Routes provides a powerful and secure constraint system to validate route parameters. For security reasons, Proc constraints are deprecated - use the secure alternatives below.

Built-in Constraint Types

router = RubyRoutes.draw do
  # Integer validation
  get '/users/:id', to: 'users#show', constraints: { id: :int }

  # UUID validation
  get '/resources/:uuid', to: 'resources#show', constraints: { uuid: :uuid }

  # Email validation
  get '/users/:email', to: 'users#show', constraints: { email: :email }

  # URL-friendly slug validation
  get '/posts/:slug', to: 'posts#show', constraints: { slug: :slug }

  # Alphabetic characters only
  get '/categories/:name', to: 'categories#show', constraints: { name: :alpha }

  # Alphanumeric characters only
  get '/codes/:code', to: 'codes#show', constraints: { code: :alphanumeric }
end
router = RubyRoutes.draw do
  # Length constraints
  get '/users/:username', to: 'users#show',
      constraints: { 
        username: { 
          min_length: 3, 
          max_length: 20,
          format: /\A[a-zA-Z0-9_]+\z/
        } 
      }

  # Allowed values (whitelist)
  get '/posts/:status', to: 'posts#show',
      constraints: { 
        status: { in: %w[draft published archived] }
      }

  # Numeric ranges
  get '/products/:price', to: 'products#show',
      constraints: { 
        price: { range: 1..10000 }
      }
end

Regular Expression Constraints

router = RubyRoutes.draw do
  # Custom regex pattern (with ReDoS protection)
  get '/products/:sku', to: 'products#show', 
      constraints: { sku: /\A[A-Z]{2}\d{4}\z/ }
end

⚠️ Security Notice: Proc Constraints Deprecated

# ❌ DEPRECATED - Security risk!
get '/users/:id', to: 'users#show',
    constraints: { id: ->(value) { value.to_i > 0 } }

# ✅ Use secure alternatives instead:
get '/users/:id', to: 'users#show',
    constraints: { id: { range: 1..Float::INFINITY } }

📚 For complete constraint documentation, see CONSTRAINTS.md
🔄 For migration help, see MIGRATION_GUIDE.md

Scopes

router = RubyRoutes.draw do
  scope defaults: { format: 'html' } do
    get '/posts', to: 'posts#index'
  end
end

Concerns

router = RubyRoutes.draw do
  concern :commentable do
    resources :comments
  end

  resources :posts do
    concerns :commentable
  end

  resources :articles do
    concerns :commentable
  end
end

Root Route

router = RubyRoutes.draw do
  root to: 'home#index'
end

Route Matching

router = RubyRoutes.draw do
  get '/users/:id', to: 'users#show'
  post '/users', to: 'users#create'
end

# Match a request
result = router.route_set.match('GET', '/users/123')
if result
  puts "Controller: #{result[:controller]}"
  puts "Action: #{result[:action]}"
  puts "Params: #{result[:params]}"
  # => Controller: users
  # => Action: show
  # => Params: {"id"=>"123"}
end

Path Generation

router = RubyRoutes.draw do
  get '/users/:id', as: :user, to: 'users#show'
  get '/posts/:id/comments/:comment_id', as: :post_comment, to: 'comments#show'
end

# Generate paths
router.route_set.generate_path(:user, id: '123')
# => "/users/123"

router.route_set.generate_path(:post_comment, id: '456', comment_id: '789')
# => "/posts/456/comments/789"

Integration with Rack

require 'rack'
require 'ruby_routes'

# Define routes
router = RubyRoutes.draw do
  get '/', to: 'home#index'
  get '/users', to: 'users#index'
  get '/users/:id', to: 'users#show'
end

# Create Rack app
class RubyRoutesApp
  def initialize(router)
    @router = router
  end

  def call(env)
    request_method = env['REQUEST_METHOD']
    request_path = env['PATH_INFO']

    route_info = @router.route_set.match(request_method, request_path)

    if route_info
      # Handle the request
      controller = route_info[:controller]
      action = route_info[:action]
      params = route_info[:params]

      # Your controller logic here
      [200, {'Content-Type' => 'text/html'}, ["Hello from #{controller}##{action}"]]
    else
      [404, {'Content-Type' => 'text/html'}, ['Not Found']]
    end
  end
end

# Run the app
app = RubyRoutesApp.new(router)
Rack::Handler::WEBrick.run app, Port: 9292

API Reference

RubyRoutes.draw(&block)

Creates a new router instance and yields to the block for route definition.

HTTP Methods

  • get(path, options = {})
  • post(path, options = {})
  • put(path, options = {})
  • patch(path, options = {})
  • delete(path, options = {})
  • match(path, options = {})

Resource Methods

  • resources(name, options = {}) - Creates RESTful routes for a collection
  • resource(name, options = {}) - Creates RESTful routes for a singular resource

Options

  • to: 'controller#action' - Specifies controller and action
  • controller: 'name' - Specifies controller name
  • action: 'name' - Specifies action name
  • as: :name - Names the route for path generation
  • via: :method - Specifies HTTP method(s)
  • constraints: {} - Adds route constraints
  • defaults: {} - Sets default parameters

RouteSet Methods

  • match(method, path) - Matches a request to a route
  • generate_path(name, params = {}) - Generates path from named route
  • find_route(method, path) - Finds a specific route
  • find_named_route(name) - Finds a named route

Documentation

Core Documentation

  • CONSTRAINTS.md - Complete guide to route constraints and security best practices
  • MIGRATION_GUIDE.md - Step-by-step guide for migrating from deprecated Proc constraints

Examples

See the examples/ directory for more detailed examples:

  • examples/basic_usage.rb - Basic routing examples
  • examples/rack_integration.rb - Full Rack application example

Security

Ruby Routes prioritizes security and has implemented several protections:

🔒 Security Features

  • XSS Protection: All HTML output is properly escaped
  • ReDoS Protection: Regular expression constraints have timeout protection
  • Secure Constraints: Deprecated dangerous Proc constraints in favor of secure alternatives
  • Thread Safety: All caching and shared resources are thread-safe
  • Input Validation: Comprehensive parameter validation before reaching application code

⚠️ Important Security Notice

Proc constraints are deprecated due to security risks and will be removed in a future version. They allow arbitrary code execution which can be exploited for:

  • Code injection attacks
  • Denial of service attacks
  • System compromise

Migration Required: If you're using Proc constraints, please migrate to secure alternatives using our Migration Guide.

Testing

Run the test suite:

bundle exec rspec

The test suite includes comprehensive security tests to ensure all protections are working correctly.

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -am 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Create a new Pull Request

License

This gem is available as open source under the terms of the MIT License.

Acknowledgments

This gem was inspired by Rails routing and aims to provide a lightweight alternative for Ruby applications that need flexible routing without the full Rails framework.