When Rails is loaded, Lepus’s Railtie wires up sensible defaults. No initializer is strictly required.
What the Railtie does
- Sets
config.logger = Rails.logger(unless you’ve already set one). - Sets
config.app_executor = Rails.application.executor— every consumerperformruns inside the Rails executor, which means:- Autoloading works (Zeitwerk is active).
- Query cache is cleared after each message.
- Connection cleanup runs after each message.
- Reloading works in development.
- Subscribes the log subscriber for friendly production log lines.
- Integrates with
Rails.error— unhandled exceptions are reported there.
Overriding
Lepus.configure do |config| config.rabbitmq_url = ENV.fetch('RABBITMQ_URL') config.connection_name = 'my-service'
# Override the defaults: config.logger = MyLogger.new config.app_executor = nil # disable executor wrapping entirely
config.consumers_directory = 'app/consumers'
config.worker(:default) do |w| w.pool_size = 5 w.before_fork { ActiveRecord::Base.connection_handler.clear_all_connections! } w.after_fork { ActiveRecord::Base.establish_connection } endendDefining consumers and producers
Put them in app/consumers/ and app/producers/. With config.consumers_directory = 'app/consumers', lepus start with no arguments auto-loads all of them.
class OrdersConsumer < Lepus::Consumer configure( queue: 'orders', exchange: { name: 'orders', type: :topic, durable: true }, routing_key: 'order.*' ) use :json, symbolize_keys: true
def perform(message) Order.create!(message.payload) :ack endendclass OrdersProducer < Lepus::Producer configure(exchange: { name: 'orders', type: :topic, durable: true }) use :json use :correlation_idendRunning alongside a Rails web app
# Terminal 1 — webbin/rails server
# Terminal 2 — consumersbundle exec lepus start --require_file config/environment.rbOr use Foreman:
# Procfileweb: bin/rails serverworker: bundle exec lepus start --require_file config/environment.rbThe Puma plugin
For apps that want consumers running inside the same Puma process (development convenience, or small-scale production where you don’t want another service):
plugin :lepusThe plugin forks consumer workers as Puma’s cluster workers would, tying their lifecycle to Puma’s.
Caution: running consumers inside Puma means process restarts on deploy take longer (waiting for in-flight messages) and you can’t scale consumers independently of web requests. For non-trivial workloads, run them as a separate service.
Mounting the web dashboard
require 'lepus/web'
authenticate :user, ->(u) { u.admin? } do mount Lepus::Web::App, at: '/lepus'endSee web.md.
Testing
# spec/rails_helper.rb (or spec/spec_helper.rb)require 'lepus/testing'
RSpec.configure do |config| config.before(:each) { Lepus::Testing.enable!; Lepus::Testing.reset! }endSee testing.md.
Active Record gotchas
Since app_executor is set to Rails.application.executor, Active Record connection management is handled per message — no manual clear_active_connections! needed.
For worker subprocesses, the before_fork / after_fork hooks close and reopen connections cleanly:
Lepus.configure do |config| config.worker(:default) do |w| w.before_fork { ActiveRecord::Base.connection_handler.clear_all_connections! } w.after_fork { ActiveRecord::Base.establish_connection } endendWithout these hooks, all children inherit the same database socket and things go badly fast.
Exception reporting
Lepus’s Rails integration reports unhandled exceptions via Rails.error:
# config/application.rb or an initializerRails.error.subscribe do |exception, handled:, severity:, context:, source:| Honeybadger.notify(exception, context: context) if source == 'lepus'endOr use the :honeybadger middleware directly on your consumers — see middleware.md.
Zeitwerk
Consumer and producer classes are autoloaded by Zeitwerk like any other Rails class. app/consumers/orders_consumer.rb → OrdersConsumer, namespaced with the usual folder-to-module rules.
Generators
None at the moment. Create consumer and producer files by hand (or your editor’s snippet of choice).