Default URL Options in Ruby on Rails
Reading time: 7 minutes
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
- Define Default URL for ActiveStorage to fix Mixed Content Error
- Working with Legacy Ruby on Rails: spring.gem fork() Crash
- Analyzing SassC::SyntaxError in Ruby on Rails 7.0
- Deploy Ruby on Rails 7.0 to Dokku micro PaaS
- Fixing require LoadError as an example for the matrix gem