Skip to content

Render Liquid fields by default - #1584

Open
caitmich wants to merge 10 commits into
developfrom
liquid/render-by-default
Open

Render Liquid fields by default#1584
caitmich wants to merge 10 commits into
developfrom
liquid/render-by-default

Conversation

@caitmich

@caitmich caitmich commented May 1, 2026

Copy link
Copy Markdown
Contributor

Problem

Liquid template tags in field values (e.g. `{{ project.name }}` in a Title or Description field) were not evaluated automatically. Each view that wanted rendered output had to opt in explicitly — calling a helper, passing assigns, or wiring up a filter. This led to inconsistent behaviour: the same field could appear raw in one place and rendered in another, and new views silently showed unrendered templates.

Solution

Render-by-default: `fields` and `title` on models always return Liquid-evaluated values in request contexts. `raw_fields` is the explicit escape hatch, used only when editing, diffing, or writing to the database.

Three moving parts:

1. LiquidRenderContext (app/lib/liquid_render_context.rb, new)

  • Thread-local context holder with `set`, `clear`, `current`, and `render`.
  • Controllers store a lazy proc before the action runs; `render` resolves it on first use — after all `before_action`s have run and instance variables are set (so that liquid_resource_assigns has been set).
  • Errors from Liquid evaluation fall back to the raw value rather than raising.

2. `HasFields#dradis_has_fields_for`

  • raw_fields contains the existing parser logic, unchanged.
  • fields renders via LiquidRenderContext.render when a context is present and memoizes the result on the instance.
  • A reentrancy guard (@_rendering_fields) prevents infinite loops when a field value references the same record's fields via a drop (e.g. {{ issue.fields['Impact'] }} inside another field).
  • set_field and delete_field write through raw_fields so rendered output is never saved back to the database.
  • title inherits rendering automatically since it delegates to fields.fetch('Title', ...).

3. LiquidEnabledResource + ProjectScoped

  • with_liquid_render_context lives in LiquidEnabledResource, which is now included by ProjectScoped. All project-scoped controllers get Liquid rendering automatically. Non-project controllers that need it (e.g. MarkupController) include LiquidEnabledResource directly.

DiffedContent is updated to use raw_fields so sync and diff operations always work on raw Liquid templates.

Testing Steps

  • Field containing `{{ project.name }}`: renders correctly in index tables, show headings, breadcrumbs, and page `<title>`
  • Field containing cross-field references (e.g. `{{ issue.fields['Impact'] }}`): reentrancy guard prevents infinite loops, raw value returned as fallback
  • Edit form shows the raw Liquid template (form binds to `:text`, not `fields`)
  • Background and API contexts never set a `LiquidRenderContext`, so `fields` returns raw values — no behaviour change for jobs or API consumers

Check List

  • Added a CHANGELOG entry
  • Commit message has a detailed description of what changed and why.

@caitmich caitmich changed the title Liquid/render by default render Liquid fields by default via thread-local context May 1, 2026
@caitmich caitmich changed the title render Liquid fields by default via thread-local context Render Liquid fields by default May 1, 2026
MattBudz and others added 7 commits May 1, 2026 11:24
Introduce LiquidRenderContext, a thread-local context holder that
controllers set before the view renders and clear afterwards. HasFields
gains a raw_fields method (previous fields logic) and an updated fields
method that renders Liquid values when context is present. set_field and
delete_field now write raw_fields so rendered output is never persisted.
LiquidEnabledResource registers an around_action to set the context for
controllers that already include it (IssuesController, EvidenceController,
etc.).

This eliminates one-off Liquid rendering fixes per view. Any attribute
read via fields or title in a request context is rendered automatically.
Tighten rescue to Liquid::Error (was StandardError), add filters: []
to match the existing liquid_filter.rb options, and update DiffedContent
to call raw_fields throughout so rendered Liquid output is never written
back to record.content or used as source for field sync operations.
Co-authored-by: Copilot <copilot@github.com>
@caitmich
caitmich force-pushed the liquid/render-by-default branch from d5a8235 to 1da08c5 Compare May 1, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants