Skip to content

Conversation

@ericproulx
Copy link
Contributor

Refactor Router Architecture: Improve Encapsulation and API Design

Overview

This PR refactors the router internals to improve code quality, maintainability, and API design. The changes focus on better encapsulation, explicit interfaces, and cleaner delegation patterns throughout the routing system.

Motivation

The previous implementation had several design issues:

  • Over-reliance on options hashes: Route classes accessed deeply nested options (e.g., route.options[:endpoint].call(env))
  • Implicit dependencies: Required parameters were hidden inside options hashes
  • Poor encapsulation: Internal implementation details were exposed through public interfaces
  • Inconsistent patterns: Mixed use of delegation approaches (delegate_missing_to vs explicit delegation)

These issues made the code harder to understand, test, and modify.

Key Changes

1. Router Classes Refactoring

BaseRoute

  • Changed constructor to accept pattern as first-class parameter instead of nested in options
  • Added explicit Forwardable delegation for common methods (path, origin, description, etc.)
  • Made the interface more explicit and discoverable

Before:

def initialize(options)
  @options = options
end

def pattern_regexp
  pattern.to_regexp  # pattern came from options
end

After:

def initialize(pattern, options = {})
  @pattern = pattern
  @options = options
end

def_delegators :@pattern, :path, :origin
def_delegators :@options, :description, :version, :requirements, ...

def pattern_regexp
  @pattern.to_regexp
end

GreedyRoute

  • Converted to keyword arguments for explicit interface (endpoint:, allow_header:)
  • Added explicit delegation to endpoint's call method
  • Simplified params method to return nil instead of accessing nested options

Before:

def initialize(pattern, options)
  @pattern = pattern
  super(options)
end

def params(_input = nil)
  options[:params] || {}
end

After:

def initialize(pattern, endpoint:, allow_header:)
  super(pattern)
  @endpoint = endpoint
  @allow_header = allow_header
end

def_delegators :@endpoint, :call

def params(_input = nil)
  nil
end

Route

  • Constructor now accepts endpoint and pattern as explicit parameters
  • Pattern construction moved to the caller for better separation of concerns
  • Removed pattern creation logic from Route class

Before:

def initialize(method, origin, path, options)
  @request_method = upcase_method(method)
  @pattern = Grape::Router::Pattern.new(origin, path, options)
  super(options)
end

After:

def initialize(endpoint, method, pattern, options)
  super(pattern, options)
  @app = endpoint
  @request_method = upcase_method(method)
end

Pattern

  • Converted from positional + options hash to pure keyword arguments
  • Inlined helper methods for clarity
  • Simplified extract_capture method to use direct hash construction

Before:

def initialize(origin, suffix, options)
  @origin = origin
  @path = build_path(origin, options[:anchor], suffix)
  @pattern = build_pattern(@path, options[:params], options[:format], ...)
end

After:

def initialize(origin:, suffix:, anchor:, params:, format:, version:, requirements:)
  @origin = origin
  @path = PatternCache[[build_path_from_pattern(@origin, anchor), suffix]]
  @pattern = Mustermann::Grape.new(@path, uri_decode: true, params: params, ...)
end

2. Endpoint Refactoring

  • Converted constructor to use keyword arguments (**options)
  • Removed require_option method (now handled by Ruby's keyword argument validation)
  • Moved route preparation logic to private methods for better organization
  • Improved method signatures (prepare_version, prepare_routes_requirements) to accept explicit parameters

3. Router Improvements

  • Simplified associate_routes to accept a GreedyRoute instance directly
  • Changed route access from route.options[:endpoint].call(env) to route.call(env)
  • Changed route access from route.options[:allow_header] to route.allow_header
  • Fixed nil handling in make_routing_args to avoid merging when route_params is nil

4. Code Quality Improvements

  • Replaced map(&:routes).flatten with flat_map(&:routes) for better performance
  • Removed unnecessary splat operators (*extract_input_and_method(env)extract_input_and_method(env))
  • Moved namespace variables to appropriate scopes
  • Extracted DSL methods list to a constant (Grape::Util::ApiDescription::DSL_METHODS)

5. API Instance Updates

  • Updated collect_route_config_per_pattern to create GreedyRoute instances explicitly
  • Removed inline options hash creation in favor of explicit constructor calls
  • Improved code readability by separating route creation from router registration

Breaking Changes

None. These are internal refactorings that don't change the public API.

Testing

  • All existing specs pass
  • Updated specs for GreedyRoute to reflect new constructor signature
  • Updated specs for Endpoint to use keyword arguments
  • Updated specs for route_param DSL method

Benefits

  1. Better Encapsulation: Internal implementation details are no longer exposed through options hashes
  2. Clearer Interfaces: Keyword arguments make required parameters explicit
  3. Improved Testability: Easier to mock and test individual components
  4. Better Performance: Using flat_map and removing unnecessary hash lookups
  5. Maintainability: Code is easier to understand and modify
  6. Type Safety: Ruby's keyword argument validation helps catch errors earlier

Files Changed

  • lib/grape/api/instance.rb
  • lib/grape/dsl/routing.rb
  • lib/grape/endpoint.rb
  • lib/grape/router.rb
  • lib/grape/router/base_route.rb
  • lib/grape/router/greedy_route.rb
  • lib/grape/router/pattern.rb
  • lib/grape/router/route.rb
  • lib/grape/util/api_description.rb
  • Corresponding spec files

@ericproulx ericproulx force-pushed the refactor/router-pattern-delegation branch from a4fa2d1 to 314f455 Compare December 1, 2025 01:57
- Convert Route classes to use explicit parameters instead of options hashes
- Add Forwardable delegation for cleaner interfaces
- Move Pattern construction to caller for better separation of concerns
- Improve API design with keyword arguments throughout router system
- Replace nested option access (route.options[:endpoint]) with direct methods
- Simplify code with flat_map and remove unnecessary splat operators
- Extract DSL_METHODS constant in ApiDescription for reusability

This refactoring improves code maintainability, testability, and performance
while maintaining backward compatibility with the public API.
@ericproulx ericproulx force-pushed the refactor/router-pattern-delegation branch from 314f455 to 11bd88a Compare December 1, 2025 01:59
Copy link
Member

@dblock dblock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, although .. YOLO.

@dblock
Copy link
Member

dblock commented Dec 1, 2025

Danger token was reset by GitHub and hardcoding the token will no longer work :( We need to replace it with something else.

@dblock dblock merged commit d4244ab into master Dec 1, 2025
108 of 109 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants