Migrating to Otto v2.0.0-pre1

What’s New in v2.0.0-pre1

This pre-release includes extensive test coverage improvements (76 new test cases), core module refactoring, middleware stack unification, and a major update to Logic class authentication patterns. See the full changelog for complete details.

The main breaking changes affect: 1. Applications that directly manipulate the middleware stack 2. Logic classes using the old authentication signature

Middleware Stack Unified API

Otto v2.0.0-pre1 introduces a significant refactoring of the middleware stack management, providing a more consistent and efficient approach to middleware configuration.

Key Changes

Unified Middleware Registration

Previous versions had separate legacy and new middleware stacks. In v2.0.0-pre1, we’ve consolidated these into a single, more powerful middleware stack.

Before: ruby # Old approach with separate stacks otto.middleware_stack << SomeMiddleware otto.middleware.add(AnotherMiddleware)

After: ruby # Unified middleware registration otto.use(SomeMiddleware) otto.use(AnotherMiddleware)

Performance Improvements

  • Middleware lookup is now O(1) using a Set
  • Memoized middleware list reduces repeated array creation
  • Prevent duplicate middleware registrations with identical configurations

Migration Steps

  1. Replace all otto.middleware_stack << calls with otto.use()
  2. Remove any direct references to otto.middleware_stack
  3. Use otto.middleware.includes?() instead of manual stack checks

Authentication and Security Methods

Authentication and security methods now consistently use the new unified middleware stack:

```ruby # Before otto.middleware_stack « Otto::Security::CSRFMiddleware

After (no change needed)

otto.enable_csrf_protection! ```

Performance Considerations

The new implementation is more memory-efficient and provides faster middleware lookups, especially for applications with many middleware components.

Potential Breaking Changes

  • Code relying on direct manipulation of middleware_stack will need updates
  • Method signatures for middleware configuration remain the same

Example Migration

```ruby # Old code class MyApp def initialize @otto = Otto.new @otto.middleware_stack « CustomMiddleware @otto.middleware.add(AnotherMiddleware) end end

New code

class MyApp def initialize @otto = Otto.new @otto.use(CustomMiddleware) @otto.use(AnotherMiddleware) end end ```

Troubleshooting

If you encounter any issues with middleware registration or configuration, please file an issue at Otto GitHub Repository.

Logic Class RequestContext Pattern

Otto v2.0.0-pre1 introduces a major improvement to Logic class authentication with the new RequestContext pattern.

Key Changes

New Constructor Signature

Logic classes now use a cleaner, more powerful constructor signature that provides immutable context.

Before: ```ruby class MyLogic attr_reader :session, :user, :params, :locale

def initialize(session, user, params, locale) @session = session @user = user @params = params @locale = locale end

def process return { error: ‘not authenticated’ } unless @user { result: ‘success’, user_name: @user[‘name’] } end end ```

After: ```ruby class MyLogic attr_reader :context, :params, :locale

def initialize(context, params, locale) @context = context @params = params @locale = locale end

def process return { error: ‘not authenticated’ } unless @context.authenticated? { result: ‘success’, user_name: @context.user_name, roles: @context.roles, permissions: @context.permissions } end end ```

RequestContext Benefits

  1. Immutable Structure - RequestContext is a Ruby Data class that can’t be accidentally modified
  2. Helper Methods - Built-in methods like authenticated?, has_role?, has_permission?
  3. Cleaner Interface - Single context parameter instead of separate session/user parameters
  4. Type Safety - Better IDE support and documentation
  5. Future-proof - New authentication data automatically available

RequestContext API

```ruby # Authentication status context.authenticated? # Boolean: has non-empty user data context.anonymous? # Boolean: not authenticated

User information

context.user_id # User ID from various possible locations context.user_name # User name/username context.session_id # Session identifier

Role and permission checks

context.has_role?(‘admin’) # Single role check context.has_permission?(‘write’) # Single permission check context.has_any_role?(‘admin’, ‘mod’) # Multiple role check context.roles # Array of all user roles context.permissions # Array of all user permissions

Raw data access

context.session # Session hash context.user # User hash context.auth_method # Authentication method used context.metadata # Additional context data ```

Migration Steps

  1. Update Logic class constructor signatures from 4 parameters to 3: ```ruby # Change this: def initialize(session, user, params, locale)

    To this:

    def initialize(context, params, locale) ```

  2. Update instance variables: ```ruby # Change this: @session = session @user = user

    To this:

    @context = context ```

  3. Update authentication checks: ```ruby # Change this: return error unless @user

    To this:

    return error unless @context.authenticated? ```

  4. Update data access: ```ruby # Change this: user_name = @user&.dig(‘name’) user_role = @user&.dig(‘role’)

    To this:

    user_name = @context.user_name user_role = @context.roles.first ```

Example Migration

Before (v1.x): ```ruby class AdminPanel attr_reader :session, :user, :params, :locale

def initialize(session, user, params, locale) @session = session @user = user @params = params @locale = locale end

def raise_concerns raise ‘Access denied’ unless @user&.dig(‘role’) == ‘admin’ end

def process { panel: ‘admin’, user: @user&.dig(‘name’) || ‘unknown’, session_id: @session&.dig(‘id’) } end end ```

After (v2.0): ```ruby class AdminPanel attr_reader :context, :params, :locale

def initialize(context, params, locale) @context = context @params = params @locale = locale end

def raise_concerns raise ‘Access denied’ unless @context.has_role?(‘admin’) end

def process { panel: ‘admin’, user: @context.user_name || ‘unknown’, session_id: @context.session_id, authenticated: @context.authenticated?, permissions: @context.permissions } end end ```

This provides a cleaner, more maintainable interface while giving Logic classes access to rich authentication context.