Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e1ce7ce
Refactored ExtraOptionConfigs to BaseConfiguration pattern with Activ…
philayres Apr 2, 2026
462badc
Added source_attribute pattern, ReferenceEntry NamedConfiguration, sp…
philayres Apr 2, 2026
b5543f3
Added source_attribute pattern for Embed and ESignConfig with configu…
philayres Apr 2, 2026
c59c446
Added NamedConfiguration to DbConfigs for column config entries - fix…
philayres Apr 2, 2026
71d791f
Added NamedConfiguration to FieldOptions for per-field options - fixe…
philayres Apr 2, 2026
24a998f
Added Configurations class for top-level _configurations options - fi…
philayres Apr 2, 2026
7caa105
Added Comments and DataDictionaryConfig classes for top-level options…
philayres Apr 2, 2026
771bff6
Added Constants class for top-level _constants options - fixes #986
philayres Apr 2, 2026
e4609a9
Fixed parsed_options_text_spec migration timeouts by reusing table - …
philayres Apr 2, 2026
0fb1de8
Added key? to BaseNamedConfiguration, fixed Constants to_h in substit…
philayres Apr 2, 2026
e58979d
Added []= to BaseNamedConfiguration for template compatibility - fixe…
philayres Apr 2, 2026
0c5daeb
Added except to BaseConfiguration for ConditionalActions compatibilit…
philayres Apr 2, 2026
df58c8f
Updated redcap spec to check Hash-like behavior instead of exact Hash…
philayres Apr 2, 2026
2844cca
Fixed extra option config compatibility regressions - fixes #986
philayres Apr 2, 2026
73dbb90
Fixed migration trigger and spec regressions - fixes #986
philayres Apr 3, 2026
af82c45
Fixed config notices enrichment, template null-safety, and save-trigg…
philayres Apr 4, 2026
1cf9d6a
Updated option config validations and schema docs - fixes #986
philayres Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,9 @@ def show_caption_before(key, captions, mode: nil, no_sub: nil, ignore_missing: t

mode ||= action_name == 'new' ? :new : :edit
caption = captions[key]
caption = caption[:"#{mode}_caption"] || caption[:caption] || '' if caption.is_a?(Hash)
if caption.is_a?(Hash) || caption.is_a?(OptionConfigs::BaseNamedConfiguration)
caption = caption[:"#{mode}_caption"] || caption[:caption] || ''
end
if @form_object_instance && !no_sub
caption = Formatter::Substitution.substitute(caption, data: @form_object_instance, tag_subs: nil,
ignore_missing:)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pattern 1: Always allow adding the reference
add_reference_if:
always: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pattern 2: Never allow adding the reference
add_reference_if:
never: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Pattern 3: Conditional reference addition
add_reference_if:
all:
this:
status: active
not_any:
activity_log__example_reviews:
extra_log_type: closed
22 changes: 21 additions & 1 deletion app/models/admin/defs/extra_options_creatable_if_defs.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,22 @@
# creatable_if options
creatable_if: 'ref: ** conditions reference **'
#
# Pattern 1: Always creatable
creatable_if:
always: true

# Pattern 2: Never creatable
creatable_if:
never: true

# Pattern 3: Creatable only when conditions are met
creatable_if:
all:
this:
status: active
not_any:
activity_log__example_reviews:
extra_log_type: closed

# Notes:
# - creatable_if must be a Hash using the standard conditional-actions syntax.
# - This commonly interacts with view_options.hide_unless_creatable and with references.creatable_if.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pattern 1: Always creatable
creatable_if:
always: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pattern 2: Never creatable
creatable_if:
never: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Pattern 3: Creatable only when conditions are met
creatable_if:
all:
this:
status: active
not_any:
activity_log__example_reviews:
extra_log_type: closed
32 changes: 28 additions & 4 deletions app/models/admin/defs/extra_options_editable_if_defs.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
# editable_if options
editable_if: |
'ref: ** conditions reference **'
#
# Pattern 1: Explicitly always editable
editable_if:
always: true

NOTE: if not defined, the default is to only allow an item to be editable if it is the most recently created or
item in the list. If you want items to be always editable, use *always: true*
# Pattern 2: Explicitly never editable
editable_if:
never: true

# Pattern 3: Conditional editable rules using the standard conditions reference
editable_if:
all:
this:
status: draft
not_any:
activity_log__example_reviews:
extra_log_type: finalized

# Pattern 4: Merge reusable conditions with local rules
editable_if:
<<: *some_condition_anchor
all:
this:
status: draft

# Notes:
# - editable_if must be a Hash using the standard conditional-actions syntax.
# - If not defined, the default behavior is to allow editing only for the most recent item in the list.
# - Use always: true when you want to override that default and keep all matching items editable.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pattern 1: Explicitly always editable
editable_if:
always: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pattern 2: Explicitly never editable
editable_if:
never: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Pattern 3: Conditional editable rules
editable_if:
all:
this:
status: draft
not_any:
activity_log__example_reviews:
extra_log_type: finalized
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Pattern 4: Merge reusable conditions with local rules
editable_if:
<<: *some_condition_anchor
all:
this:
status: draft
45 changes: 21 additions & 24 deletions app/models/admin/defs/extra_options_embed_defs.yaml
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
# embed options
# embed can be defined in one of three styles:
embed: default_embed_resource
# Simply embeds the dynamic model matching the default_embed_resource_name for this activity
# Must include a field acting as a foreign key onto the parent `<singularized parent table name>_id`
#
# Pattern 1: Use the activity's default embedded resource
embed: default_embed_resource

embed: resource name for the item to be embedded
# Must include a field acting as a foreign key onto the parent `<singularized parent table name>_id`
# Pattern 2: Embed a specific resource by resource name string
embed: dynamic_model__resource_name

embed(form 2):
resource_name: resource name for the item to embed
resource_id: |
(optional) literal integer or a named attribute on the resource to use as an id of the embedded item
# Pattern 3: Explicit Hash form for direct embed configuration
embed:
resource_name: dynamic_model__resource_name
resource_id: 123 | attribute_name
limit: 1

When not specified, the embedded item is looked up based on the existence of a foreign key
field in the target direct embed table pointing back to the parent record. The foreign key
field is named "<singularized parent table name>_id"
limit: (optional) default 1 - although other values may be set, the aim is not really to use direct embedded items for this

# NOTE: direct embed may be configured either using this *embed:* configuration, or directly
# defining fields on the parent and optionally target tables.
# An advantage of using fields on the parent table is that the target embed resource may be specified on a
# record by record basis.
# To define an embedded item using fields alone, use one of the following field configurations:
# <parent table>: embed_resource_name, embed_resource_id - directly reference the embedded item
# <parent table>: embed_resource_name, <target embed table>: <singularized parent table name>_id - foreign key reference.
# Whichever way an embedded item is specified,
# `\{\{substitutions\}\}` may reference attributes in it item using `\{\{embedded_item.<attribute name>\}\}`
# Notes:
# - embed supports only the three patterns above.
# - In Hash form, the supported admin keys are resource_name, resource_id and limit.
# - resource_id may be a literal integer or an attribute name on the resource.
# - limit is normally 1 for direct embeds.
# - The embedded resource must still support linking back to the parent record, usually through
# <singularized parent table name>_id.
# - Direct embed may also be configured using fields alone:
# <parent table>: embed_resource_name, embed_resource_id
# <parent table>: embed_resource_name plus <target table>: <singularized parent table name>_id
# - Whichever way an embedded item is specified, {{embedded_item.<attribute name>}} substitutions are available.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Pattern 1: Default embedded resource
embed: default_embed_resource
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Pattern 2: Specific resource name
embed: dynamic_model__resource_name
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Pattern 3: Explicit hash configuration
embed:
resource_name: dynamic_model__resource_name
resource_id: 123 | attribute_name
limit: 1
94 changes: 53 additions & 41 deletions app/models/admin/defs/extra_options_references_defs.yaml
Original file line number Diff line number Diff line change
@@ -1,58 +1,70 @@
# references options
#
# Pattern 1: Simple reference definition
references:
model_name:
label: button label
result_label: alternative label rather than the to-record label in result caption
from: this | master | any | user_is_creator (item in any master that was created by current user)
without_reference: |
true | outside_master -
true will check only within the current master, if the instance has a master association
outside_master always ignores the master, even if defined, relying only on the filter_by definition
from: this | master | any | user_is_creator
add: many | one_to_master | one_to_this

# Pattern 2: Reference with add_with and filter_by
references:
activity_log__example_step:
label: Add Example Step
from: this
add: one_to_this
add_with:
extra_log_type: type name
extra_log_type: review
item_name:
embedded_item:
field_name: value
filter_by:
field_name: literal value to filter the referenced items by - may include [substitutions](../general/substitutions.md)
field_name: literal value or {{substitution}}
field_name2:
this:
this:
field_name: return_value
# Note that this is evaluated in the context of the current item, so
# the attributes returned are relative to the `from:` item:
# `from: this`, then the attributes are from the current item
# `from: master` or `from: any`, then they are from the master item
order_by: # Order results within model references matching this model and filter
# Note that this can be overridden by view_options.sort_references, sorting the
# results across all references
field_name: asc | desc
field_name2: asc | desc

# Pattern 3: Reference with ordering and activity selector
references:
activity_log__example_step:
from: this
add: many
order_by:
created_at: desc
type_config:
activity_selector:
# use an activity selector to show the creatable extra log types
extra_log_type_key1: Label for button
extra_log_type_key2: Label for button
extra_log_type_key3: Label for button
extra_log_type_key2: Another label

# Pattern 4: Reference display behavior
references:
model_name:
result_label: alternative label shown in the result caption
view_as:
edit: hide|readonly|not_embedded|select_or_add
show: hide|readonly|see_presence|filestore|as_edit
new: outside_this|not_embedded|select_or_add
_notes_:
- "*outside_this* presents a button that triggers a form outside of this container
in the regular panel"
edit: hide | readonly | not_embedded | select_or_add
show: hide | readonly | see_presence | filestore | as_edit
new: outside_this | not_embedded | select_or_add
view_options:
always_open: true # force the reference to always be expanded
showable_if: # exclude to always show
creatable_if: # exclude to always allow creation
prevent_disable: true | condition Hash
# always prevent the reference from being disabled, even if the containing record is editable
also_disable_record: false (default) | true
# if not prevented from disabling the model reference, also disable the referenced record if the
# reference is disabled
allow_disable_if_not_editable: true | condition Hash
# always allow the references to be disabled, even if the containing record is not editable
prevent_reload_on_save: true | false (default)
# prevent the parent from reloading on save - use in combination with {save_action: expand_reference}
action_position: top|bottom
# position this action at the top or bottom of the reference results (default: bottom)
always_open: true
showable_if:
always: true
creatable_if:
all:
this:
status: active

# Pattern 5: Reference disable behavior
references:
model_name:
prevent_disable: true | <condition Hash>
also_disable_record: false | true
allow_disable_if_not_editable: true | <condition Hash>
prevent_reload_on_save: true | false
action_position: top | bottom

# Notes:
# - Each reference entry must be a Hash.
# - Top-level keys are reference model names; they may be declared in hash form or array form.
# - without_reference may be true or outside_master.
# - order_by applies within one reference definition; view_options.sort_references can reorder across all references.
# - Only the documented keys in each entry are supported for admin configuration.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Pattern 1: Simple reference definition
references:
model_name:
label: button label
from: this | master | any | user_is_creator
add: many | one_to_master | one_to_this
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Pattern 2: add_with and filter_by
references:
activity_log__example_step:
label: Add Example Step
from: this
add: one_to_this
add_with:
extra_log_type: review
item_name:
embedded_item:
field_name: value
filter_by:
field_name: literal value or {{substitution}}
field_name2:
this:
field_name: return_value
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Pattern 3: order_by and type_config
references:
activity_log__example_step:
from: this
add: many
order_by:
created_at: desc
type_config:
activity_selector:
extra_log_type_key1: Label for button
extra_log_type_key2: Another label
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Pattern 4: Display behavior
references:
model_name:
result_label: alternative label shown in the result caption
view_as:
edit: hide | readonly | not_embedded | select_or_add
show: hide | readonly | see_presence | filestore | as_edit
new: outside_this | not_embedded | select_or_add
view_options:
always_open: true
showable_if:
always: true
creatable_if:
all:
this:
status: active
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Pattern 5: Disable and reload behavior
references:
model_name:
prevent_disable: true | <condition Hash>
also_disable_record: false | true
allow_disable_if_not_editable: true | <condition Hash>
prevent_reload_on_save: true | false
action_position: top | bottom
Loading