{"record":{"id":"b53e94953aaf9232","repo":"heartcombo/devise","slug":"route-should-be-true-a-symbol-or-a-hash","errorCode":null,"errorMessage":":route should be true, a Symbol or a Hash","messagePattern":":route should be true, a Symbol or a Hash","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/devise.rb","lineNumber":423,"sourceCode":"    end\n\n    if controller = options[:controller]\n      controller = (controller == true ? module_name : controller)\n      CONTROLLERS[module_name] = controller\n    end\n\n    NO_INPUT << strategy if options[:no_input]\n\n    if route = options[:route]\n      case route\n      when TrueClass\n        key, value = module_name, []\n      when Symbol\n        key, value = route, []\n      when Hash\n        key, value = route.keys.first, route.values.flatten\n      else\n        raise ArgumentError, \":route should be true, a Symbol or a Hash\"\n      end\n\n      URL_HELPERS[key] ||= []\n      URL_HELPERS[key].concat(value)\n      URL_HELPERS[key].uniq!\n\n      ROUTES[module_name] = key\n    end\n\n    if options[:model]\n      path = (options[:model] == true ? \"devise/models/#{module_name}\" : options[:model])\n      camelized = ActiveSupport::Inflector.camelize(module_name.to_s)\n      Devise::Models.send(:autoload, camelized.to_sym, path)\n    end\n\n    Devise::Mapping.add_module module_name\n  end\n","sourceCodeStart":405,"sourceCodeEnd":441,"githubUrl":"https://github.com/heartcombo/devise/blob/372b295fe6f63b4af3269f5dcd51a18c0bc2016c/lib/devise.rb#L405-L441","documentation":"Devise.add_module registers authentication modules (both built-ins like :validatable and custom ones you declare in initializers). Its :route option tells Devise which route group the module's controller routes attach to, and only three shapes are legal: true (routes named after the module itself), a Symbol (routes under that scope, e.g. route: :session), or a Hash mapping a scope to actions (e.g. route: { session: [] }). Anything else (a String, Array, integer, etc.) raises ArgumentError at boot.","triggerScenarios":"Calling Devise.add_module :my_module, route: \"sessions\" (String) or route: [:a, :b] (Array) in config/initializers/devise.rb or a custom gem's engine initializer. Passing a truthy-but-wrong-typed value pulled from ENV/YAML/CLI options into add_module. Any custom Devise module registration whose :route value is not exactly true, a Symbol, or a Hash with scope => actions shape.","commonSituations":"Writing a custom Devise module (e.g. two-factor, impersonation) and guessing the add_module API instead of copying the shape used by Devise's own registrations (lib/devise.rb calls like add_module :registerable, route: :registration); copy-pasting module code from an old fork with a different API; feeding options through a config layer that stringifies values.","solutions":["Use a Symbol naming the shared route group: Devise.add_module :my_module, route: :session — reuse an existing route key (check Devise::URL_HELPERS keys / devise.rb's own add_module calls) if the module should piggyback on existing routes.","Use true if the module needs its own route group named after the module itself: Devise.add_module :my_module, route: true.","Use a Hash when you must scope to an existing group: route: { session: [] } (keys = scope, values = extra URL helper actions array).","If the module needs no routes at all (pure model concern, like :validatable), omit the :route option entirely.","Restart the app after fixing; this raises during initializer load so a stale spring/bootsnap process can mask the fix (bin/spring stop)."],"exampleFix":"# before (config/initializers/devise.rb or custom gem initializer)\nDevise.add_module(:two_factor, model: \"devise/two_factor\", route: \"sessions\") # String -> ArgumentError\n\n# after — reuse the session route group via Symbol\nDevise.add_module(:two_factor, model: \"devise/two_factor\", route: :session)\n\n# after — own route group via true\nDevise.add_module(:two_factor, model: \"devise/two_factor\", route: true)\n\n# after — hash form, scope with no extra url helpers\nDevise.add_module(:two_factor, model: \"devise/two_factor\", route: { session: [] })","handlingStrategy":"validation","validationCode":"# Validate the :route option shape before registering a custom module:\nroute = :session # or true / { session: [] }\nunless route == true || route.is_a?(Symbol) || route.is_a?(Hash)\n  raise ArgumentError, \"devise :route must be true, a Symbol, or a Hash (got #{route.class})\"\nend\nDevise.add_module(:two_factor, model: \"devise/two_factor\", route: route)","typeGuard":"def valid_devise_route_option?(value)\n  value == true || value.is_a?(Symbol) || value.is_a?(Hash)\nend","tryCatchPattern":"# Wrap initializer-time registration so a bad option fails with file context:\nbegin\n  Devise.add_module(:two_factor, route: route_option)\nrescue ArgumentError => e\n  raise ArgumentError, \"#{e.message} (from config/initializers/devise.rb, module :two_factor)\"\nend","preventionTips":["Copy the exact add_module call shapes from devise.rb's own registrations (e.g. add_module :recoverable, route: :password) instead of guessing the API.","Keep custom module registration in one initializer and boot the app (or run rails runner 'Devise.add_module' smoke checks) in CI to catch bad options.","If options flow from config files or ENV, normalize them before they reach add_module (strings won't be coerced for you).","Remember :route is optional — omit it for model-only modules like :validatable."],"tags":["devise","rails","configuration","custom-module","boot-error"],"backgroundTag":"invalid-configuration-value","analyzedSha":"372b295fe6f63b4af3269f5dcd51a18c0bc2016c","analyzedAt":"2026-08-21T11:55:31.116Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}