Flowengine::Rails
Introduction
FlowEngine::Rails is a Rails Engine that wraps the flowengine core gem with ActiveRecord persistence, a Hotwire-based wizard UI, an admin CRUD interface, and an iframe-embeddable widget. It allows non-technical users to define multi-step form flows via a Ruby DSL, store them in the database, and serve them as interactive step-by-step wizards.
Flows support seven step types: text, number, boolean, single_select, multi_select, number_matrix, and display. Conditional transitions between steps are driven by the core engine's rule evaluator (if_rule:, contains(), etc.).
Key features
- Admin UI -- full CRUD for flow definitions with DSL validation and Mermaid diagram visualization
- Wizard UI -- Hotwire/Turbo-powered step-by-step form sessions with progress tracking
- Iframe embedding -- embed flows on external sites with auto-resizing and completion callbacks
- Versioning -- definitions are automatically versioned; once sessions exist, the definition becomes immutable
- Completion callbacks -- fire custom logic when a user finishes a flow
Requirements
- Ruby >= 4.0.1
- Rails >= 8.1.2
- The
flowenginegem (~> 0.1)
Usage and Integration into a Rails Application
Installation
Add the gem to your Gemfile:
gem "flowengine-rails"Run the install generator, which copies migrations, creates an initializer, and mounts the engine:
bin/rails generate flow_engine:install
bin/rails db:migrateThis mounts the engine at /flow_engine. To change the mount point, edit config/routes.rb:
mount FlowEngine::Rails::Engine => "/wizards"Configuration
Edit config/initializers/flow_engine.rb:
FlowEngine::Rails.configure do |config|
# Origins allowed to embed flows via iframe (use ["*"] for all)
config.embed_allowed_origins = ["https://example.com"]
# Cache parsed DSL definitions in memory (recommended for production)
config.cache_definitions = Rails.env.production?
# Callback fired when a session completes
config.on_session_complete = ->(session) {
LeadNotifier.qualified(session).deliver_later
}
# Admin authentication -- method called as a before_action
config.admin_authentication_method = :authenticate_admin!
endCreating a flow definition
Use the generator to scaffold a flow definition file and a seed rake task:
bin/rails generate flow_engine:flow lead_qualificationThis creates db/flow_definitions/lead_qualification.rb with a starter DSL template. Edit it to define your flow:
LEAD_QUALIFICATION_DSL = <<~RUBY
FlowEngine.define do
start :company_size
step :company_size do
type :single_select
question "How many employees does your company have?"
options ["1-10", "11-50", "51-200", "200+"]
transition to: :budget, if_rule: "contains(answer, '200')"
transition to: :industry
end
step :industry do
type :single_select
question "What industry are you in?"
options ["Technology", "Finance", "Healthcare", "Other"]
transition to: :budget
end
step :budget do
type :number
question "What is your annual budget for this service?"
transition to: :thank_you
end
step :thank_you do
type :display
question "Thank you! We will be in touch."
end
end
RUBYSeed it into the database:
bin/rails flow_engine:seed:lead_qualificationOr create definitions programmatically:
FlowEngine::FlowDefinition.create!(
name: "lead_qualification",
dsl: LEAD_QUALIFICATION_DSL
)The version auto-increments per name. To make it available to users, activate it:
FlowEngine::FlowDefinition.latest_version("lead_qualification").activate!Admin interface
The admin UI is available at /flow_engine/admin/definitions. To protect it, set admin_authentication_method in the initializer to a method defined on your ApplicationController:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def authenticate_admin!
redirect_to root_path unless current_user&.admin?
end
endFrom the admin UI you can create, edit, activate/deactivate definitions, and view Mermaid flow diagrams.
Embedding flows on external sites
Add embed.js to the host page and call FlowEngineEmbed.embed():
<script src="https://yourapp.com/flow_engine/assets/flow_engine/embed.js"></script>
<div id="flow-container"></div>
<script>
FlowEngineEmbed.embed({
target: "#flow-container",
definitionId: 1,
baseUrl: "https://yourapp.com/flow_engine",
onComplete: function(data) {
console.log("Flow completed:", data);
}
});
</script>The iframe auto-resizes to fit content. Make sure to add the host origin to embed_allowed_origins in the configuration.
Accessing session results
After a flow session completes, its answers are available as JSON:
session = FlowEngine::FlowSession.completed.last
session.result_json
# => { definition_name: "lead_qualification",
# definition_version: 1,
# answers: { "company_size" => "51-200", "budget" => 50000 },
# history: ["company_size", "industry", "budget", "thank_you"],
# status: "completed",
# completed_at: "2026-03-16T..." }Troubleshooting
DSL validation errors on save
The FlowDefinition model validates that the DSL string parses correctly before saving. If you see "dsl is invalid" errors, check that your DSL is valid Ruby and uses the correct FlowEngine.define { ... } syntax. Test your DSL in a console:
FlowEngine.load_dsl(your_dsl_string)"Cannot edit definition with existing sessions"
Once a FlowDefinition has associated FlowSession records, it becomes read-only to preserve data integrity. Create a new version instead:
old = FlowEngine::FlowDefinition.find(id)
FlowEngine::FlowDefinition.create!(name: old.name, dsl: updated_dsl)Iframe embedding returns no content
- Verify the host origin is listed in
config.embed_allowed_origins - Ensure the
?embed=truequery param is being passed (the embed script handles this automatically) - Check browser console for CORS errors
Sessions stuck in in_progress
A session can be abandoned manually:
FlowEngine::FlowSession.find(id).abandon!To find stale sessions:
FlowEngine::FlowSession.in_progress.where("updated_at < ?", 24.hours.ago)Migration conflicts
If your app already has tables named flow_engine_definitions or flow_engine_sessions, the install migrations will conflict. Rename or drop the existing tables before running bin/rails db:migrate.
Development
Setup
git clone https://github.com/kigster/flowengine-rails.git
cd flowengine-rails
bundle installThe flowengine core gem is expected as a sibling directory for local development:
flowengine-gems/
flowengine/ # core gem
flowengine-rails/ # this gem
Running tests
Tests use an in-memory SQLite database -- no external database setup required:
bundle exec rspec # run specs
bundle exec rubocop # run linter
bundle exec rake # both (default task)A dummy Rails app lives in spec/dummy/ and is used by controller and integration specs.
Project structure
app/
models/flow_engine/ # FlowDefinition, FlowSession
controllers/flow_engine/ # SessionsController, Admin::DefinitionsController
views/flow_engine/ # Wizard UI, admin UI, step partials
assets/ # embed.js, Stimulus controllers, CSS
lib/
flowengine/rails/ # Engine, Configuration, DslLoader
generators/flow_engine/ # install and flow generators
db/migrate/ # Definition and session tables
spec/ # RSpec suite with dummy app
CI
Three GitHub Actions workflows run on push:
-
main.yml --
bundle exec rakeon Ruby 3.4.4 -
rspec.yml --
bundle exec rspecon Ruby 4.0 (checks outflowenginecore alongside) -
rubocop.yml --
bundle exec rubocopon Ruby 4.0
Contributing
- Fork the repo
- Create a feature branch (
git checkout -b feature/my-feature) - Commit your changes (keep commits atomic, one logical change per commit)
- Ensure
bundle exec rakepasses - Open a pull request
License
Released under the MIT License.