Skip to main content

Application Modes

This document describes the Application Mode feature, which allows the system to operate in different operational contexts based on deployment environment and user needs.

Overview

Our codebase supports multiple application modes that determine which features, UI elements, and business logic are active for users. This architecture enables a single codebase to serve different business use cases through mode-specific configurations.

The system currently supports two primary application modes:

  1. EDI Mode: Focused on Electronic Data Interchange and transportation management functions
  2. Payroll Mode: Focused on payroll processing and HR management functions

How App Mode Works

The AppMode class serves as a context wrapper that determines:

  • Which navigation elements to display
  • User permission structures
  • Available functionality
  • UI branding and theming
  • Application paths and redirects
  • System-wide configuration settings

Mode Selection Logic

The application mode is determined using the following hierarchy:

  1. Host-based detection: Different hostnames automatically set the appropriate mode

    • omni.edi2xml.com and app.paiehub.ca → Payroll Mode
    • app.edi2xml.com → EDI Mode
  2. Local development: Uses the default_app_mode configuration value in development

  3. Fallback: Defaults to Payroll Mode if no other conditions are met

def from_referer_host(host_name)
if ['omni.edi2xml.com', 'app.paiehub.ca'].include?(host_name)
AppMode.new(PAYROLL_MODE)
elsif host_name == 'app.edi2xml.com'
AppMode.new(EDI_MODE)
elsif Rails.env.local?
AppMode.new(Rails.application.config.default_app_mode)
else
AppMode.new(PAYROLL_MODE)
end
end

Mode-Specific Functionality

Each application mode provides different configurations for:

1. User Interface and Navigation

Each mode has its own navigation configuration that determines which menu items and UI elements are visible to users:

def user_navigation_configuration_id
case mode
when PAYROLL_MODE
NavigationConfiguration::PAYROLL
when EDI_MODE
NavigationConfiguration::EDI
end
end

2. Permission Groups

Different roles and permission structures apply based on the active mode:

def permission_groups_codes
case mode
when PAYROLL_MODE
[PermissionGroup::Codes::PAYROLL_ADMIN]
when EDI_MODE
[PermissionGroup::Codes::EDI]
else
[]
end
end

3. Business Logic Variations

The system uses the mode to determine which business rules apply:

def supports_partner_by_company?
!payroll?
end

4. User Onboarding

Different registration and agreement flows are presented based on the active mode:

def agreement_path
if payroll?
'/onboard/payroll/agreement'
elsif edi?
'/onboard/edi/agreement'
end
end

def agreement_type
if payroll?
CompanyAgreement::PAYROLL_TYPE
elsif edi?
CompanyAgreement::EDI_TYPE
end
end

Configuration

The application mode is configured in several places:

  1. application.rb: Sets the default mode for development environments

    config.default_app_mode = 'payroll' # Restart server after change
  2. Environment-specific URLs: Different mode-specific URLs are configured

    config.payroll_web_ui_url = ENV.fetch("PAYROLL_WEB_UI_URL", 'http://localhost:3000')
    config.edi_web_ui_url = ENV.fetch("EDI_WEB_UI_URL", 'http://localhost:3000')

Best Practices

When developing features that might behave differently based on application mode:

  1. Use mode-based conditionals: Always check the current mode when implementing features that differ between modes

    if app_mode.edi?
    # EDI-specific logic
    elsif app_mode.payroll?
    # Payroll-specific logic
    end
  2. Keep mode logic encapsulated: Add new mode-specific behavior to the AppMode class rather than scattering mode checks throughout the codebase

  3. Test in all modes: Ensure features are tested in both Payroll and EDI modes to verify correct behavior

  4. Use the helper methods: Prefer using the predicate methods (edi?, payroll?) over direct comparison with mode constants

Technical Implementation

See these template PRs where new app mode is added:

The AppMode class is implemented as a value object that wraps the mode string and provides behavior based on that mode. This pattern centralizes mode-specific logic and simplifies the addition of new modes in the future.

class AppMode
PAYROLL_MODE = 'payroll'
EDI_MODE = 'edi'

ACCEPTABLE_MODES = [PAYROLL_MODE, EDI_MODE].freeze

# Implementation details...

private

def payroll?
mode == PAYROLL_MODE
end

def edi?
mode == EDI_MODE
end
end

Adding a New Application Mode

To add a new application mode to the system:

  1. Add a new mode constant to the AppMode class
  2. Add the new mode to ACCEPTABLE_MODES
  3. Create a predicate method for the new mode
  4. Update all case statements in the AppMode class to handle the new mode
  5. Configure appropriate navigation and permissions for the new mode
  6. Update host-based detection if needed

The architecture is designed to make adding new modes straightforward without requiring extensive changes throughout the codebase.