Better initializer structure for Ruby applications
Initializers are one of the popular patterns used in ruby/rails/sinatra applications
to configure a globally used service or library. In most cases its a sinle file
located under config/initializers
directory and hosts a chunk of code that gets
executes on application startup.
Here's an example of sendgrid initializer from rails application:
config = YAML.load_file(Rails.root.join("config/sendgrid.yml")).symbolize_keys
ActionMailer::Base.register_interceptor(SendGrid::MailInterceptor)
ActionMailer::Base.smtp_settings = config[Rails.env]
The problem with initializers is not about the code, but mostly about understanding what's going on in there. Also, testing initializers is not necessarily an easy task since its just a chunk of code that gets loaded on startup. When deploying application for the first time it usually takes some time to get everything configured properly and you'll be getting random errors, most likely related to initializers.
So with a little bit of restructuring you can convert code into a class that does the same thing, but could be easily tested and reused. Example:
class SendgridInitializer
def initialize(env)
@env = env
end
def run
require_config
configure_mailer
end
private
def config_path
Rails.root.join("config/sendgrid.yml")
end
def config
YAML.load_file(config_path).symbolize_keys
end
def configure_mailer
ActionMailer::Base.register_interceptor(SendGrid::MailInterceptor)
ActionMailer::Base.smtp_settings = config[@env]
end
def require_config
unless File.exists?(config_path)
raise "Sendgrid config file was not found at #{config_path}"
end
end
end
And then you just invoke it in initializer file config/initializers/sendgrid.rb
:
SendgridInitializer.new(Rails.env).run
Of course this example is pretty simple, but the same approach works the same for more complex initializers (fog initializer, payment gateway initializers, etc) and could be tested the same way as your regular application code (models, services, lib, etc)