Demistifying Rails default_url_options
The way default_url_options work is not very intuitive, here are my findings for Rails versions 7.0 and above.
I believe that a refactoring would be in order (I might as well try to make a PR one day) and that they should be centralized, favouring dynamic over static options.
url_options vs default_url_options
A URL is essentialy built by merging
explicit args > default_url_options > request-derived values
You should only deal with default_url_options. Do not touch url_options, it is an internal method used to build URLs.
What can you put in there?
┌─────────────────┬───────────────────────────────────────────────────────────────────────┐
│ :host │ The hostname (required for _url helpers unless derived from request) │
│ :port │ Port number, e.g. 3000 │
│ :protocol │ e.g. "http" or "https" │
│ :subdomain │ Subdomain portion; false strips all subdomains │
│ :domain │ Domain portion, used with :tld_length │
│ :tld_length │ Number of TLD labels, defaults to 1 (used with :subdomain/:domain) │
│ :anchor │ Fragment appended to URL, e.g. "section-1" │
│ :params │ Query string parameters hash │
│ :path_params │ Parameters only used for named dynamic segments; extras are discarded │
│ :trailing_slash │ If true, adds a trailing slash │
│ :script_name │ Path prefix relative to domain root (for mounted engines) │
│ :only_path │ If true, returns relative URL (no host/protocol) │
└─────────────────┴───────────────────────────────────────────────────────────────────────┘
Plus any route segment parameters (like :locale) which are merged into the path if the route defines them, or appended as query params if it doesn’t.
Relationships between the various items
Rails.application.default_url_options is an alias for Rails.application.routes.default_url_options, both reading and writing.
ActionMailer::Base.default_url_options is a separate object. It is populated dynamically from config.action_mailer.default_url_options when the railtie is initialized in a configuration block.
I’ve found some references to config.action_controller.default_url_options but AFAICT it doesn’t exist.
The various contexts
Keeping present that explicit args always have the priority, this is where default_url_options come from
Controllers
By default the controller will add to the url_options (not default!) host, port and proto.
You can define a default_url_options controller instance method, and this becomes the priority chain:
explicit args
> controller's default_url_options
> request-derived values (host, port, protocol)
> Rails.application.routes.default_url_options
The options are merged, not replaced.
Mailers
Mailers can have their own separate default_url_options.
They can be an instance method on the mailer class.
You can also define them globally as ActionMailer::Base.default_url_options. The railtie initializer actually copies whatever you set as config.action_mailer.default_url_options in a rails configuration block to an ActionMailer::Base.default_url_options class attribute. If you try to set a value after the initialization is complete you must act directly on ActionMailer::Base.default_url_options.
explicit args
> mailer's default_url_options (instance method, if defined)
> ActionMailer::Base.default_url_options
> Rails.application.routes.default_url_options
Important thing to keep in mind: the instance method will override the class attribute (if defined) unless it calls super.
Direct access
You might want to use URL helpers in a different context (models, jobs, service objects, presenters).
You can call the methods on the url helpers like this
Rails.application.routes.url_helpers.posts_path
In this case there will be no default_url_options instance method available, so it will just pick up the global Rails.application.routes.default_url_options.
You can otherwise include the helpers in your class (importing a lot of methods) with
include Rails.application.routes.url_helpers
And then you’ll be free to implement your own default_url_options method.
My recommendations
Try to keep the configuration simple. Define the static options in the config/environment/*.rb files. Make sure you are aware of any default_url_options instance method defined in controllers or mailers. Watchout for URL helper calls in other contexts. Avoid the draper gem, that makes it even more complicated.