Default URL Options in Ruby on Rails

Reading time: 7 minutes

Ruby on Rails default_url_options in console

Like most Ruby developers you have to set a default_url_options on a Ruby on Rails project. Depending on the use case you have to set several of them with different values.

The name of the method default_url_options is the same, but it is used in different contexts (controller, mail, or file storage, it may be confusing and can even have side effects for your project you have to figure out yourself. Depending on the value you set, they may be reused in other contexts like ActiveStorage, ActionMailer, and third-party gems like devise. I couldn’t find any official documentation for the most common cases (web, mail, file storage, test), I’ve written one.

Building URLs in Ruby on Rails

First of all, let’s figure out, what this setting is used for and dependent on the setup in which contexts it is optional and where it is mandatory to be set or to overwrite.

Start the rails console in development RAILS_ENV=development and check the values. This is for a newly created Ruby on Rails 7 project:

# open the ruby on rails console (or bundle exec rails console)
bin/rails console
Loading development environment (Rails 7.0.7.2)
# check default URL (if not overwritten)
irb> app.url_options
=> {:host=>"www.example.com", :protocol=>"http"}
# build the root URL for the application
irb> app.root_url
=> "http://www.example.com/"
# check the global application setting
irb> Rails.application.default_url_options
=> {}
irb> Rails.application.routes.default_url_options
=> {}

We can see in the rails console and read in the documentation, the default value for the host is www.example.com and for the protocol it is http. These values are configured, they must be set, before the application will be instantiated:

bin/rails console
Loading development environment (Rails 7.0.7.2)
# check preset values
irb> app.root_url
=> "http://www.example.com/"
irb> Rails.application.routes.default_url_options = { host: "localhost", protocol: "http" }
# if you check now, you will find the old values
irb> app.url_options
=> {:host=>"www.example.com", :protocol=>"http"}
irb> app.root_url
=> "http://www.example.com/"
# create a new instance of app object with previously defined url options
irb> app(create = true)
app(create=true)
=>
#<ActionDispatch::Integration::Session:0x00000001078faab8
 @_mock_session=nil,
 @_routes=nil,
 @accept="text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
 @app=#<Ikos::Application>,
 @controller=nil,
 @host="localhost",
 @https=false,
 @named_routes_configured=true,
 @remote_addr="127.0.0.1",
 @request=nil,
 @request_count=0,
 @response=nil,
 @url_options=nil>
# check the new qpp url_options value
irb> app.url_options
=> {:host=>"localhost", :protocol=>"http"}
# generate a root_url
irb> app.root_url
=> "http://localhost/"

Additional: See all available objects and methods in the ruby on rails console Rails::ConsoleMethods.

Requests and URLs

If you request an URL through a browser, the URL is part of the request and is provided within the request object:

# open the Ruby on Rails Console
$ bin/rails c
Loading development environment (Rails 7.0.7.2)
# Make a GET request to the application
irb> app.get "http://localhost:3000"
Started GET "/" for 127.0.0.1 at 2023-09-01 10:39:45 +0200
Processing by HomeController#index as HTML
  Rendering layout layouts/application.html.slim
  Rendering home/index.html.slim within layouts/application
  Rendered home/index.html.slim within layouts/application (Duration: 0.5ms | Allocations: 270)
  Rendered layout layouts/application.html.slim (Duration: 5.6ms | Allocations: 4361)
Completed 200 OK in 7ms (Views: 6.1ms | ActiveRecord: 0.0ms | Allocations: 4903)
# get the request object
irb> request = app.request
=> #<ActionDispatch::Request GET "http://localhost:3000/" for 127.0.0.1>
# read the URL from request
irb> request.url
=> "http://localhost:3000/"

The URL is part of a web request and is provided through HTTP protocol.

ActionMailer and URLs

ActionMailer doesn’t have a request. It don’t know which URL it has to use in the emails. You have to set it dependent on your use case.

ActionMail setup for devise gem

Almost all Ruby on Rails developers used the devise gem at some point and have run the setup, where you need a default_url_options for different environments (development, test, production, and maybe staging):

# config/environments/development.rb
config.action_mailer.default_url_options = {
  host: "localhost", port: 3000
}
# config/environments/test.rb
config.action_mailer.default_url_options = {
  host: "www.example.com", protocol: "http"
}
# config/environments/production.rb
config.action_mailer.default_url_options = {
  host: "www.production-site.com", protocol: "https"
}

Setting this parameter allows devise through ActionMailer to build URLs for a password reset, password confirmation, and all the other emails. This setup is ActionMailer related. It is valid for sending mails and most (if not all) email-related third-party gems.

ActionMailer and Rails Assets

If you want to use rails assets like images (logo, etc. located in app/assets/images) in emails, you have to define, which asset URL should be used. ActionMailer can be the same URL or another server, where the assets are hosted:

# define for every environment
# example in config/environments/production.rb
  config.action_mailer.asset_host = "https://www.production-site.com"

If you don’t define an asset host, the files linked will be without an URL and can’t be loaded in emails.

HINT: Assets can also be delivered from a third-party service or a CDN network. Check where your assets are located.

ActionMailer and ActiveStorage URLs in mails

If you want to use ActiveStorage for delivering images, files, etc. in emails, you have to define default URL options for Rails routes:

# config/environments/production.rb
Rails.application.routes.default_url_options = {
  host: "www.production-site.com",
  protocol: "https"
}

Check the ActiveStorage description below for more details.

ActiveStorage and URLs

By default, Ruby on Rails delivers any ActiveStorage files through a controller, which verifies, that the user can access the corresponding attachment. According to DiskService#url_helpers method ActiveStorage is using Rails.application.routes.url_helpers by default:

def url_helpers
  @url_helpers ||= Rails.application.routes.url_helpers
end

To set a default URL for ActiveStorage, you have to set it in routes default_url_options:

# config/environments/production.rb
Rails.application.routes.default_url_options = { host: "www.production-site.com",
                                                 protocol: "https" }

Previously I’ve written a post regarding Define Default URL for ActiveStorage to fix Mixed Content Error which has some more details around ActiveStorage and URLs.

Rails testing environment and default URL options

By default the Ruby on Rails test environment uses these settings for URL:

$ RAILS_ENV="test" bin/rails console
Loading test environment (Rails 7.0.7.2)
irb> app.url_options
=> {:host=>"www.example.com", :protocol=>"http"}

Don’t change the values for the test environment or it may break your browser tests (in Rails called system tests). If you don’t combine the problem with this specific change, it will cost you a lot of time, to find the reason for the problem.

Third-party Gems and URLs

Dependent on the gem you have to check, which URL generator method or configuration the gem is using. I’ve checked the devise gem and noticed gem. They both use the default_url_options from routes:

Rails.application.routes.default_url_options

Check the documentation and maybe the code, if it is missing in documentation.

Things can get confusing

You have seen, there are several places, where you can and sometimes should set up default_url_options. If you don’t set any of the values, you will have errors like:

$ bin/rails console
irb> Rails.application.default_url_options
=> {}
irb> Rails.application.routes.default_url_options
=> {}
# query the URL for the first user
irb> Rails.application.routes.url_helpers.url_for(User.first)
  ruby/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.0.7.2/lib/action_dispatch/http/url.rb:64:in `full_url_for': Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true (ArgumentError)

            raise ArgumentError, "Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true"
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Now we set the missing value and check it again:

$ bin/rails console
irb> Rails.application.default_url_options = { host: "localhost", protocol: "http" }
# Get the URL for the first user
irb> Rails.application.routes.url_helpers.url_for(User.first)
=> "http://localhost/users/1"

But some of them are reused if set like:

bin/rails console
# check default values for application
irb> Rails.application.default_url_options
=> {}
# check default values for routes
irb> Rails.application.routes.default_url_options
=> {}
# check the value for application
irb> Rails.application.default_url_options = { host: "localhost", protocol: "http" }
# check routes value
Rails.application.routes.default_url_options
=> {:host=>"localhost", :protocol=>"http"}

If you set the value for routes, it is accessible in application configuration too:

bin/rails console
# check default values for application
irb> Rails.application.default_url_options
=> {}
# check default values for routes
irb> Rails.application.routes.default_url_options
=> {}
# check the value for application
irb> Rails.application.routes.default_url_options = { host: "localhost", protocol: "http" }
# check routes value
Rails.application.default_url_options
=> {:host=>"localhost", :protocol=>"http"}

As recommended in most newer tutorial, host, protocol and port should be set in routes configuration:

Rails.application.routes.default_url_options = { host: "localhost", protocol: "http" }

Routes changes affecting mailer and controller

If you set the value in routes, it is used in ActionMailer, ActiveStorage, and ActionController too. You don’t need to set it for each of them.

Summary

If you hit problems with URLs in Ruby on Rails, it is really hard to figure out, why and when something is wrong, missing, or has to be set. I’ve hit this in several projects in different constellations, couldn’t find a detailed explanation, so it was time to write one.

For further reading check this post I’ve written before targeting this whole topic in this post: Define Default URL for ActiveStorage to fix Mixed Content Error


Newsletter


See Also


Tags