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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +36 -1
  3. data/CHANGELOG.md +31 -0
  4. data/CLAUDE.md +85 -0
  5. data/Gemfile.lock +1 -1
  6. data/README.md +13 -3
  7. data/VERSION +1 -1
  8. data/compony.gemspec +3 -3
  9. data/doc/ComponentGenerator.html +1 -1
  10. data/doc/Components.html +1 -1
  11. data/doc/ComponentsGenerator.html +1 -1
  12. data/doc/Compony/Component.html +54 -54
  13. data/doc/Compony/ComponentMixins/Default/Labelling.html +1 -1
  14. data/doc/Compony/ComponentMixins/Default/Standalone/ResourcefulVerbDsl.html +1 -1
  15. data/doc/Compony/ComponentMixins/Default/Standalone/StandaloneDsl.html +109 -70
  16. data/doc/Compony/ComponentMixins/Default/Standalone/VerbDsl.html +64 -28
  17. data/doc/Compony/ComponentMixins/Default/Standalone.html +1 -1
  18. data/doc/Compony/ComponentMixins/Default.html +1 -1
  19. data/doc/Compony/ComponentMixins/Resourceful.html +213 -74
  20. data/doc/Compony/ComponentMixins.html +1 -1
  21. data/doc/Compony/Components/Buttons/CssButton.html +1 -1
  22. data/doc/Compony/Components/Buttons/Link.html +1 -1
  23. data/doc/Compony/Components/Buttons.html +1 -1
  24. data/doc/Compony/Components/Destroy.html +83 -29
  25. data/doc/Compony/Components/Edit.html +110 -38
  26. data/doc/Compony/Components/Form.html +551 -208
  27. data/doc/Compony/Components/Index.html +1 -1
  28. data/doc/Compony/Components/List.html +3 -3
  29. data/doc/Compony/Components/New.html +110 -38
  30. data/doc/Compony/Components/Show.html +1 -1
  31. data/doc/Compony/Components/WithForm.html +194 -47
  32. data/doc/Compony/Components.html +1 -1
  33. data/doc/Compony/ControllerMixin.html +1 -1
  34. data/doc/Compony/Engine.html +1 -1
  35. data/doc/Compony/Intent.html +2 -2
  36. data/doc/Compony/ManageIntentsDsl.html +1 -1
  37. data/doc/Compony/MethodAccessibleHash.html +1 -1
  38. data/doc/Compony/ModelFields/Anchormodel.html +1 -1
  39. data/doc/Compony/ModelFields/Association.html +1 -1
  40. data/doc/Compony/ModelFields/Attachment.html +1 -1
  41. data/doc/Compony/ModelFields/Base.html +1 -1
  42. data/doc/Compony/ModelFields/Boolean.html +1 -1
  43. data/doc/Compony/ModelFields/Color.html +1 -1
  44. data/doc/Compony/ModelFields/Currency.html +1 -1
  45. data/doc/Compony/ModelFields/Date.html +1 -1
  46. data/doc/Compony/ModelFields/Datetime.html +1 -1
  47. data/doc/Compony/ModelFields/Decimal.html +1 -1
  48. data/doc/Compony/ModelFields/Email.html +1 -1
  49. data/doc/Compony/ModelFields/Float.html +1 -1
  50. data/doc/Compony/ModelFields/Integer.html +1 -1
  51. data/doc/Compony/ModelFields/Percentage.html +1 -1
  52. data/doc/Compony/ModelFields/Phone.html +1 -1
  53. data/doc/Compony/ModelFields/RichText.html +1 -1
  54. data/doc/Compony/ModelFields/String.html +1 -1
  55. data/doc/Compony/ModelFields/Text.html +1 -1
  56. data/doc/Compony/ModelFields/Time.html +1 -1
  57. data/doc/Compony/ModelFields/Url.html +1 -1
  58. data/doc/Compony/ModelFields.html +1 -1
  59. data/doc/Compony/ModelMixin.html +1 -1
  60. data/doc/Compony/NaturalOrdering.html +1 -1
  61. data/doc/Compony/RequestContext.html +1 -1
  62. data/doc/Compony/Version.html +1 -1
  63. data/doc/Compony/ViewHelpers.html +1 -1
  64. data/doc/Compony/VirtualModel.html +1 -1
  65. data/doc/Compony.html +1 -1
  66. data/doc/ComponyController.html +1 -1
  67. data/doc/_index.html +97 -1
  68. data/doc/file.CHANGELOG.html +758 -0
  69. data/doc/file.README.html +25 -4
  70. data/doc/file.basic_component.html +314 -0
  71. data/doc/file.cookbook.html +189 -0
  72. data/doc/file.destroy.html +105 -0
  73. data/doc/file.dsl_reference.html +672 -0
  74. data/doc/file.edit.html +109 -0
  75. data/doc/file.example.html +291 -0
  76. data/doc/file.example_advanced.html +257 -0
  77. data/doc/file.feasibility.html +115 -0
  78. data/doc/file.form.html +195 -0
  79. data/doc/file.generators.html +89 -0
  80. data/doc/file.glossary.html +217 -0
  81. data/doc/file.gotchas.html +222 -0
  82. data/doc/file.index.html +135 -0
  83. data/doc/file.inheritance.html +136 -0
  84. data/doc/file.installation.html +115 -0
  85. data/doc/file.integrations.html +218 -0
  86. data/doc/file.intents.html +265 -0
  87. data/doc/file.internal_datastructures.html +129 -0
  88. data/doc/file.list.html +253 -0
  89. data/doc/file.maintaining.html +127 -0
  90. data/doc/file.model_fields.html +137 -0
  91. data/doc/file.nesting.html +237 -0
  92. data/doc/file.new.html +109 -0
  93. data/doc/file.ownership.html +98 -0
  94. data/doc/file.patterns.html +669 -0
  95. data/doc/file.pre_built_components.html +99 -0
  96. data/doc/file.resourceful.html +181 -0
  97. data/doc/file.show.html +158 -0
  98. data/doc/file.standalone.html +233 -0
  99. data/doc/file.virtual_models.html +117 -0
  100. data/doc/file.with_form.html +157 -0
  101. data/doc/file_list.html +160 -0
  102. data/doc/guide/cookbook.md +41 -0
  103. data/doc/guide/dsl_reference.md +155 -0
  104. data/doc/guide/example_advanced.md +209 -0
  105. data/doc/guide/generators.md +1 -1
  106. data/doc/guide/glossary.md +42 -0
  107. data/doc/guide/gotchas.md +125 -0
  108. data/doc/guide/maintaining.md +64 -0
  109. data/doc/guide/patterns.md +681 -0
  110. data/doc/guide/pre_built_components/edit.md +1 -1
  111. data/doc/guide/pre_built_components/index.md +64 -1
  112. data/doc/guide/pre_built_components/list.md +111 -7
  113. data/doc/guide/pre_built_components/show.md +57 -2
  114. data/doc/guide/pre_built_components/with_form.md +56 -9
  115. data/doc/guide/pre_built_components.md +7 -2
  116. data/doc/guide/standalone.md +16 -1
  117. data/doc/index.html +25 -4
  118. data/doc/integrations.md +61 -0
  119. data/doc/llms.txt +62 -0
  120. data/doc/top-level-namespace.html +1 -1
  121. data/lib/compony/component.rb +8 -3
  122. data/lib/compony/component_mixins/default/standalone/standalone_dsl.rb +32 -15
  123. data/lib/compony/component_mixins/default/standalone/verb_dsl.rb +11 -3
  124. data/lib/compony/component_mixins/resourceful.rb +30 -16
  125. data/lib/compony/components/destroy.rb +21 -1
  126. data/lib/compony/components/edit.rb +25 -1
  127. data/lib/compony/components/form.rb +63 -21
  128. data/lib/compony/components/list.rb +1 -1
  129. data/lib/compony/components/new.rb +25 -1
  130. data/lib/compony/components/with_form.rb +20 -5
  131. data/lib/compony/intent.rb +1 -1
  132. 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)
@@ -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](./doc/guide/inheritance.md#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)