perchfall-rails
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
SyntheticRunActiveRecord model that stores every check, with denormalized error counts and the full report JSON for later inspection. - Background job — a
Perchfall::Rails::RunJobActiveJob subclass you canperform_later(including withignore: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_controllerat 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
playwrightnpm 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 stillApplicationControllerin a production environment. Must inherit fromActionController::Base.queue_name— ActiveJob queue used byRunJob. 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 eachSyntheticRunis persisted. Receives the run as a single positional argument and fires on both the happy path andRunJob's error rescues — once per persisted run, which under ActiveJob retries means once per attempt. Userun.idto route downstream work to that specific persisted run (SomeJob.perform_later(run.id)) — each retry attempt persists a distinct id, sorun.iddoes 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 intoPerchfall::Rails::SyntheticRunfrom aconfig.to_prepareblock (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'sSyntheticRun.create!; if one raises, the create fails, the job dies, noSyntheticRunis written (not even an error row), andafter_persistdoes not fire. Reserve extension callbacks for declarations and logic that has been hardened against raising; for observability-style side effects, useafter_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.