I was recently part of a Mastodon discussion about the best way to run code in a Rails application before Puma boots. The need for this is that sometimes you want some code that only runs within a webserver, not when running rake tasks or rails commands or opening the console.

Here we go…

Via server do blocks

Rails 6.1 added aRailtie#server hook that can be invoked in application.rb or a gem’s engine.rb. It’s invoked when the config.ru file is evaluated.

Warning: the invocation of the hook in config.ru is missing from a lot of Rails projects. I once lackadaisically tried to address this upstream, but doublecheck your config.ru actually contains Rails.application.load_server as it should.

In your config.ru

The Rackup configuration file only gets evaluated by a webserver, so that is a natural place to invoke your webserver-only code.

Aside: like boot.rb, I either find interesting things lurking around in configu.ru , or it’s one of those files that hasn’t been touched since rails new was first run.

As a Puma plugin

If you’re dedicated to Puma, write a simple plugin for it. Puma plugins also allow hooking into more sophisticated places than simply “before it starts”.

tbh, I wasn’t aware of Puma plugins as an integration point until Solid Queue shipped a Puma plugin for its in-webserver mode. I like it!

By searching the callstack

I just mentioned Solid Queue has having an admirable integration point. My GoodJob has an… ok one: searching the callstack for webserver fingerprints. I chose this path at the time for the broadest zero-configuration compatibility with older versions of Rails, different webservers, and JRuby, but I wouldn’t recommend it very strongly today because it feels brittle:

def in_webserver?
  return @_in_webserver unless @_in_webserver.nil?

  @_in_webserver = Rails.const_defined?(:Server) || begin
    self_caller = caller
    self_caller.grep(%r{config.ru}).any? || # EXAMPLE: config.ru:3:in `block in <main>' OR config.ru:3:in `new_from_string'
      self_caller.grep(%r{puma/request}).any? || # EXAMPLE: puma-5.6.4/lib/puma/request.rb:76:in `handle_request'
      self_caller.grep(%{/rack/handler/}).any? || # EXAMPLE: iodine-0.7.44/lib/rack/handler/iodine.rb:13:in `start'
      (Concurrent.on_jruby? && self_caller.grep(%r{jruby/rack/rails_booter}).any?) # EXAMPLE: uri:classloader:/jruby/rack/rails_booter.rb:83:in `load_environment'
  end || false
end

Other thoughts

There is a lot of trickiness to this problem. Mastodon is sorta special because it’s an application that is distributed. As opposed to your own specially customized Rails application you’re wholly developing, or a narrow gem/engine that you’re distributing to be installed in someone else’s application.

It’s tough to recommend a single best option. For a gem or engine, like Solid Queue or GoodJob, you’re probably focused most on the simplest and trouble-free installation with the widest compatibility. For your own application, the goal is probably not leaving a forgotten footgun to be stumbled over in the future. Good luck!