compony 0.11.8 → 0.11.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +36 -1
- data/CHANGELOG.md +31 -0
- data/CLAUDE.md +85 -0
- data/Gemfile.lock +1 -1
- data/README.md +13 -3
- data/VERSION +1 -1
- data/compony.gemspec +3 -3
- data/doc/ComponentGenerator.html +1 -1
- data/doc/Components.html +1 -1
- data/doc/ComponentsGenerator.html +1 -1
- data/doc/Compony/Component.html +54 -54
- data/doc/Compony/ComponentMixins/Default/Labelling.html +1 -1
- data/doc/Compony/ComponentMixins/Default/Standalone/ResourcefulVerbDsl.html +1 -1
- data/doc/Compony/ComponentMixins/Default/Standalone/StandaloneDsl.html +109 -70
- data/doc/Compony/ComponentMixins/Default/Standalone/VerbDsl.html +64 -28
- data/doc/Compony/ComponentMixins/Default/Standalone.html +1 -1
- data/doc/Compony/ComponentMixins/Default.html +1 -1
- data/doc/Compony/ComponentMixins/Resourceful.html +213 -74
- data/doc/Compony/ComponentMixins.html +1 -1
- data/doc/Compony/Components/Buttons/CssButton.html +1 -1
- data/doc/Compony/Components/Buttons/Link.html +1 -1
- data/doc/Compony/Components/Buttons.html +1 -1
- data/doc/Compony/Components/Destroy.html +83 -29
- data/doc/Compony/Components/Edit.html +110 -38
- data/doc/Compony/Components/Form.html +551 -208
- data/doc/Compony/Components/Index.html +1 -1
- data/doc/Compony/Components/List.html +3 -3
- data/doc/Compony/Components/New.html +110 -38
- data/doc/Compony/Components/Show.html +1 -1
- data/doc/Compony/Components/WithForm.html +194 -47
- data/doc/Compony/Components.html +1 -1
- data/doc/Compony/ControllerMixin.html +1 -1
- data/doc/Compony/Engine.html +1 -1
- data/doc/Compony/Intent.html +2 -2
- data/doc/Compony/ManageIntentsDsl.html +1 -1
- data/doc/Compony/MethodAccessibleHash.html +1 -1
- data/doc/Compony/ModelFields/Anchormodel.html +1 -1
- data/doc/Compony/ModelFields/Association.html +1 -1
- data/doc/Compony/ModelFields/Attachment.html +1 -1
- data/doc/Compony/ModelFields/Base.html +1 -1
- data/doc/Compony/ModelFields/Boolean.html +1 -1
- data/doc/Compony/ModelFields/Color.html +1 -1
- data/doc/Compony/ModelFields/Currency.html +1 -1
- data/doc/Compony/ModelFields/Date.html +1 -1
- data/doc/Compony/ModelFields/Datetime.html +1 -1
- data/doc/Compony/ModelFields/Decimal.html +1 -1
- data/doc/Compony/ModelFields/Email.html +1 -1
- data/doc/Compony/ModelFields/Float.html +1 -1
- data/doc/Compony/ModelFields/Integer.html +1 -1
- data/doc/Compony/ModelFields/Percentage.html +1 -1
- data/doc/Compony/ModelFields/Phone.html +1 -1
- data/doc/Compony/ModelFields/RichText.html +1 -1
- data/doc/Compony/ModelFields/String.html +1 -1
- data/doc/Compony/ModelFields/Text.html +1 -1
- data/doc/Compony/ModelFields/Time.html +1 -1
- data/doc/Compony/ModelFields/Url.html +1 -1
- data/doc/Compony/ModelFields.html +1 -1
- data/doc/Compony/ModelMixin.html +1 -1
- data/doc/Compony/NaturalOrdering.html +1 -1
- data/doc/Compony/RequestContext.html +1 -1
- data/doc/Compony/Version.html +1 -1
- data/doc/Compony/ViewHelpers.html +1 -1
- data/doc/Compony/VirtualModel.html +1 -1
- data/doc/Compony.html +1 -1
- data/doc/ComponyController.html +1 -1
- data/doc/_index.html +97 -1
- data/doc/file.CHANGELOG.html +758 -0
- data/doc/file.README.html +25 -4
- data/doc/file.basic_component.html +314 -0
- data/doc/file.cookbook.html +189 -0
- data/doc/file.destroy.html +105 -0
- data/doc/file.dsl_reference.html +672 -0
- data/doc/file.edit.html +109 -0
- data/doc/file.example.html +291 -0
- data/doc/file.example_advanced.html +257 -0
- data/doc/file.feasibility.html +115 -0
- data/doc/file.form.html +195 -0
- data/doc/file.generators.html +89 -0
- data/doc/file.glossary.html +217 -0
- data/doc/file.gotchas.html +222 -0
- data/doc/file.index.html +135 -0
- data/doc/file.inheritance.html +136 -0
- data/doc/file.installation.html +115 -0
- data/doc/file.integrations.html +218 -0
- data/doc/file.intents.html +265 -0
- data/doc/file.internal_datastructures.html +129 -0
- data/doc/file.list.html +253 -0
- data/doc/file.maintaining.html +127 -0
- data/doc/file.model_fields.html +137 -0
- data/doc/file.nesting.html +237 -0
- data/doc/file.new.html +109 -0
- data/doc/file.ownership.html +98 -0
- data/doc/file.patterns.html +669 -0
- data/doc/file.pre_built_components.html +99 -0
- data/doc/file.resourceful.html +181 -0
- data/doc/file.show.html +158 -0
- data/doc/file.standalone.html +233 -0
- data/doc/file.virtual_models.html +117 -0
- data/doc/file.with_form.html +157 -0
- data/doc/file_list.html +160 -0
- data/doc/guide/cookbook.md +41 -0
- data/doc/guide/dsl_reference.md +155 -0
- data/doc/guide/example_advanced.md +209 -0
- data/doc/guide/generators.md +1 -1
- data/doc/guide/glossary.md +42 -0
- data/doc/guide/gotchas.md +125 -0
- data/doc/guide/maintaining.md +64 -0
- data/doc/guide/patterns.md +681 -0
- data/doc/guide/pre_built_components/edit.md +1 -1
- data/doc/guide/pre_built_components/index.md +64 -1
- data/doc/guide/pre_built_components/list.md +111 -7
- data/doc/guide/pre_built_components/show.md +57 -2
- data/doc/guide/pre_built_components/with_form.md +56 -9
- data/doc/guide/pre_built_components.md +7 -2
- data/doc/guide/standalone.md +16 -1
- data/doc/index.html +25 -4
- data/doc/integrations.md +61 -0
- data/doc/llms.txt +62 -0
- data/doc/top-level-namespace.html +1 -1
- data/lib/compony/component.rb +8 -3
- data/lib/compony/component_mixins/default/standalone/standalone_dsl.rb +32 -15
- data/lib/compony/component_mixins/default/standalone/verb_dsl.rb +11 -3
- data/lib/compony/component_mixins/resourceful.rb +30 -16
- data/lib/compony/components/destroy.rb +21 -1
- data/lib/compony/components/edit.rb +25 -1
- data/lib/compony/components/form.rb +63 -21
- data/lib/compony/components/list.rb +1 -1
- data/lib/compony/components/new.rb +25 -1
- data/lib/compony/components/with_form.rb +20 -5
- data/lib/compony/intent.rb +1 -1
- metadata +43 -1
data/doc/file_list.html
CHANGED
|
@@ -49,6 +49,166 @@
|
|
|
49
49
|
</li>
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
<li id="object_CHANGELOG" class="even">
|
|
53
|
+
<div class="item"><span class="object_link"><a href="file.CHANGELOG.html" title="CHANGELOG">CHANGELOG</a></span></div>
|
|
54
|
+
</li>
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
<li id="object_installation" class="odd">
|
|
58
|
+
<div class="item"><span class="object_link"><a href="file.installation.html" title="installation">installation</a></span></div>
|
|
59
|
+
</li>
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
<li id="object_example" class="even">
|
|
63
|
+
<div class="item"><span class="object_link"><a href="file.example.html" title="example">example</a></span></div>
|
|
64
|
+
</li>
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
<li id="object_example_advanced" class="odd">
|
|
68
|
+
<div class="item"><span class="object_link"><a href="file.example_advanced.html" title="example_advanced">example_advanced</a></span></div>
|
|
69
|
+
</li>
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
<li id="object_basic_component" class="even">
|
|
73
|
+
<div class="item"><span class="object_link"><a href="file.basic_component.html" title="basic_component">basic_component</a></span></div>
|
|
74
|
+
</li>
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
<li id="object_standalone" class="odd">
|
|
78
|
+
<div class="item"><span class="object_link"><a href="file.standalone.html" title="standalone">standalone</a></span></div>
|
|
79
|
+
</li>
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
<li id="object_inheritance" class="even">
|
|
83
|
+
<div class="item"><span class="object_link"><a href="file.inheritance.html" title="inheritance">inheritance</a></span></div>
|
|
84
|
+
</li>
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
<li id="object_nesting" class="odd">
|
|
88
|
+
<div class="item"><span class="object_link"><a href="file.nesting.html" title="nesting">nesting</a></span></div>
|
|
89
|
+
</li>
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
<li id="object_resourceful" class="even">
|
|
93
|
+
<div class="item"><span class="object_link"><a href="file.resourceful.html" title="resourceful">resourceful</a></span></div>
|
|
94
|
+
</li>
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
<li id="object_intents" class="odd">
|
|
98
|
+
<div class="item"><span class="object_link"><a href="file.intents.html" title="intents">intents</a></span></div>
|
|
99
|
+
</li>
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
<li id="object_feasibility" class="even">
|
|
103
|
+
<div class="item"><span class="object_link"><a href="file.feasibility.html" title="feasibility">feasibility</a></span></div>
|
|
104
|
+
</li>
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
<li id="object_ownership" class="odd">
|
|
108
|
+
<div class="item"><span class="object_link"><a href="file.ownership.html" title="ownership">ownership</a></span></div>
|
|
109
|
+
</li>
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
<li id="object_model_fields" class="even">
|
|
113
|
+
<div class="item"><span class="object_link"><a href="file.model_fields.html" title="model_fields">model_fields</a></span></div>
|
|
114
|
+
</li>
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
<li id="object_generators" class="odd">
|
|
118
|
+
<div class="item"><span class="object_link"><a href="file.generators.html" title="generators">generators</a></span></div>
|
|
119
|
+
</li>
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
<li id="object_internal_datastructures" class="even">
|
|
123
|
+
<div class="item"><span class="object_link"><a href="file.internal_datastructures.html" title="internal_datastructures">internal_datastructures</a></span></div>
|
|
124
|
+
</li>
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
<li id="object_virtual_models" class="odd">
|
|
128
|
+
<div class="item"><span class="object_link"><a href="file.virtual_models.html" title="virtual_models">virtual_models</a></span></div>
|
|
129
|
+
</li>
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
<li id="object_pre_built_components" class="even">
|
|
133
|
+
<div class="item"><span class="object_link"><a href="file.pre_built_components.html" title="pre_built_components">pre_built_components</a></span></div>
|
|
134
|
+
</li>
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
<li id="object_show" class="odd">
|
|
138
|
+
<div class="item"><span class="object_link"><a href="file.show.html" title="show">show</a></span></div>
|
|
139
|
+
</li>
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
<li id="object_index" class="even">
|
|
143
|
+
<div class="item"><span class="object_link"><a href="file.index.html" title="index">index</a></span></div>
|
|
144
|
+
</li>
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
<li id="object_list" class="odd">
|
|
148
|
+
<div class="item"><span class="object_link"><a href="file.list.html" title="list">list</a></span></div>
|
|
149
|
+
</li>
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
<li id="object_destroy" class="even">
|
|
153
|
+
<div class="item"><span class="object_link"><a href="file.destroy.html" title="destroy">destroy</a></span></div>
|
|
154
|
+
</li>
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
<li id="object_with_form" class="odd">
|
|
158
|
+
<div class="item"><span class="object_link"><a href="file.with_form.html" title="with_form">with_form</a></span></div>
|
|
159
|
+
</li>
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
<li id="object_form" class="even">
|
|
163
|
+
<div class="item"><span class="object_link"><a href="file.form.html" title="form">form</a></span></div>
|
|
164
|
+
</li>
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
<li id="object_new" class="odd">
|
|
168
|
+
<div class="item"><span class="object_link"><a href="file.new.html" title="new">new</a></span></div>
|
|
169
|
+
</li>
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
<li id="object_edit" class="even">
|
|
173
|
+
<div class="item"><span class="object_link"><a href="file.edit.html" title="edit">edit</a></span></div>
|
|
174
|
+
</li>
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
<li id="object_dsl_reference" class="odd">
|
|
178
|
+
<div class="item"><span class="object_link"><a href="file.dsl_reference.html" title="dsl_reference">dsl_reference</a></span></div>
|
|
179
|
+
</li>
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
<li id="object_glossary" class="even">
|
|
183
|
+
<div class="item"><span class="object_link"><a href="file.glossary.html" title="glossary">glossary</a></span></div>
|
|
184
|
+
</li>
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
<li id="object_gotchas" class="odd">
|
|
188
|
+
<div class="item"><span class="object_link"><a href="file.gotchas.html" title="gotchas">gotchas</a></span></div>
|
|
189
|
+
</li>
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
<li id="object_patterns" class="even">
|
|
193
|
+
<div class="item"><span class="object_link"><a href="file.patterns.html" title="patterns">patterns</a></span></div>
|
|
194
|
+
</li>
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
<li id="object_cookbook" class="odd">
|
|
198
|
+
<div class="item"><span class="object_link"><a href="file.cookbook.html" title="cookbook">cookbook</a></span></div>
|
|
199
|
+
</li>
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
<li id="object_integrations" class="even">
|
|
203
|
+
<div class="item"><span class="object_link"><a href="file.integrations.html" title="integrations">integrations</a></span></div>
|
|
204
|
+
</li>
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
<li id="object_maintaining" class="odd">
|
|
208
|
+
<div class="item"><span class="object_link"><a href="file.maintaining.html" title="maintaining">maintaining</a></span></div>
|
|
209
|
+
</li>
|
|
210
|
+
|
|
211
|
+
|
|
52
212
|
|
|
53
213
|
</ul>
|
|
54
214
|
</div>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
|
+
|
|
3
|
+
# Cookbook
|
|
4
|
+
|
|
5
|
+
Task-oriented entry point: "I want to do X — where is it?". Every recipe lives in
|
|
6
|
+
[Real-world patterns](./patterns.md) or the guide; this page is a pure index by goal so
|
|
7
|
+
you don't need to know a pattern's name. Nothing is duplicated here — only links.
|
|
8
|
+
|
|
9
|
+
## Recipes by task
|
|
10
|
+
|
|
11
|
+
| I want to… | See |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| Share layout/chrome across all components | [patterns §1 base layer](./patterns.md#1-the-app-base-component-layer) |
|
|
14
|
+
| Add a CRUD screen with almost no code | [patterns §2 thin leaves](./patterns.md#2-thin-leaf-components), [example.md](./example.md) |
|
|
15
|
+
| List records with columns/filters/sorts | [List](./pre_built_components/list.md), [patterns §3–4](./patterns.md#3-index--load_data-scope--nested-list) |
|
|
16
|
+
| Scope/order what an Index shows | [patterns §3](./patterns.md#3-index--load_data-scope--nested-list) (`load_data`) |
|
|
17
|
+
| Embed a child list inside a Show | [patterns §4](./patterns.md#4-list-customization), [nesting.md](./nesting.md) |
|
|
18
|
+
| Build a custom form + strong params | [patterns §5](./patterns.md#5-custom-form--schemacop-kept-in-sync), [Form](./pre_built_components/form.md) |
|
|
19
|
+
| Nested attributes in a form | [patterns §5](./patterns.md#5-custom-form--schemacop-kept-in-sync), [example_advanced.md](./example_advanced.md) |
|
|
20
|
+
| Autocomplete / ajax select field | [patterns §6](./patterns.md#6-autocomplete-form-app-level-subclass) |
|
|
21
|
+
| Split a detail page into tabs | [patterns §7](./patterns.md#7-tabbed-show-via-a-mixin) |
|
|
22
|
+
| Prefill/derive fields before save | [patterns §8](./patterns.md#8-lifecycle-hooks-for-derived-data) (`after_assign_attributes`) |
|
|
23
|
+
| Guard/redirect before rendering | [patterns §8](./patterns.md#8-lifecycle-hooks-for-derived-data), [basic_component.md](./basic_component.md#redirecting-away--intercepting-rendering) |
|
|
24
|
+
| Customize the action toolbar | [patterns §9](./patterns.md#9-exposed-intents-as-the-action-toolbar), [intents.md](./intents.md#exposed-intents) |
|
|
25
|
+
| Disable a button with a reason | [feasibility.md](./feasibility.md), [patterns §9](./patterns.md#9-exposed-intents-as-the-action-toolbar) |
|
|
26
|
+
| Export CSV / PDF | [patterns §10](./patterns.md#10-csv--pdf-via-respond-format) |
|
|
27
|
+
| Launch a background job from a button | [patterns §11](./patterns.md#11-non-crud-job-dispatch-toggles-clone) |
|
|
28
|
+
| Toggle a boolean (activate/lock/…) | [patterns §11](./patterns.md#11-non-crud-job-dispatch-toggles-clone) |
|
|
29
|
+
| Clone/duplicate a record | [patterns §11](./patterns.md#11-non-crud-job-dispatch-toggles-clone) |
|
|
30
|
+
| Non-persistent / upload-only form | [patterns §12](./patterns.md#12-virtual-model-for-non-persistent--upload-forms), [virtual_models.md](./virtual_models.md) |
|
|
31
|
+
| Public page / inbound webhook | [patterns §13](./patterns.md#13-public-endpoints--webhooks), [gotchas §14](./gotchas.md#14-public-endpoint-still-401redirecting) |
|
|
32
|
+
| Custom button look | [patterns §14](./patterns.md#14-custom-button-style), [intents.md](./intents.md#adding-your-own-styles) |
|
|
33
|
+
| Inline-edit a card without a full page | [patterns §15 turbo-frame inline edit](./patterns.md#15-inline-edit-card-with-a-turbo-frame) |
|
|
34
|
+
| Multi-step wizard across components | [patterns §16 multi-step wizard](./patterns.md#16-multi-step-wizard-across-components) |
|
|
35
|
+
| Reorder/inline-patch without a form | [patterns §17 inline PATCH](./patterns.md#17-inline-patch-without-a-form-reorder--quick-toggle) |
|
|
36
|
+
| Magic-login / invite / reset / confirm link (no session) | [patterns §18 signed-token capability links](./patterns.md#18-signed-token-capability-links-auth-less-onboarding--magic-links) |
|
|
37
|
+
|
|
38
|
+
If a goal isn't listed, check the [DSL reference](./dsl_reference.md) and
|
|
39
|
+
[glossary](./glossary.md).
|
|
40
|
+
|
|
41
|
+
[Guide index](/README.md#guide--documentation)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
|
+
|
|
3
|
+
# DSL reference
|
|
4
|
+
|
|
5
|
+
Flat lookup of every Compony DSL method, its calling context, signature, and a one-line
|
|
6
|
+
description. This page is generated from `# DSL method` markers in the source — when in
|
|
7
|
+
doubt the Ruby source under `lib/compony/` is authoritative. "Context" says where the call
|
|
8
|
+
is legal.
|
|
9
|
+
|
|
10
|
+
Contexts:
|
|
11
|
+
|
|
12
|
+
- **class** — directly in the component class body.
|
|
13
|
+
- **setup** — inside `setup do ... end`.
|
|
14
|
+
- **standalone** — inside `standalone do ... end`.
|
|
15
|
+
- **verb** — inside `verb :x do ... end`.
|
|
16
|
+
- **form_fields** — inside a Form component's `form_fields do ... end`.
|
|
17
|
+
- **content** — inside a `content do ... end` block (a RequestContext).
|
|
18
|
+
- **model** — in an `ActiveRecord`/model class that `include`s the model mixin.
|
|
19
|
+
|
|
20
|
+
## Component definition
|
|
21
|
+
|
|
22
|
+
| Method | Context | Signature | Description |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| `setup` | class | `setup { block }` | Main config block. Runs at end of init; parent runs before child. |
|
|
25
|
+
| `label` | setup | `label(:short/:long/:all) { \|model\| ... }` | Component title + link/button text. Resourceful comps take 1 block arg. |
|
|
26
|
+
| `color` | setup | `color { '#AA0000' }` | Component color (not used by Compony itself). |
|
|
27
|
+
| `icon` | setup | `icon { %i[fa-solid circle] }` | Component icon (not used by Compony itself). |
|
|
28
|
+
| `content` | setup | `content(name = :main, before: nil, hidden: false) { block }` | Define/replace a named view block (Dyny). `hidden: true` = don't auto-render. Non-obvious use: hidden `:main` + `:wrapper` chrome → [patterns §1](/doc/guide/patterns.md#1-the-app-base-component-layer). |
|
|
29
|
+
| `content` | content | `content(:name)` | Render another content block of the *same* component (nesting). [patterns §1](/doc/guide/patterns.md#1-the-app-base-component-layer). |
|
|
30
|
+
| `remove_content` | setup | `remove_content(:name)` | Remove an inherited content block (returns false if absent). |
|
|
31
|
+
| `remove_content!` | setup | `remove_content!(:name)` | Same, raises if the block was not found. |
|
|
32
|
+
| `before_render` | setup | `before_render(name = :main, before: nil) { block }` | Pre-content hook. If it sets a response body (e.g. redirect), content is skipped. Non-obvious uses: verb-independent guard → [patterns §8](/doc/guide/patterns.md#8-lifecycle-hooks-for-derived-data); wizard step nav → [patterns §16](/doc/guide/patterns.md#16-multi-step-wizard-across-components). |
|
|
33
|
+
| `exposed_intents` | setup | `exposed_intents { add ...; remove ... }` | Declare intents the layout/parent renders. See `add`/`remove`, and the toolbar pattern → [patterns §9](/doc/guide/patterns.md#9-exposed-intents-as-the-action-toolbar). |
|
|
34
|
+
| `path` | setup | `path { \|model, *args, standalone_name:, **kw\| ... }` | Override path generation for this component (advanced). Runs outside the request context — build URLs via `Rails.application.routes.url_helpers`. Non-obvious use: mint a signed token into the URL → [patterns §18](/doc/guide/patterns.md#18-signed-token-capability-links-auth-less-onboarding--magic-links); see also [standalone.md](/doc/guide/standalone.md#customizing-path-generation). |
|
|
35
|
+
|
|
36
|
+
`exposed_intents` DSL (inside its block):
|
|
37
|
+
|
|
38
|
+
| Method | Signature | Description |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `add` | `add(comp, model_or_family, before: nil, **intent_opts)` | Add/replace an exposed intent (keyed by intent name). |
|
|
41
|
+
| `remove` | `remove(:intent_name)` | Remove a previously added exposed intent. |
|
|
42
|
+
|
|
43
|
+
## Standalone (routing)
|
|
44
|
+
|
|
45
|
+
| Method | Context | Signature | Description |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `standalone` | setup | `standalone(name = nil, path:, constraints: nil, scope: nil, scope_args: {}) { ... }` | Generate a Rails route. Non-obvious use: a *named* extra `standalone` for an ajax companion endpoint of the same screen → [patterns §17](/doc/guide/patterns.md#17-inline-patch-without-a-form-reorder--quick-toggle). |
|
|
48
|
+
| `verb` | standalone | `verb(:get/:post/:patch/:put/:delete/...) { ... }` | Config one HTTP verb. Up to once per verb per standalone. |
|
|
49
|
+
| `skip_authentication!` | standalone | `skip_authentication!` | Disable app authentication for this standalone (still need `authorize`). Non-obvious use: token-gated auth-less links → [patterns §18](/doc/guide/patterns.md#18-signed-token-capability-links-auth-less-onboarding--magic-links). |
|
|
50
|
+
| `skip_forgery_protection!` | standalone | `skip_forgery_protection!` | Disable CSRF for this standalone's action. Non-obvious use: inbound webhooks → [patterns §13](/doc/guide/patterns.md#13-public-endpoints--webhooks). |
|
|
51
|
+
| `layout` | standalone | `layout('layouts/backend')` | Rails layout for this standalone. Defaults to `layouts/application`. Centralize in the base layer → [patterns §1](/doc/guide/patterns.md#1-the-app-base-component-layer). |
|
|
52
|
+
| `authorize` | verb | `authorize { can?(:read, @data) }` | **Mandatory.** Truthy = access; falsy → `CanCan::AccessDenied`. Non-obvious use: validate a signed token → [patterns §18](/doc/guide/patterns.md#18-signed-token-capability-links-auth-less-onboarding--magic-links). |
|
|
53
|
+
| `respond` | verb | `respond(format = nil) { ... }` | Override the controller response (e.g. send_data, render json, redirect). Overriding `nil` format **skips `authorize`** — re-check yourself. Non-obvious uses: CSV/PDF export → [patterns §10](/doc/guide/patterns.md#10-csv--pdf-via-respond-format); ajax-only PATCH → [patterns §17](/doc/guide/patterns.md#17-inline-patch-without-a-form-reorder--quick-toggle). |
|
|
54
|
+
|
|
55
|
+
## Resourceful lifecycle hooks
|
|
56
|
+
|
|
57
|
+
Available in **setup** (global, all verbs) on resourceful components; the same names also
|
|
58
|
+
work inside a **verb** block to override for one path+verb.
|
|
59
|
+
|
|
60
|
+
| Method | Signature | Description |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `data_class` | `data_class(NewClass = nil)` | Set/get the model class. Defaults to family name singularized+constantized. Non-obvious use: point at a `VirtualModel` → [patterns §12](/doc/guide/patterns.md#12-virtual-model-for-non-persistent--upload-forms). |
|
|
63
|
+
| `load_data` | `load_data { @data = ... }` | Override record loading. Default: `data_class.find(params[:id])`. Runs before `authorize`. Non-obvious uses: Index scope → [patterns §3](/doc/guide/patterns.md#3-index--load_data-scope--nested-list); load+dup for clone → [patterns §11](/doc/guide/patterns.md#11-non-crud-job-dispatch-toggles-clone). |
|
|
64
|
+
| `after_load_data` | `after_load_data { ... }` | Runs after `load_data`, before `authorize`. Refine an AR relation here. |
|
|
65
|
+
| `assign_attributes` | `assign_attributes { ... }` | Assign validated params onto `@data`. Pre-built forms supply a default. |
|
|
66
|
+
| `after_assign_attributes` | `after_assign_attributes { ... }` | After `assign_attributes`, before `store_data`. Prefill/derive fields here → [patterns §8](/doc/guide/patterns.md#8-lifecycle-hooks-for-derived-data); seed from a token → [patterns §18](/doc/guide/patterns.md#18-signed-token-capability-links-auth-less-onboarding--magic-links). |
|
|
67
|
+
| `store_data` | `store_data { @data.save }` | Persist `@data`. Override for virtual models / uploads → [patterns §12](/doc/guide/patterns.md#12-virtual-model-for-non-persistent--upload-forms), or to enqueue work / multi-record txn → [patterns §11](/doc/guide/patterns.md#11-non-crud-job-dispatch-toggles-clone). |
|
|
68
|
+
|
|
69
|
+
## Form component DSL
|
|
70
|
+
|
|
71
|
+
`class Components::X::Form < Compony::Components::Form`
|
|
72
|
+
|
|
73
|
+
| Method | Context | Signature | Description |
|
|
74
|
+
| --- | --- | --- | --- |
|
|
75
|
+
| `form_fields` | setup | `form_fields { ... }` | **Mandatory.** Block holding the form inputs (Dyny + `field`/`f`). |
|
|
76
|
+
| `field` | form_fields | `field(:name, multilang: false, **simple_form_opts)` | Render a simple_form input inferred from the model field. `as:`/`hidden:` supported. |
|
|
77
|
+
| `pw_field` | form_fields | `pw_field(:password, **opts)` | Password input; checks `:set_password` ability. |
|
|
78
|
+
| `f` | form_fields | `f` | The underlying `simple_form` builder (for `f.rich_text_area` etc.). |
|
|
79
|
+
| `collect` | form_fields | `collect(...)` | Wrap a collection in Rails-compatible format (Anchormodel helper). |
|
|
80
|
+
| `schema_field` | setup | `schema_field(:name, multilang: false)` | Whitelist one field in the Schemacop param schema. For associations use the **association name**, not `_id`. |
|
|
81
|
+
| `schema_fields` | setup | `schema_fields(:a, :b, ...)` | Mass `schema_field`. |
|
|
82
|
+
| `schema_pw_field` | setup | `schema_pw_field(:password)` | Whitelist a password param (checks `:set_password`). |
|
|
83
|
+
| `schema_line` | setup | `schema_line { str? :foo }` | Add a raw Schemacop3 line (nested attrs, custom shapes) → [patterns §5](/doc/guide/patterns.md#5-custom-form--schemacop-kept-in-sync). |
|
|
84
|
+
| `schema` | setup | `schema(:wrapper_key) { ... }` | Replace the whole schema + wrapper key (fully manual). |
|
|
85
|
+
| `form_params` | setup | `form_params(**opts)` | Extra kwargs passed to `simple_form_for`. |
|
|
86
|
+
| `disable!` | setup | `disable!` | Render all inputs disabled. |
|
|
87
|
+
| `skip_autofocus` | setup | `skip_autofocus` | Don't autofocus the first field. |
|
|
88
|
+
|
|
89
|
+
WithForm / New / Edit wiring (in the New/Edit component's `setup`):
|
|
90
|
+
|
|
91
|
+
| Method | Signature | Description |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| `submit_verb` | `submit_verb(:patch)` | HTTP verb the form submits with (`:post` for New, `:patch` for Edit). |
|
|
94
|
+
| `form_comp_class` | `form_comp_class(Components::X::MyForm)` | Use a custom Form component instead of `Components::<Family>::Form`. Non-obvious uses: non-default form → [patterns §5](/doc/guide/patterns.md#5-custom-form--schemacop-kept-in-sync); token-gated signup → [patterns §18](/doc/guide/patterns.md#18-signed-token-capability-links-auth-less-onboarding--magic-links). |
|
|
95
|
+
| `form_cancancan_action` | `form_cancancan_action(:edit)` | CanCanCan action used for per-field `permitted_attributes`. Pass `nil` to disable per-field auth (e.g. token-gated forms → [patterns §18](/doc/guide/patterns.md#18-signed-token-capability-links-auth-less-onboarding--magic-links)). |
|
|
96
|
+
| `submit_path` | `submit_path { Compony.path(...) }` | Override where the form POSTs/PATCHes to. Non-obvious use: carry a token through submit → [patterns §18](/doc/guide/patterns.md#18-signed-token-capability-links-auth-less-onboarding--magic-links). |
|
|
97
|
+
| `on_created` / `on_updated` | `on_created { ... }` | Post-save, pre-respond hook (success). |
|
|
98
|
+
| `on_created_respond` / `on_updated_respond` | `... { ... }` | Override the success response (default: flash + redirect). |
|
|
99
|
+
| `on_created_redirect_path` / `on_updated_redirect_path` | `... { path }` | Override the success redirect target. Non-obvious use: chain wizard steps → [patterns §16](/doc/guide/patterns.md#16-multi-step-wizard-across-components). |
|
|
100
|
+
| `on_create_failed_respond` / `on_update_failed` | `... { ... }` | Override the validation-failure response. |
|
|
101
|
+
| `on_destroyed` / `on_destroyed_respond` / `on_destroyed_redirect_path` | `... { ... }` | Destroy component equivalents. |
|
|
102
|
+
|
|
103
|
+
## List / Index component DSL
|
|
104
|
+
|
|
105
|
+
`Compony::Components::List` (nest in Index or owner Show); `Compony::Components::Index`.
|
|
106
|
+
|
|
107
|
+
| Method | Context | Signature | Description |
|
|
108
|
+
| --- | --- | --- | --- |
|
|
109
|
+
| `column` | setup | `column(:name, label: nil, class: nil, link_opts: {}) { \|record\| ... }` | Add/define a column; block renders the cell. |
|
|
110
|
+
| `columns` | setup | `columns(:a, :b, as_title: false)` | Add several field-based columns. `as_title: true` = card title on mobile. |
|
|
111
|
+
| `filter` | setup | `filter(:name, label: nil) { \|f\| ... }` | Add/define a filter; block customizes the input. |
|
|
112
|
+
| `filters` | setup | `filters(:a, :b)` | Add several filters. |
|
|
113
|
+
| `sort` | setup | `sort(:name, label: nil)` | Add a sort option. |
|
|
114
|
+
| `sorts` | setup | `sorts(:a, :b)` | Add several sort options. |
|
|
115
|
+
| `default_sorting` | setup | `default_sorting('id desc')` | Default Ransack sort string. |
|
|
116
|
+
| `row_intents` | setup | `row_intents(**opts) { add/remove }` | Per-row action buttons (intent DSL) → [patterns §4](/doc/guide/patterns.md#4-list-customization). |
|
|
117
|
+
| `pagination` | setup | `pagination(false)` | Enable/disable pagination (caution: loads all rows). |
|
|
118
|
+
| `results_per_page` | setup | `results_per_page(20)` | Rows per page when paginating. |
|
|
119
|
+
| `filtering` | setup | `filtering(false)` | Enable/disable filtering entirely. |
|
|
120
|
+
| `sorting` | setup | `sorting(false)` | Enable/disable both sorting links and in-filter sorting. |
|
|
121
|
+
| `sorting_in_filter` / `sorting_links` | setup | `sorting_links(false)` | Toggle one sorting UI independently. |
|
|
122
|
+
| `filter_label_class` / `filter_input_class` / `filter_select_class` | setup | `filter_input_class('...')` | CSS classes for filter form elements. |
|
|
123
|
+
| `skip_column` | setup | `skip_column(:name)` | Remove an inherited column (Show/List). |
|
|
124
|
+
|
|
125
|
+
Many of these also accept `skip_*` kwargs on the constructor when the List is nested via
|
|
126
|
+
`render_sub_comp` (e.g. `render_sub_comp(:list, coll, skip_pagination: true)`).
|
|
127
|
+
|
|
128
|
+
## Model mixin (in your `ApplicationRecord` models)
|
|
129
|
+
|
|
130
|
+
| Method | Context | Signature | Description |
|
|
131
|
+
| --- | --- | --- | --- |
|
|
132
|
+
| `field` | model (class) | `field(:name, :type, multilang: false, **attrs)` | Declare a UI-relevant attribute + its type/formatting. |
|
|
133
|
+
| `prevent` | model (class) | `prevent(:destroy, 'msg') { condition }` | Feasibility: block returns truthy → action prevented (buttons disabled). |
|
|
134
|
+
| `owned_by` | model (class) | `owned_by(:invoice)` | Mark this model as owned by another; adjusts redirects/top actions. |
|
|
135
|
+
| `skip_autodetect_feasibilities` | model (class) | `skip_autodetect_feasibilities` | Don't auto-derive `:destroy` preventions from `dependent:` associations. |
|
|
136
|
+
| `feasible?` | model (instance) | `record.feasible?(:destroy)` | True if no prevention blocks the action. |
|
|
137
|
+
| `feasibility_messages` | model (instance) | `record.feasibility_messages(:destroy)` | Array of reasons (like `errors`). |
|
|
138
|
+
| `full_feasibility_messages` | model (instance) | `record.full_feasibility_messages(:destroy)` | Joined human string (like `full_messages`). |
|
|
139
|
+
|
|
140
|
+
`label` is not a DSL method but **every Compony model must implement `def label`** (or have
|
|
141
|
+
a `label` column) — used for titles and link text.
|
|
142
|
+
|
|
143
|
+
## Intent / navigation helpers
|
|
144
|
+
|
|
145
|
+
Used from `content` blocks or controllers — see [intents.md](/doc/guide/intents.md).
|
|
146
|
+
|
|
147
|
+
| Method | Context | Signature | Description |
|
|
148
|
+
| --- | --- | --- | --- |
|
|
149
|
+
| `Compony.intent` | anywhere | `Compony.intent(:show, model, **opts)` | Build an `Intent`. |
|
|
150
|
+
| `Compony.path` | anywhere | `Compony.path(:index, :users, **opts)` | Rails path string (redirects). |
|
|
151
|
+
| `render_intent` | content | `render_intent(:edit, model, style:, label:, button:)` | Render a link/button to a component. Wrap in `concat`. |
|
|
152
|
+
| `render_sub_comp` | content | `render_sub_comp(:list, @data.quotes, **opts)` | Instantiate + nest another component. Wrap in `concat`. |
|
|
153
|
+
| `Compony.comp_class_for` | anywhere | `Compony.comp_class_for(:show, family)` | Comp class or `nil`. |
|
|
154
|
+
|
|
155
|
+
[Guide index](/README.md#guide--documentation)
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
|
+
|
|
3
|
+
# Advanced example
|
|
4
|
+
|
|
5
|
+
[example.md](/doc/guide/example.md) shows the basics. This one combines patterns you hit in
|
|
6
|
+
real apps: a custom form, feasibility, exposed intents, a CSV export endpoint, a
|
|
7
|
+
`before_render` guard, and a virtual-model launch form for a background job.
|
|
8
|
+
|
|
9
|
+
Domain: invoicing. An `Invoice` has line items, can be locked, exported as CSV, and a
|
|
10
|
+
background "send reminders" job can be launched from a non-persistent form.
|
|
11
|
+
|
|
12
|
+
## The models
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# app/models/invoice.rb
|
|
16
|
+
class Invoice < ApplicationRecord
|
|
17
|
+
has_many :line_items, dependent: :destroy
|
|
18
|
+
accepts_nested_attributes_for :line_items, allow_destroy: true
|
|
19
|
+
belongs_to :customer
|
|
20
|
+
|
|
21
|
+
field :number, :string
|
|
22
|
+
field :customer, :association
|
|
23
|
+
field :total, :decimal
|
|
24
|
+
field :locked, :boolean
|
|
25
|
+
field :issued_at, :datetime
|
|
26
|
+
|
|
27
|
+
def label = number
|
|
28
|
+
|
|
29
|
+
# Feasibility: a locked invoice must not be edited or destroyed.
|
|
30
|
+
# Back every important prevention with a real validation (see note in feasibility.md).
|
|
31
|
+
prevent %i[edit destroy], 'the invoice is locked' do
|
|
32
|
+
locked?
|
|
33
|
+
end
|
|
34
|
+
validate { errors.add(:base, 'invoice is locked') if locked_was && locked? }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# app/models/line_item.rb
|
|
38
|
+
class LineItem < ApplicationRecord
|
|
39
|
+
belongs_to :invoice
|
|
40
|
+
field :description, :string
|
|
41
|
+
field :amount, :decimal
|
|
42
|
+
def label = description
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Index with a CSV export endpoint and exposed intents
|
|
47
|
+
|
|
48
|
+
One component, two routes: the HTML list and a `.csv` download. The CSV intent is exposed
|
|
49
|
+
so the layout renders a "Download CSV" button in the page header.
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
class Components::Invoices::Index < Compony::Component
|
|
53
|
+
include Compony::ComponentMixins::Resourceful
|
|
54
|
+
|
|
55
|
+
setup do
|
|
56
|
+
label(:all) { 'Invoices' }
|
|
57
|
+
|
|
58
|
+
standalone path: 'invoices' do
|
|
59
|
+
verb :get do
|
|
60
|
+
authorize { can?(:read, Invoice) }
|
|
61
|
+
# Default (HTML) response renders content. CSV gets its own response:
|
|
62
|
+
respond :csv do
|
|
63
|
+
authorize_csv = can?(:read, Invoice) or raise CanCan::AccessDenied # respond skips authorize!
|
|
64
|
+
send_data(InvoiceCsv.new(@data).to_csv, filename: 'invoices.csv', type: 'text/csv')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
load_data { @data = Invoice.accessible_by(current_ability).order(issued_at: :desc) }
|
|
70
|
+
|
|
71
|
+
exposed_intents do
|
|
72
|
+
add :index, :invoices, label: 'Download CSV', name: :csv, path: { format: :csv }
|
|
73
|
+
add :request_reminders, :invoices, label: 'Send reminders', method: :get
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
content do
|
|
77
|
+
concat render_intent(:new, :invoices, button: { label: { format: :short } })
|
|
78
|
+
concat render_sub_comp(:list, @data)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Notes:
|
|
85
|
+
|
|
86
|
+
- `respond :csv` handles `GET /invoices.csv`. Because overriding `respond` **skips the
|
|
87
|
+
default `authorize`** (see [gotchas.md](/doc/guide/gotchas.md#3-overriding-respond-skips-authorization)),
|
|
88
|
+
the CSV branch re-checks the ability itself.
|
|
89
|
+
- `path: { format: :csv }` on the exposed intent makes its button point at `.csv`.
|
|
90
|
+
- The layout renders exposed intents of `Compony.root_comp` — see
|
|
91
|
+
[intents.md](/doc/guide/intents.md#rendering-exposed-intents).
|
|
92
|
+
|
|
93
|
+
## A custom Form with nested line items
|
|
94
|
+
|
|
95
|
+
`Edit`/`New` look for `Components::Invoices::Form` by default. We write it with
|
|
96
|
+
`accepts_nested_attributes_for` via `simple_form`'s `simple_fields_for`, and whitelist the
|
|
97
|
+
nested params with a raw `schema_line`.
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class Components::Invoices::Form < Compony::Components::Form
|
|
101
|
+
setup do
|
|
102
|
+
form_fields do
|
|
103
|
+
concat field(:number)
|
|
104
|
+
concat field(:customer, as: :tom_select) # association name, NOT customer_id
|
|
105
|
+
concat field(:issued_at, as: :flatpickr_datetime)
|
|
106
|
+
concat(f.simple_fields_for(:line_items) do |lf|
|
|
107
|
+
concat lf.input(:description)
|
|
108
|
+
concat lf.input(:amount)
|
|
109
|
+
end)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
schema_fields :number, :customer, :issued_at
|
|
113
|
+
# Nested attributes need a manual Schemacop line:
|
|
114
|
+
schema_line do
|
|
115
|
+
ary? :line_items_attributes do
|
|
116
|
+
list :hash do
|
|
117
|
+
int? :id
|
|
118
|
+
str? :description
|
|
119
|
+
num? :amount
|
|
120
|
+
boo? :_destroy
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`schema_field :customer` (association name) lets Compony add `customer_id` automatically —
|
|
129
|
+
passing `:customer_id` here would not work
|
|
130
|
+
([gotchas.md](/doc/guide/gotchas.md#4-schema_field-with-the-foreign-key-name)).
|
|
131
|
+
|
|
132
|
+
## Edit: thin, plus a guard and a custom redirect
|
|
133
|
+
|
|
134
|
+
`Edit` inherits all CRUD wiring. We only add a `before_render` guard (independent of HTTP
|
|
135
|
+
verb) and override where a successful save lands.
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
class Components::Invoices::Edit < Compony::Components::Edit
|
|
139
|
+
setup do
|
|
140
|
+
before_render do
|
|
141
|
+
if @data.locked?
|
|
142
|
+
flash.alert = 'This invoice is locked.'
|
|
143
|
+
redirect_to Compony.path(:show, @data)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
on_updated_redirect_path { Compony.path(:show, @data) }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The `prevent :edit` in the model already greys out the edit button with a tooltip; the
|
|
153
|
+
`before_render` guard plus the model validation stop a hand-crafted request
|
|
154
|
+
([feasibility.md](/doc/guide/feasibility.md) explains why all three layers are needed).
|
|
155
|
+
|
|
156
|
+
`Components::Invoices::New`, `Show`, `Destroy` can stay empty — the pre-built parents do
|
|
157
|
+
the work.
|
|
158
|
+
|
|
159
|
+
## A virtual-model launch form for a background job
|
|
160
|
+
|
|
161
|
+
"Send reminders" should pop a small form (which customers? how many days overdue?) and, on
|
|
162
|
+
submit, queue a job — nothing is persisted. Inherit `New`, back it with a
|
|
163
|
+
`Compony::VirtualModel`, and take over `on_created_respond`.
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
class Components::Invoices::RequestReminders < Compony::Components::New
|
|
167
|
+
class VirtualModel < Compony::VirtualModel
|
|
168
|
+
attribute :min_days_overdue, :integer, default: 7
|
|
169
|
+
attribute :only_locked, :boolean, default: false
|
|
170
|
+
field :min_days_overdue, :integer
|
|
171
|
+
field :only_locked, :boolean
|
|
172
|
+
validates :min_days_overdue, numericality: { greater_than: 0 }
|
|
173
|
+
def label = 'Reminder request'
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
class Form < Compony::Components::Form
|
|
177
|
+
setup do
|
|
178
|
+
form_fields do
|
|
179
|
+
concat field(:min_days_overdue)
|
|
180
|
+
concat field(:only_locked)
|
|
181
|
+
end
|
|
182
|
+
schema_fields :min_days_overdue, :only_locked
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
setup do
|
|
187
|
+
standalone path: 'invoices/request_reminders' # avoid clashing with the New route
|
|
188
|
+
data_class VirtualModel
|
|
189
|
+
form_comp_class Components::Invoices::RequestReminders::Form
|
|
190
|
+
label(:all) { 'Send reminders' }
|
|
191
|
+
|
|
192
|
+
# @data.save is a no-op (virtual). on_created_respond fires only after validations pass.
|
|
193
|
+
on_created_respond do
|
|
194
|
+
SendRemindersJob.perform_later(min_days_overdue: @data.min_days_overdue,
|
|
195
|
+
only_locked: @data.only_locked)
|
|
196
|
+
flash.notice = 'Reminders queued — give it a few minutes.'
|
|
197
|
+
redirect_to Compony.path(:index, :invoices)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Why it works: inheriting `New` gives the `new`/`create` flow. On submit Compony validates,
|
|
204
|
+
re-renders the form with errors on failure, otherwise calls `@data.save` (a no-op on a
|
|
205
|
+
virtual model) and runs `on_created_respond`, where you regain control. For ActiveStorage
|
|
206
|
+
on a virtual model, also override `store_data` to only `validate` — see
|
|
207
|
+
[virtual_models.md](/doc/guide/virtual_models.md).
|
|
208
|
+
|
|
209
|
+
[Guide index](/README.md#guide--documentation)
|
data/doc/guide/generators.md
CHANGED
|
@@ -10,6 +10,6 @@ To make your life easier and coding faster, Compony comes with two generators:
|
|
|
10
10
|
|
|
11
11
|
### Support for custom base components
|
|
12
12
|
|
|
13
|
-
Generators will automatically detect your `BaseComponents` (see [Inheritance: best practice](./
|
|
13
|
+
Generators will automatically detect your `BaseComponents` (see [Inheritance: best practice](./inheritance.md#best-practice)).
|
|
14
14
|
|
|
15
15
|
[Guide index](/README.md#guide--documentation)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
|
+
|
|
3
|
+
# Glossary
|
|
4
|
+
|
|
5
|
+
One-line definitions of Compony vocabulary. Deeper treatment is linked.
|
|
6
|
+
|
|
7
|
+
| Term | Definition |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| **Component** | A Ruby class (`Components::Family::Name`) bundling a route, controller action and view. See [basic_component.md](/doc/guide/basic_component.md). |
|
|
10
|
+
| **Family** | The plural namespace grouping a model's components, analogous to a Rails controller (`Users`). |
|
|
11
|
+
| **Comp name** | The component's own name, analogous to a Rails action (`show`). |
|
|
12
|
+
| **`setup`** | Class-level block holding nearly all component config; parent's runs before child's. |
|
|
13
|
+
| **Content block** | A named (`:main` default) view block rendered via Dyny inside a RequestContext. |
|
|
14
|
+
| **`before_render`** | Hook chain run before content; if it sets a response body (e.g. redirect) content is skipped. |
|
|
15
|
+
| **Standalone** | A `standalone` config that makes Compony emit a Rails route for the component. See [standalone.md](/doc/guide/standalone.md). |
|
|
16
|
+
| **Standalone name** | Identifier for one of several routes a component exposes; `nil` = the main one. |
|
|
17
|
+
| **Verb** | An HTTP method config inside a standalone (`verb :get do ... end`). |
|
|
18
|
+
| **`authorize`** | Mandatory per-verb block; truthy grants access, falsy raises `CanCan::AccessDenied`. |
|
|
19
|
+
| **`respond`** | Per-verb block overriding the controller response; overriding it skips the default authorize step. |
|
|
20
|
+
| **Resourceful** | A component that auto-loads a record/relation into `@data` (mixin or pre-built parent). See [resourceful.md](/doc/guide/resourceful.md). |
|
|
21
|
+
| **`@data`** | The record (or AR relation) a resourceful component operates on. |
|
|
22
|
+
| **`data_class`** | The model class a resourceful component expects; defaults from the family name. |
|
|
23
|
+
| **Lifecycle hooks** | `load_data → after_load_data → authorize → assign_attributes → after_assign_attributes → store_data`. |
|
|
24
|
+
| **Intent** | A gateway object to a target component carrying context (model, path, feasibility, label). See [intents.md](/doc/guide/intents.md). |
|
|
25
|
+
| **Exposed intent** | An intent a component declares for its parent/layout to render (e.g. an actions toolbar). |
|
|
26
|
+
| **`Compony.path`** | Helper that builds a Rails path string via an intent (use for redirects). |
|
|
27
|
+
| **`render_intent`** | Content/view helper that renders a link or button to another component. |
|
|
28
|
+
| **`render_sub_comp`** | Content helper that instantiates and nests another component. |
|
|
29
|
+
| **Sub comp / parent comp** | A component instantiated inside another; the outer one is its `parent_comp`. |
|
|
30
|
+
| **Root comp** | The top component of the current render tree (no parent); `Compony.root_comp`. |
|
|
31
|
+
| **RequestContext** | Dslblend object content blocks run in: providers are the component, controller, helpers. See [internal_datastructures.md](/doc/guide/internal_datastructures.md). |
|
|
32
|
+
| **Backfire** | Dslblend mechanism copying instance vars set in a block back onto the component. |
|
|
33
|
+
| **Button / style** | A presenter component for an intent; `style` (`:css_button`, `:link`, custom) selects the class. |
|
|
34
|
+
| **Feasibility** | Framework where model `prevent` blocks disable buttons/links to an action with a reason. See [feasibility.md](/doc/guide/feasibility.md). |
|
|
35
|
+
| **`prevent`** | Model class DSL declaring a feasibility prevention for one or more actions. |
|
|
36
|
+
| **Field** | A model-level declaration (`field :name, :type`) of a UI-relevant attribute and its formatting. See [model_fields.md](/doc/guide/model_fields.md). |
|
|
37
|
+
| **Ownership / `owned_by`** | Declares a model conceptually part of another, adjusting redirects/top actions. See [ownership.md](/doc/guide/ownership.md). |
|
|
38
|
+
| **Virtual model** | A non-persistent ActiveType-backed model usable as a form target. See [virtual_models.md](/doc/guide/virtual_models.md). |
|
|
39
|
+
| **Dyny** | The HTML-as-Ruby templating gem content blocks are written in ([repo](https://github.com/kalsan/dyny)). |
|
|
40
|
+
| **Abstract component** | A component never routed directly, only inherited from (e.g. an app's `BaseComponents::*`). |
|
|
41
|
+
|
|
42
|
+
[Guide index](/README.md#guide--documentation)
|