Module: OpenLoam::BusinessRules::Actions
- Defined in:
- lib/open_loam/business_rules/actions.rb
Overview
The typed action vocabulary — a FIXED, safe set the engine dispatches. No
arbitrary code: an action is a json descriptor with a type and known
keys. call_webhook is deferred (emit_event → a webhook endpoint already
covers it).
Constant Summary collapse
- PLUMBING =
%w[id tenant_id lock_version deleted_at created_at updated_at].freeze
Class Method Summary collapse
-
.refuse_credential_column!(field) ⇒ Object
A business rule has no business setting credentials, even on a tenant-scoped record: never a password column, a *_digest, or email.
-
.refuse_cross_tenant!(record) ⇒ Object
Belt-and-suspenders: subject_for only ever loads a current-tenant record, but never write one whose tenant is not the tenant in context.
- .refuse_workflow_column!(record, field) ⇒ Object
-
.run(action, record) ⇒ Object
Run one action against the record that triggered the rule.
-
.run_emit_event(action) ⇒ Object
Publish validates the name format (raising InvalidEventNameError), so a malformed event name is caught by the engine's per-rule isolation.
- .run_notify(action, _record) ⇒ Object
-
.run_set_field(action, record) ⇒ Object
Assign a whitelisted attribute or custom field on the TRIGGERING record.
- .writable_column?(klass, field) ⇒ Boolean
Class Method Details
.refuse_credential_column!(field) ⇒ Object
A business rule has no business setting credentials, even on a tenant-scoped record: never a password column, a *_digest, or email.
75 76 77 78 79 80 |
# File 'lib/open_loam/business_rules/actions.rb', line 75 def self.refuse_credential_column!(field) normalized = field.downcase return unless normalized.include?("password") || normalized.end_with?("_digest") || normalized == "email" raise ArgumentError, "set_field must not write the credential column #{field.inspect}" end |
.refuse_cross_tenant!(record) ⇒ Object
Belt-and-suspenders: subject_for only ever loads a current-tenant record, but never write one whose tenant is not the tenant in context.
84 85 86 87 88 89 |
# File 'lib/open_loam/business_rules/actions.rb', line 84 def self.refuse_cross_tenant!(record) return unless record.respond_to?(:tenant_id) return if record.tenant_id == OpenLoam.tenant!.id raise ArgumentError, "set_field refuses a record outside the current tenant" end |
.refuse_workflow_column!(record, field) ⇒ Object
67 68 69 70 71 |
# File 'lib/open_loam/business_rules/actions.rb', line 67 def self.refuse_workflow_column!(record, field) return unless record.class.respond_to?(:open_loam_workflow) && record.class.open_loam_workflow&.column == field raise ArgumentError, "set_field must not write the workflow column #{field.inspect} — use a transition" end |
.run(action, record) ⇒ Object
Run one action against the record that triggered the rule. Returns a
short label of what it did (for the execution log). :veto is the
block_transition signal (see OpenLoam::BusinessRules.veto?).
13 14 15 16 17 18 19 20 21 |
# File 'lib/open_loam/business_rules/actions.rb', line 13 def self.run(action, record) case action["type"].to_s when "notify" then run_notify(action, record) when "emit_event" then run_emit_event(action) when "set_field" then run_set_field(action, record) when "block_transition" then :veto else raise ArgumentError, "unknown action type #{action["type"].inspect}" end end |
.run_emit_event(action) ⇒ Object
Publish validates the name format (raising InvalidEventNameError), so a malformed event name is caught by the engine's per-rule isolation.
37 38 39 40 |
# File 'lib/open_loam/business_rules/actions.rb', line 37 def self.run_emit_event(action) OpenLoam::Events.publish(action["name"], (action["payload"] || {}).symbolize_keys) "emit_event(#{action["name"]})" end |
.run_notify(action, _record) ⇒ Object
23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/open_loam/business_rules/actions.rb', line 23 def self.run_notify(action, _record) title = action["title"].to_s body = action["body"] if action["role"].present? OpenLoam::Notifications.notify_role(action["role"], title: title, body: body) elsif action["user_id"].present? user = User.find_by(id: action["user_id"]) OpenLoam::Notifications.notify(user, title: title, body: body) if user end "notify" end |
.run_set_field(action, record) ⇒ Object
Assign a whitelisted attribute or custom field on the TRIGGERING record. Refused: the workflow status column (would bypass the transition gate — use a transition), and plumbing columns. The change is saved as the event's actor, and the rule is identifiable via the execution log.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/open_loam/business_rules/actions.rb', line 46 def self.run_set_field(action, record) field = action["field"].to_s refuse_workflow_column!(record, field) refuse_credential_column!(field) refuse_cross_tenant!(record) if writable_column?(record.class, field) record.update!(field => action["value"]) elsif Condition.custom_field?(record, field) record.set_custom_field(field, action["value"]) record.save! else raise ArgumentError, "set_field: #{field.inspect} is not a writable column or custom field" end "set_field(#{field})" end |
.writable_column?(klass, field) ⇒ Boolean
63 64 65 |
# File 'lib/open_loam/business_rules/actions.rb', line 63 def self.writable_column?(klass, field) klass.column_names.include?(field) && PLUMBING.exclude?(field) end |