Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ActionMailer's default_url_options: How to retrieve port automatically from DefaultOptions?

I often have many Rails apps running in parallel, so I have configured them to use different ports:

# Change default port of development server, see http://stackoverflow.com/questions/18103316
require 'rails/commands/server'
module DefaultOptions
  def default_options
    super.merge!(Port: 3001)
  end
end

Now it's important that this also works for ActionMailer in development env:

config.action_mailer.default_url_options = {host: 'localhost:3001'}

But instead of hardcoding this, I'd like to set it directly from DefaultOptions. How is this possible?

like image 459
Joshua Muheim Avatar asked Apr 03 '15 11:04

Joshua Muheim


2 Answers

Here is a small deviation from your code:

# config/boot.rb
require 'rails/commands/server'
module DefaultOptions
  PORT = 3001

  def default_options
    super.merge!(Port: PORT)
  end
end

# This line was part of the Stack Overflow answer you quoted, and is important
# With newer Ruby versions, you can call `prepend` directly
Rails::Server.prepend(DefaultOptions)

This follows the Stack Overflow answer you quoted, with the exception of extracting the port to the constant DefaultOptions::PORT. Now note that there is nothing magical about the name of the module DefaultOptions, it simply defines a plain module that is then prepended to Rails::Server. You could have named it however you wanted. When the development server launches, a new Rails::Server object is instantiated, and at some point the default_options method is called on that object. Because of the use of prepend, the method lookup will first reach the method you defined in DefaultOptions. The super in that method simply calls the original un-prepended default_options defined in Rails::Server.

The reason why it is "hard" to get the values in default_options is because it is an instance method, meaning you can access it only on an instance of Rails::Server class, and we don't typically have a hold of the server object. You could access it like this:

# config/development.config
require 'rails/commands/server'
Rails::Server.new.default_options[:Port]

But I think this is an unnecessary dependency and object creation. The name Rails::Server also implies we may want to have just one object of this class, and I wouldn't instantiate server objects just to get a hold of their configuration hash. Therefore, extracting the port out to a constant you can hold regardless of whether you have a reference to a server object - DefaultOptions::PORT - is cleaner in my mind.

So, now that we got a hold of DefaultOptions::PORT constant, you can use it in your mailer:

# config/development.rb
  config.action_mailer.smtp_settings = {
  :port => DefaultOptions::PORT,
  :address => '...',
  :user_name => '...',
  :password => '...',
  :domain => '...',
  :authentication => :plain
}

You could also consider having the mailer and port definition in a yaml file, so that you do not need to "sprinkle" parts of your configuration in different locations - it might save you some head banging later.

If you'd like to do this, you could create a wrapper class yourself that uses YAML.load_file to load your new yaml configuration file into a hash. Alternatively, check out Figaro gem, it gives a convenient way to place all Rails configurations in a single file - application.yml - and access them from everywhere using ENV.

If you were to use Figaro, for example, and have the PORT key in application.yml, then your code may look like this:

# config/boot.rb
require 'rails/commands/server'
module DefaultOptions
  def default_options
    super.merge!(Port: ENV['PORT'])
  end
end

Rails::Server.prepend(DefaultOptions)

# config/development.rb
config.action_mailer.smtp_settings = {
  :port => ENV['PORT'],
  :address => ENV['SMTP_SERVER'],
  :user_name => ENV['SMTP_LOGIN'],
  :password => ENV['SMTP_PASSWORD'],
  :domain => ENV['MAILER_DOMAIN'],
  :authentication => :plain
}
like image 86
AmitA Avatar answered Nov 15 '22 10:11

AmitA


You can use the Rails::Server::Options to get the information you need and then configure the ActionMailer accordingly:

config.action_mailer.default_url_options = { host: Rails::Server.new.options[:Host], port: Rails::Server.new.options[:Port] }
like image 34
Paulo Fidalgo Avatar answered Nov 15 '22 10:11

Paulo Fidalgo