Demistifying Rails default_url_options

The way default_url_options work is not very intuitive, here are my findings for verions 7.0 and above.

url_options vs default_url_options

You should only deal with default_url_options. url_options is used internally and you should not touch it.

A URL is essentialy built by merging

explicit args  >  default_url_options  >  request-derived values

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.

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 adefault_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. You define them in the environment file as config.action_mailer.default_url_options and they are copied by the railtie to aActionMailer::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
    > specific mailer's default_url_options (instance method, if defined)
      > ActionMailer::Base.default_url_options (class attribute, via config.action_mailer.default_url_options)
        > Rails.application.routes.default_url_options

The instance method will override the class attribute (if defined) unless it calls super.

Direct access

When you do something like Rails.application.routes.url_helpers.posts_path then the dynamicdefault_url_options will be empty, but it still picks up Rails.application.routes.default_url_options. If you have dynamic bits you will need to pass them explicitly, or include the method and define your default_url_options (see below).

Including the url helpers

When in your class you include Rails.application.routes.url_helpers then you can define a default_url_options method in your class