Workivo Docs: Rules Engine
Rules Engine
Rules are Ruby scripts that run automatically when records are created, updated, or deleted. You create and manage them entirely through the Workivo UI — no code deployment needed.
Available variables
Every rule script has access to these variables:
record— the record being created, updated, or destroyedattributes— the form values submitted by the user (Hash with symbol keys)actor— the User who triggered the action (nil if system-triggered)state— the full state hash{ record:, attributes: }
Before vs after — what persists
In before_* stages, the record hasn't been saved yet. Any changes you make to record will be included when the save happens automatically.
In after_* stages, the record is already committed to the database. Changes you make to record will NOT be saved — you must explicitly call record.update_columns(...) or record.update(...) if you want to modify the record after it's been saved.
You can always create other records (Log.create!, Page.create!, etc.) in any stage — those get saved immediately.
Condition
Optional Ruby code. The script runs only when the condition returns a truthy value. If blank, the script always runs. Same variables available as in the script.
CRUD examples
before/create — Modify the record before it saves
See String docs for methods like strip, split, capitalize, join.
record.name = record.name.strip.split.map(&:capitalize).join(" ")
record.description = "Created by #{actor&.username}"
after/create — Log that something was created (record is already saved)
See JSON docs for formatting data.
Log.create!(
source: "rule",
message: "Created #{record.class} ##{record.id}",
context: { actor: actor&.username }.to_json
)
before/update — Validate before allowing an update
if record.name.blank?
raise Rules::RuleError, "Name cannot be empty"
end
after/update — Update a different record based on this change
if record.saved_change_to_attribute?("status")
page = Page.find_by(slug: "dashboard")
if page
page.content = "Status changed to #{record.status}"
page.save!
end
end
before/destroy — Block deletion under certain conditions
if actor&.username == record.created_by
raise Rules::RuleError, "You cannot delete your own record"
end
after/destroy — Clean up related data
Page.where(created_by: record.username).update_all(archived: true)
REST call example
See the Net::HTTP docs and URI docs for the full API.
require "net/http"
require "uri"
require "json"
uri = URI("https://api.example.com/webhook")
body = { event: "created", id: record.id, type: record.class.name }.to_json
headers = { "Content-Type" => "application/json" }
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == "https"
request = Net::HTTP::Post.new(uri, headers)
request.body = body
response = http.request(request)
Log.create!(source: "rule", message: "Webhook sent: #{response.code}")
Working with a different record
You're not limited to the record that triggered the rule. You can query and modify any table:
admin = User.find_by(username: "admin")
if admin
pref = admin.preferences.find_or_initialize_by(entity: "notifications")
pref.value = "last_activity_at: #{Time.current}"
pref.save!
end
Tips
- Each rule runs in its own isolated scope — local variables don't leak between rules
raise Rules::RuleError, "message"to abort the action (before_* stages) or show an error- Before_*: modify
recorddirectly, changes save automatically - After_*: record is already in the DB — changes to
recordwon't persist unless you call.save!or.update_columns() - Use
requirefor any Ruby stdlib (JSON, Net::HTTP, CSV, etc.) - Use
record.saved_change_to_attribute?("field")in after/update to detect what changed - Check
actorto know who triggered the action; it may be nil
Back: