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:
- EDI Mode: Focused on Electronic Data Interchange and transportation management functions
- 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:
-
Host-based detection: Different hostnames automatically set the appropriate mode
omni.edi2xml.comandapp.paiehub.ca→ Payroll Modeapp.edi2xml.com→ EDI Mode
-
Local development: Uses the
default_app_modeconfiguration value in development -
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:
-
application.rb: Sets the default mode for development environments
config.default_app_mode = 'payroll' # Restart server after change -
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:
-
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 -
Keep mode logic encapsulated: Add new mode-specific behavior to the AppMode class rather than scattering mode checks throughout the codebase
-
Test in all modes: Ensure features are tested in both Payroll and EDI modes to verify correct behavior
-
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:
- Add a new mode constant to the
AppModeclass - Add the new mode to
ACCEPTABLE_MODES - Create a predicate method for the new mode
- Update all case statements in the
AppModeclass to handle the new mode - Configure appropriate navigation and permissions for the new mode
- Update host-based detection if needed
The architecture is designed to make adding new modes straightforward without requiring extensive changes throughout the codebase.