Class: Moderate::Engine

Inherits:
Rails::Engine
  • Object
show all
Defined in:
lib/moderate/engine.rb

Overview

The Rails engine that wires ‘moderate` into a host app.

It’s an ISOLATED engine (‘isolate_namespace Moderate`) for the same reason Devise’s is: the gem ships a mountable public DSA notice form (‘mount Moderate::Engine => “/legal”`, see docs/dsa-notice-form.md) with its own controllers, views, helpers, and `moderate_`-prefixed tables. Isolation keeps every one of those from colliding with the host app’s namespace, and gives the host the ‘moderate.` URL-helper proxy for the mounted routes.

NOTE: the engine is intentionally thin. The host can use the whole gem (reporting, blocking, filtering, the queue) WITHOUT mounting anything — the mount is only needed for the public Art. 16 notice form. Everything here is plumbing that runs whether or not the engine is mounted.

Constant Summary collapse

LIB_ROOT =

The autoloadable code that needs the host’s ActiveRecord/ApplicationRecord and the host’s ‘config.user_class` to exist before it loads — the four AR models, the three model concerns, the async classify job, the filter adapters, and the service objects — lives under `lib/moderate/models,jobs,filters,services`, NOT under the engine’s ‘app/` tree. That’s a deliberate gem-packaging choice (everything ships under ‘lib/`), but it means we have to teach the host’s Zeitwerk loader about that subtree ourselves, with three corrections:

1. NAMESPACE. A bare `eager_load_paths << ".../lib/moderate"` would map the
   directory to the TOP-LEVEL namespace, so Zeitwerk would expect
   `lib/moderate/configuration.rb` to define `::Configuration` (not
   `Moderate::Configuration`) and blow up with "uninitialized constant
   Configuration" during eager load. We push the dir mapped to the `Moderate`
   module instead, so `lib/moderate/models/report.rb` → `Moderate::Report`.
   (Zeitwerk's `push_dir(path, namespace:)` is exactly for this — see
   https://github.com/fxn/zeitwerk#custom-root-namespaces.)

2. COLLAPSED DIRECTORIES. `models/`, `models/concerns/`, and `jobs/` are
   organizational, not namespace, segments — `report.rb` must be
   `Moderate::Report`, NOT `Moderate::Models::Report`; `actor.rb` must be
   `Moderate::Actor`, not `Moderate::Models::Concerns::Actor`. We `collapse`
   them so they contribute no namespace. (`services/` and `filters/` are
   KEPT, because the code intentionally namespaces `Moderate::Services::*`
   and `Moderate::Filters::*`.)

3. IGNORED FILES. The gem's SPINE (`version`, `errors`, `label`, `result`,
   `event`, `configuration`, `engine`, `macros`) is `require_relative`'d from
   `lib/moderate.rb` so the value objects work in a plain-Ruby context with
   no Rails — those files MUST NOT also be managed by Zeitwerk (a constant
   can't be both manually required and autoloaded). The 0.x compatibility
   shims (`text`, `text_validator`, `word_list`) are ignored too: they either
   define non-conventional constants (`text_validator.rb` reopens
   `ActiveModel::Validations`, not a `Moderate::*` constant) or are legacy and
   loaded only on demand.

We do this on the host’s MAIN autoloader (‘Rails.autoloaders.main`) inside an initializer scheduled BEFORE `:set_autoload_paths`, the same point Rails wires up its own roots — so our `push_dir`/`collapse`/`ignore` are in place before Zeitwerk’s ‘setup`/`eager_load` ever run.

File.expand_path("..", __dir__)
MODERATE_LIB =

> …/lib/moderate

File.expand_path("moderate", LIB_ROOT)
ZEITWERK_IGNORED =

Spine + 0.x-compat files Zeitwerk must NOT manage (they’re require_relative’d from the spine or define non-conventional constants).

%w[
  version.rb errors.rb label.rb result.rb event.rb
  configuration.rb engine.rb macros.rb
  text.rb text_validator.rb word_list.rb
].freeze