perchfall-rails

CI Gem Version License: MIT

A mountable Rails engine that adds a synthetic monitoring dashboard and background job pipeline to any Rails application, powered by the perchfall gem.

Why perchfall-rails

perchfall gives you a Report value object — what a real Chromium browser saw at a URL. It deliberately does not impose a database schema or job queue; you bring your own.

perchfall-rails ships the Rails pieces:

  • Schema and persistence — a SyntheticRun ActiveRecord model that stores every check, with denormalized error counts and the full report JSON for later inspection.
  • Background job — a Perchfall::Rails::RunJob ActiveJob subclass you can perform_later (including with ignore: rules), with sensible retry behavior.
  • Dashboard — paginated, filterable index of runs and a detail view that surfaces every network and console error.
  • Auth-agnostic — point base_controller at any controller in your app and inherit its authentication.
  • Extensible — host-facing seams let you add associations, scope the dashboard to the current user, and react to each persisted run without monkey-patching the engine. See ADR 0004.

If you only need the Ruby API, use perchfall directly. If you want a Rails-idiomatic place for synthetic results to live, mount this.

Requirements

  • Ruby >= 3.3
  • Rails >= 8.0
  • Node.js >= 18 with the playwright npm package and a Chromium binary (installed via the engine's rake task — see below)
  • pagy ~> 43.0 (bundled as a dependency)

Installation

Add to your Gemfile:

gem "perchfall-rails"

Install the gem, the initializer, the engine migrations, and Playwright:

bundle install
bin/rails generate perchfall:rails:install
bin/rails perchfall_rails:install:migrations
bin/rails db:migrate
bin/rails perchfall_rails:install:playwright

Mount the engine in config/routes.rb:

mount Perchfall::Rails::Engine, at: "/perchfall"

Verify the Node/Playwright/Chromium environment:

bin/rails perchfall_rails:check

The dashboard is now available at /perchfall/synthetic_runs.

Quick start

Enqueue a synthetic run from anywhere in your application:

Perchfall::Rails::RunJob.perform_later(url: "https://example.com")
# => #<Perchfall::Rails::RunJob ...>

When the job runs, it creates one SyntheticRun record with the full Chromium report, network errors, and console errors. View it at /perchfall/synthetic_runs.

To suppress known-noisy errors, pass ignore: rules:

rule = Perchfall::IgnoreRule.new(
  pattern: "chrome-error://",
  type:    "*",
  target:  :console
)

Perchfall::Rails::RunJob.perform_later(
  url:           "https://example.com",
  scenario_name: "homepage",
  ignore:        [rule]
)

Configuration

The install generator creates config/initializers/perchfall_rails.rb with base_controller set to "ApplicationController" (with a warning logged at boot in production until you change it) and the optional attributes pre-commented with sample values. A configured initializer looks like:

Perchfall::Rails.configure do |config|
  config.base_controller = "AuthenticatedController"

  # config.queue_name              = :synthetic
  # config.overrides_stylesheet    = "perchfall_rails_overrides"
  # config.after_persist           = ->(run) { SomeJob.perform_later(run.id) if run.failed? }
  # config.synthetic_run_extension = "SyntheticRunExtension"
end
  • base_controller — controller the engine inherits from. Required in production; the engine logs a warning at boot if this is still ApplicationController in a production environment. Must inherit from ActionController::Base.
  • queue_name — ActiveJob queue used by RunJob. Defaults to :synthetic.
  • overrides_stylesheet — optional path (without extension) to a host stylesheet loaded after the engine CSS. Use to override --perchfall-* CSS custom properties.
  • after_persist — optional callable invoked after each SyntheticRun is persisted. Receives the run as a single positional argument and fires on both the happy path and RunJob's error rescues — once per persisted run, which under ActiveJob retries means once per attempt. Use run.id to route downstream work to that specific persisted run (SomeJob.perform_later(run.id)) — each retry attempt persists a distinct id, so run.id does not dedup across attempts. The engine provides no cross-attempt key today, so handlers that need once-per-failure-event semantics (notifications, ticket-opens) should be designed to be idempotent on the host's natural keys, or should accept at-least-once delivery. Exceptions raised by the callable are rescued and logged; they do not interrupt the pipeline. See ADR 0006.
  • synthetic_run_extension — optional name of a host module the engine includes into Perchfall::Rails::SyntheticRun from a config.to_prepare block (so the include fires after host initializers, after eager_load in production, and again after every code reload in development). Use it to declare host-side associations, scopes, or callbacks (belongs_to :user, &c.). The named module must be autoloadable. Callbacks added through the extension run as part of the engine's SyntheticRun.create!; if one raises, the create fails, the job dies, no SyntheticRun is written (not even an error row), and after_persist does not fire. Reserve extension callbacks for declarations and logic that has been hardened against raising; for observability-style side effects, use after_persist. See ADR 0007.

Authentication

The engine has no built-in authentication. Set base_controller to a controller that enforces whatever authentication your application uses:

Perchfall::Rails.configure do |config|
  config.base_controller = "Admin::BaseController"
end

base_controller must inherit from ActionController::Base. Engine views use csrf_meta_tags, csp_meta_tag, and stylesheet_link_tag, none of which are available on ActionController::API controllers.

Scoping runs to the current user

By default the dashboard shows every SyntheticRun in the database. To scope it — e.g. each user only sees their own runs — define synthetic_runs_scope on base_controller:

class AuthenticatedController < ApplicationController
  before_action :authenticate_user!

  private

  def synthetic_runs_scope
    current_user.synthetic_runs
  end
end

The engine calls synthetic_runs_scope from both index and show. If the host returns an unordered relation, the engine merges in a default recent order so pagination stays stable. Pair this with two association declarations on the host side: a belongs_to :user on Perchfall::Rails::SyntheticRun declared via config.synthetic_run_extension (which includes the host module into the engine's model — see ADR 0007), and the matching has_many :synthetic_runs, class_name: "Perchfall::Rails::SyntheticRun" directly on your host model (e.g. User). The synthetic_run_extension seam includes its module into SyntheticRun, so it can only carry the belongs_to side; has_many belongs on whichever host model owns the association. See ADR 0005.

Retry behavior

When a run fails with Perchfall::Errors::Error, the job persists an error record and re-raises so the queue adapter can retry normally. Each retry that also fails creates another error record. To cap error records at one per enqueued job, subclass RunJob and discard_on:

class MyRunJob < Perchfall::Rails::RunJob
  discard_on Perchfall::Errors::Error
end

Theming

The engine ships its own stylesheet. Default colors render well in both light and dark mode (the dashboard follows prefers-color-scheme).

To customize colors, point config.overrides_stylesheet at a host CSS file:

Perchfall::Rails.configure do |config|
  config.overrides_stylesheet = "perchfall_rails_overrides"
end
/* app/assets/stylesheets/perchfall_rails_overrides.css */
:root {
  --perchfall-color-primary: #db2777;
  --perchfall-color-error:   #b91c1c;
}

Available tokens are defined in app/assets/stylesheets/perchfall/rails/application.css. Headline tokens: --perchfall-color-primary, --perchfall-color-error, --perchfall-color-text, --perchfall-color-muted, --perchfall-color-subtle, --perchfall-color-surface, --perchfall-color-surface-alt, --perchfall-color-border. The file also exposes typography, spacing, and radius tokens.

Hosts that need full chrome around the dashboard (admin nav, branding) can override the engine layout by placing their own app/views/layouts/perchfall/rails/application.html.erb in the host application.

Development

bin/setup
bundle exec rake test

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/beflagrant/perchfall-rails. See CONTRIBUTING.md. Release notes live in CHANGELOG.md.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the perchfall-rails project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the code of conduct.