Every integration in this gem is optional and guard-checked: if the underlying gem isn’t in your Gemfile, the integration is silently skipped.
Importmap (importmap-rails)
Per-theme importmaps
Each theme gets its own Importmap::Map stored on theme.engine.importmap. Theme pins don’t leak into the main app, and vice versa.
The integration also removes theme paths from the main app’s importmap config so they don’t appear in the root importmap twice:
app.config.importmap.paths.delete_if { |p| p.to_s.start_with?(themes_root) }Development hot-reload
A before_action in Multitenancy::Controller calls engine.importmap_reloader&.execute_if_updated on each request. When you edit a file under themes/<name>/app/javascript/, the theme’s importmap is re-drawn — no server restart needed.
Drawing the importmap in a layout
<%# themes/storefront/app/views/layouts/application.html.erb %><%= javascript_importmap_tags "storefront/application" %>Pinning packages
Pin inside the theme’s config/importmap.rb (same DSL as the main app):
pin 'application', preload: truepin '@hotwired/stimulus', to: '@hotwired--stimulus.js'pin_all_from 'app/javascript/storefront/controllers', under: 'storefront/controllers'Tailwind CSS (tailwindcss-rails, v4 only)
Compilation
Each theme has its own input file (app/assets/tailwind/<name>/application.css) compiled to its own output (app/assets/builds/<name>/application.css). Tailwind input directories are excluded from Propshaft so unprocessed CSS doesn’t leak into the asset pipeline.
Asset precompile hook
Rake::Task['assets:precompile'].enhance(['multitenancy:tailwindcss:build'])This is wired automatically by the integration — no setup needed.
Watch mode (dev)
bin/rails multitenancy:tailwindcss:watchForks a watcher process per theme, traps Ctrl-C to clean up children.
See rake-tasks.md for task details.
RSpec (rspec-rails)
Auto-discovery
Running rspec with no args discovers every theme’s spec/ directory:
bundle exec rspecExplicit paths
Passing an explicit directory expands it to include the matching theme’s spec/:
bundle exec rspec themes/storefrontNested themes
If storefront and storefront-admin exist, rspec themes/storefront runs both. Disambiguate by going one level deeper:
bundle exec rspec themes/storefront-adminMinitest (railties test runner)
Mirrors the RSpec integration. Theme test/ directories are auto-discovered:
bin/rails testExplicit paths expand the same way:
bin/rails test themes/storefront # runs themes/storefront/testFactoryBot (factory_bot_rails)
Theme factories under themes/<name>/spec/factories/ are added to FactoryBot’s definition paths automatically. Build, create, and define as usual:
FactoryBot.define do factory :storefront_product, class: 'Themes::Storefront::Product' do name { 'Widget' } endendWriting a custom integration
Follow the pattern used by every built-in integration: a class under Multitenancy::Integrations::<Name> with a .call(app) method, guarded against optional-gem absence:
module Multitenancy::Integrations class MyIntegration def self.call(app) return unless defined?(::SomeGem)
Multitenancy.themes.each do |theme| # wire up per-theme stuff here end end endendHook it into the Railtie in your application:
ActiveSupport.on_load(:multitenancy) do Multitenancy::Integrations::MyIntegration.call(Rails.application)endThe :multitenancy load hook fires after every built-in integration has run, so you can safely inspect Multitenancy.themes at that point.