Workivo
Menu

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 destroyed
  • attributes — 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 record directly, changes save automatically
  • After_*: record is already in the DB — changes to record won't persist unless you call .save! or .update_columns()
  • Use require for any Ruby stdlib (JSON, Net::HTTP, CSV, etc.)
  • Use record.saved_change_to_attribute?("field") in after/update to detect what changed
  • Check actor to know who triggered the action; it may be nil

Back:

Please confirm

Are you sure?