compony 0.11.11 → 0.11.13
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/.rspec +2 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +145 -106
- data/README.md +12 -1
- data/Rakefile +10 -0
- data/TODO.md +2 -1
- data/VERSION +1 -1
- data/compony.gemspec +5 -3
- data/config/locales/es.yml +46 -0
- data/doc/ComponentGenerator.html +1 -1
- data/doc/Components.html +1 -1
- data/doc/ComponentsGenerator.html +1 -1
- data/doc/Compony/Component.html +1 -1
- 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 +1 -1
- data/doc/Compony/ComponentMixins/Default/Standalone/VerbDsl.html +1 -1
- data/doc/Compony/ComponentMixins/Default/Standalone.html +1 -1
- data/doc/Compony/ComponentMixins/Default.html +1 -1
- data/doc/Compony/ComponentMixins/Resourceful.html +1 -1
- 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 +1 -1
- data/doc/Compony/Components/Edit.html +1 -1
- data/doc/Compony/Components/Form.html +1 -1
- data/doc/Compony/Components/Index.html +1 -1
- data/doc/Compony/Components/List.html +1 -1
- data/doc/Compony/Components/New.html +1 -1
- data/doc/Compony/Components/Show.html +1 -1
- data/doc/Compony/Components/WithForm.html +1 -1
- 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 +1 -1
- data/doc/Compony/ManageIntentsDsl.html +2 -2
- 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 +8 -2
- 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 +1 -1
- data/doc/file.CHANGELOG.html +88 -76
- data/doc/file.README.html +21 -11
- data/doc/file.basic_component.html +14 -14
- data/doc/file.cookbook.html +27 -27
- data/doc/file.destroy.html +2 -2
- data/doc/file.dsl_reference.html +199 -199
- data/doc/file.edit.html +3 -3
- data/doc/file.example.html +13 -13
- data/doc/file.example_advanced.html +10 -10
- data/doc/file.feasibility.html +2 -2
- data/doc/file.form.html +7 -7
- data/doc/file.generators.html +3 -3
- data/doc/file.glossary.html +44 -44
- data/doc/file.gotchas.html +35 -35
- data/doc/file.index.html +10 -10
- data/doc/file.inheritance.html +5 -5
- data/doc/file.installation.html +9 -9
- data/doc/file.integrations.html +46 -46
- data/doc/file.intents.html +11 -11
- data/doc/file.internal_datastructures.html +6 -6
- data/doc/file.list.html +35 -35
- data/doc/file.maintaining.html +21 -21
- data/doc/file.model_fields.html +3 -3
- data/doc/file.nesting.html +5 -5
- data/doc/file.new.html +3 -3
- data/doc/file.ownership.html +2 -2
- data/doc/file.patterns.html +50 -50
- data/doc/file.pre_built_components.html +2 -2
- data/doc/file.resourceful.html +5 -5
- data/doc/file.show.html +17 -17
- data/doc/file.standalone.html +12 -12
- data/doc/file.virtual_models.html +2 -2
- data/doc/file.with_form.html +23 -23
- data/doc/index.html +21 -11
- data/doc/top-level-namespace.html +1 -1
- data/lib/compony/model_fields/base.rb +3 -0
- data/spec/README.md +22 -0
- data/spec/compony/component_spec.rb +80 -0
- data/spec/compony/compony_spec.rb +102 -0
- data/spec/compony/intent_spec.rb +94 -0
- data/spec/compony/method_accessible_hash_spec.rb +32 -0
- data/spec/compony/model_fields_spec.rb +70 -0
- data/spec/compony/model_mixin_spec.rb +81 -0
- data/spec/compony/natural_ordering_spec.rb +61 -0
- data/spec/dummy/app/components/statics/home.rb +17 -0
- data/spec/dummy/app/components/statics/secret.rb +15 -0
- data/spec/dummy/app/components/users/destroy.rb +2 -0
- data/spec/dummy/app/components/users/edit.rb +2 -0
- data/spec/dummy/app/components/users/form.rb +14 -0
- data/spec/dummy/app/components/users/index.rb +29 -0
- data/spec/dummy/app/components/users/new.rb +2 -0
- data/spec/dummy/app/components/users/show.rb +26 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/models/ability.rb +7 -0
- data/spec/dummy/app/models/application_record.rb +4 -0
- data/spec/dummy/app/models/user.rb +22 -0
- data/spec/dummy/app/views/layouts/application.html.erb +7 -0
- data/spec/dummy/config/application.rb +20 -0
- data/spec/dummy/config/database.yml +3 -0
- data/spec/dummy/config/environment.rb +3 -0
- data/spec/dummy/config/initializers/simple_form.rb +9 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/db/schema.rb +14 -0
- data/spec/rails_helper.rb +23 -0
- data/spec/requests/authorization_spec.rb +16 -0
- data/spec/requests/plain_component_spec.rb +9 -0
- data/spec/requests/routing_spec.rb +20 -0
- data/spec/requests/users_crud_spec.rb +99 -0
- metadata +63 -1
data/doc/file.patterns.html
CHANGED
|
@@ -60,13 +60,13 @@
|
|
|
60
60
|
<div id="content"><div id='filecontents'>
|
|
61
61
|
<p><a href="/README_md.html#guide--documentation">Back to the guide</a></p>
|
|
62
62
|
|
|
63
|
-
<h1 id="
|
|
63
|
+
<h1 id="real-world-patterns">Real-world patterns</h1>
|
|
64
64
|
|
|
65
65
|
<p>Conventions distilled from a range of production Compony apps. These are <em>idioms</em>, not framework requirements — but they recur consistently and are worth adopting. Every example uses a neutral domain (<code>Account</code>, <code>Order</code>, <code>LineItem</code>, <code>Document</code>). Where a pattern relies on a companion gem (CanCanCan, ActiveType, simple_form, a date/select input) that is called out.</p>
|
|
66
66
|
|
|
67
67
|
<p>For exact method signatures see <a href="/doc/guide/dsl_reference_md.html">dsl_reference.md</a>; for footguns see <a href="/doc/guide/gotchas_md.html">gotchas.md</a>.</p>
|
|
68
68
|
|
|
69
|
-
<h2 id="
|
|
69
|
+
<h2 id="1-the-app-base-component-layer">1. The app base-component layer</h2>
|
|
70
70
|
|
|
71
71
|
<p>Almost every non-trivial app inserts one abstract layer between Compony’s pre-built components and the concrete ones. Concrete components inherit from the app layer, never from Compony directly. This centralizes layout, button styling, and chrome so the whole app’s look changes in one place.</p>
|
|
72
72
|
|
|
@@ -91,11 +91,11 @@
|
|
|
91
91
|
<span class='kw'>end</span> <span class='comment'># fully functional, empty body
|
|
92
92
|
</span></code></pre>
|
|
93
93
|
|
|
94
|
-
<p>Recurring forms of this layer: <code>BaseComponents::{Index,Show,New,Edit,Destroy,List}</code>. The <code>content :main, hidden: true</code> + <code>content :wrapper</code> pair is the standard way to let children override the inner content while inheriting the outer chrome (see
|
|
94
|
+
<p>Recurring forms of this layer: <code>BaseComponents::{Index,Show,New,Edit,Destroy,List}</code>. The <code>content :main, hidden: true</code> + <code>content :wrapper</code> pair is the standard way to let children override the inner content while inheriting the outer chrome (see <a href="/doc/guide/basic_component_md.html#nesting-content-blocks-calling-a-content-block-from-another">basic_component.md</a>).</p>
|
|
95
95
|
|
|
96
96
|
<p>Teams sometimes add their own helper DSL on top of this layer (CSV/PDF helpers, archive toggles, etc.). Keep such helpers in the app base layer, not in concrete components.</p>
|
|
97
97
|
|
|
98
|
-
<h2 id="
|
|
98
|
+
<h2 id="2-thin-leaf-components">2. Thin leaf components</h2>
|
|
99
99
|
|
|
100
100
|
<p>Concrete CRUD components are usually empty — all behavior is inherited. Add a <code>setup</code> block only to deviate.</p>
|
|
101
101
|
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
|
|
107
107
|
<p>This is the single most common pattern. Prefer it over hand-written endpoints (<a href="/doc/guide/gotchas_md.html#15-hand-rolled-endpoint-where-a-pre-built-crud-component-exists">gotchas.md #15</a>).</p>
|
|
108
108
|
|
|
109
|
-
<h2 id="
|
|
109
|
+
<h2 id="3-index--loaddata-scope--nested-list">3. Index = <code>load_data</code> scope + nested <code>:list</code></h2>
|
|
110
110
|
|
|
111
111
|
<p>Index components rarely render rows themselves; they load a scope and embed the family’s List via <code>render_sub_comp</code>.</p>
|
|
112
112
|
|
|
@@ -121,12 +121,12 @@
|
|
|
121
121
|
<span class='kw'>end</span>
|
|
122
122
|
</code></pre>
|
|
123
123
|
<ul><li>
|
|
124
|
-
<p><code>accessible_by(current_ability)</code> is the CanCanCan scoping idiom — pair it with the
|
|
124
|
+
<p><code>accessible_by(current_ability)</code> is the CanCanCan scoping idiom — pair it with the <code>authorize</code> block so list and access rules agree.</p>
|
|
125
125
|
</li><li>
|
|
126
|
-
<p><code>concat</code> is mandatory around <code>render_sub_comp</code>/<code>render_intent</code>
|
|
126
|
+
<p><code>concat</code> is mandatory around <code>render_sub_comp</code>/<code>render_intent</code> (<a href="/doc/guide/gotchas_md.html#2-render_intent--render_sub_comp-output-not-appearing">gotchas.md #2</a>).</p>
|
|
127
127
|
</li></ul>
|
|
128
128
|
|
|
129
|
-
<h2 id="
|
|
129
|
+
<h2 id="4-list-customization">4. List customization</h2>
|
|
130
130
|
|
|
131
131
|
<p>This pattern is typically combined with a customized <code>BaseComponents::List</code> that adds styling and features to the pre-built list component.</p>
|
|
132
132
|
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
|
|
153
153
|
<p><code>skip_*</code> options (<code>skip_pagination:</code>, <code>skip_filtering:</code>, <code>skip_columns:</code>, …) are constructor kwargs passed through <code>render_sub_comp</code>, useful for read-only embeds.</p>
|
|
154
154
|
|
|
155
|
-
<h2 id="
|
|
155
|
+
<h2 id="5-custom-form--schemacop-kept-in-sync">5. Custom form + Schemacop, kept in sync</h2>
|
|
156
156
|
|
|
157
157
|
<p><code>form_fields</code> (rendering) and <code>schema_*</code> (param whitelist) must mirror each other.</p>
|
|
158
158
|
|
|
@@ -176,13 +176,13 @@
|
|
|
176
176
|
<span class='kw'>end</span>
|
|
177
177
|
</code></pre>
|
|
178
178
|
<ul><li>
|
|
179
|
-
<p><code>as: :tom_select</code> / <code>as: :flatpickr_date(time)</code> are app-registered simple_form inputs
|
|
179
|
+
<p><code>as: :tom_select</code> / <code>as: :flatpickr_date(time)</code> are app-registered simple_form inputs (TomSelect, Flatpickr) — a good choice for selects and date pickers.</p>
|
|
180
180
|
</li><li>
|
|
181
|
-
<p>Use the <strong>association name</strong> in <code>field</code>/<code>schema_field</code>; <code>_id</code> is added automatically
|
|
181
|
+
<p>Use the <strong>association name</strong> in <code>field</code>/<code>schema_field</code>; <code>_id</code> is added automatically (<a href="/doc/guide/gotchas_md.html#4-schema_field-with-the-foreign-key-name">gotchas.md #4</a>).</p>
|
|
182
182
|
</li><li>
|
|
183
|
-
<p>Nested attributes: <code>f.simple_fields_for(:line_items)</code> in <code>form_fields</code> plus a raw
|
|
183
|
+
<p>Nested attributes: <code>f.simple_fields_for(:line_items)</code> in <code>form_fields</code> plus a raw <code>schema_line { ary? :line_items_attributes do ... end }</code>.</p>
|
|
184
184
|
</li><li>
|
|
185
|
-
<p>Multilang fields: <code>field(:title, multilang: true).each { |i| concat i }</code> paired with
|
|
185
|
+
<p>Multilang fields: <code>field(:title, multilang: true).each { |i| concat i }</code> paired with <code>schema_field :title, multilang: true</code>.</p>
|
|
186
186
|
</li></ul>
|
|
187
187
|
|
|
188
188
|
<p>Wire a non-default form into New/Edit with <code>form_comp_class</code>:</p>
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
<span class='kw'>end</span>
|
|
193
193
|
</code></pre>
|
|
194
194
|
|
|
195
|
-
<h2 id="
|
|
195
|
+
<h2 id="6-autocomplete-form-app-level-subclass">6. Autocomplete form (app-level subclass)</h2>
|
|
196
196
|
|
|
197
197
|
<p>Compony does not ship autocomplete, but a very common app pattern is an <code>AutocompleteForm</code> base (subclass of <code>Compony::Components::Form</code>) exposing an extra <code>standalone</code> JSON endpoint for an ajax select. Shape:</p>
|
|
198
198
|
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
|
|
214
214
|
<p>If you need autocomplete, build this base once and reuse it.</p>
|
|
215
215
|
|
|
216
|
-
<h2 id="
|
|
216
|
+
<h2 id="7-tabbed-show-via-a-mixin">7. Tabbed Show via a mixin</h2>
|
|
217
217
|
|
|
218
218
|
<p>Detail pages are split into tabs with a small app mixin that adds a <code>tab</code> DSL and renders a tab bar into <code>:main</code>. Each tab body typically renders <code>content :data</code> or a nested list.</p>
|
|
219
219
|
|
|
@@ -230,20 +230,20 @@
|
|
|
230
230
|
|
|
231
231
|
<p>The mixin keys the active tab off a prefixed param (<code>param_name('tab')</code>) so multiple tabbed components can coexist. Compony has no built-in tabs — copy the mixin per app.</p>
|
|
232
232
|
|
|
233
|
-
<h2 id="
|
|
233
|
+
<h2 id="8-lifecycle-hooks-for-derived-data">8. Lifecycle hooks for derived data</h2>
|
|
234
234
|
<ul><li>
|
|
235
|
-
<p><strong><code>after_assign_attributes</code></strong> — fill defaults / context after params are assigned,
|
|
235
|
+
<p><strong><code>after_assign_attributes</code></strong> — fill defaults / context after params are assigned, before validation: <code>@data.account_id ||= current_user.account_id</code>.</p>
|
|
236
236
|
</li><li>
|
|
237
|
-
<p><strong><code>before_render</code></strong> — verb-independent guards and precomputation. Redirect and the
|
|
237
|
+
<p><strong><code>before_render</code></strong> — verb-independent guards and precomputation. Redirect and the content chain is skipped: <code>ruby before_render do redirect_to Compony.path(:show, @data) if @data.locked? end </code></p>
|
|
238
238
|
</li><li>
|
|
239
239
|
<p><strong><code>load_data</code></strong> — narrow the scope (<code>accessible_by</code>, <code>includes</code>, ordering).</p>
|
|
240
240
|
</li><li>
|
|
241
241
|
<p><strong><code>store_data</code></strong> — override persistence (virtual models, file handling, bulk import).</p>
|
|
242
242
|
</li><li>
|
|
243
|
-
<p><strong><code>on_{created,updated,destroyed}_redirect_path</code></strong> — control where success lands, e.g.
|
|
243
|
+
<p><strong><code>on_{created,updated,destroyed}_redirect_path</code></strong> — control where success lands, e.g. <code>Compony.path(:show, @data.parent)</code> for owned records.</p>
|
|
244
244
|
</li></ul>
|
|
245
245
|
|
|
246
|
-
<h2 id="
|
|
246
|
+
<h2 id="9-exposed-intents-as-the-action-toolbar">9. Exposed intents as the action toolbar</h2>
|
|
247
247
|
|
|
248
248
|
<p>Concrete components tailor the header toolbar by <code>add</code>/<code>remove</code> on inherited intents.</p>
|
|
249
249
|
|
|
@@ -257,14 +257,14 @@
|
|
|
257
257
|
<ul><li>
|
|
258
258
|
<p><code>path: { format: :pdf }</code> points a button at a format endpoint (see pattern 10).</p>
|
|
259
259
|
</li><li>
|
|
260
|
-
<p><code>feasibility_action:</code> ties the button’s enabled state to a model <code>prevent</code>
|
|
260
|
+
<p><code>feasibility_action:</code> ties the button’s enabled state to a model <code>prevent</code> (<a href="/doc/guide/feasibility_md.html">feasibility.md</a>).</p>
|
|
261
261
|
</li><li>
|
|
262
|
-
<p>State-dependent toolbars (archived vs active) are done by branching inside the
|
|
262
|
+
<p>State-dependent toolbars (archived vs active) are done by branching inside the <code>exposed_intents</code> block on <code>@data</code>.</p>
|
|
263
263
|
</li><li>
|
|
264
|
-
<p>Generating one intent per enum value is common:
|
|
264
|
+
<p>Generating one intent per enum value is common: <code>Period.all.each { |p| add :new, :prices, name: :"new_#{p.key}", path: { price: { period: p.key } } }</code>.</p>
|
|
265
265
|
</li></ul>
|
|
266
266
|
|
|
267
|
-
<h2 id="
|
|
267
|
+
<h2 id="10-csv--pdf-via-respond-format">10. CSV / PDF via <code>respond :format</code></h2>
|
|
268
268
|
|
|
269
269
|
<p>A format export is the same component with an extra <code>respond</code> branch and an exposed intent pointing at it. Because overriding <code>respond</code> skips the default <code>authorize</code>, re-check there (<a href="/doc/guide/gotchas_md.html#3-overriding-respond-skips-authorization">gotchas.md #3</a>).</p>
|
|
270
270
|
|
|
@@ -285,7 +285,7 @@
|
|
|
285
285
|
<span class='comment'># exposed_intents { add :index, :orders, label: 'CSV', path: { format: :csv } }
|
|
286
286
|
</span></code></pre>
|
|
287
287
|
|
|
288
|
-
<h2 id="
|
|
288
|
+
<h2 id="11-non-crud-job-dispatch-toggles-clone">11. Non-CRUD: job dispatch, toggles, clone</h2>
|
|
289
289
|
|
|
290
290
|
<p><strong>Job dispatch</strong> — POST-only custom component, enqueue, flash, redirect:</p>
|
|
291
291
|
|
|
@@ -337,7 +337,7 @@
|
|
|
337
337
|
<span class='kw'>end</span>
|
|
338
338
|
</code></pre>
|
|
339
339
|
|
|
340
|
-
<h2 id="
|
|
340
|
+
<h2 id="12-virtual-model-for-non-persistent--upload-forms">12. Virtual model for non-persistent / upload forms</h2>
|
|
341
341
|
|
|
342
342
|
<p>Inherit <code>New</code>, back it with a <code>Compony::VirtualModel</code>, take over the response. <code>@data.save</code> is a no-op so business logic goes in <code>on_created_respond</code> (or <code>store_data</code>).</p>
|
|
343
343
|
|
|
@@ -374,7 +374,7 @@
|
|
|
374
374
|
|
|
375
375
|
<p>See <a href="/doc/guide/virtual_models_md.html">virtual_models.md</a> and <a href="/doc/guide/gotchas_md.html#12-activestorage-attachment-on-a-virtual-model">gotchas.md #12</a>.</p>
|
|
376
376
|
|
|
377
|
-
<h2 id="
|
|
377
|
+
<h2 id="13-public-endpoints--webhooks">13. Public endpoints & webhooks</h2>
|
|
378
378
|
|
|
379
379
|
<pre class="code ruby"><code class="ruby"><span class='kw'>class</span> <span class='const'><span class='object_link'><a href="Components.html" title="Components (module)">Components</a></span></span><span class='op'>::</span><span class='const'>Public</span><span class='op'>::</span><span class='const'>Webhook</span> <span class='op'><</span> <span class='const'><span class='object_link'><a href="Compony.html" title="Compony (module)">Compony</a></span></span><span class='op'>::</span><span class='const'><span class='object_link'><a href="Compony/Component.html" title="Compony::Component (class)">Component</a></span></span>
|
|
380
380
|
<span class='id identifier rubyid_setup'>setup</span> <span class='kw'>do</span>
|
|
@@ -401,7 +401,7 @@
|
|
|
401
401
|
|
|
402
402
|
<p>A login-aware redirect splitter is the same shape with <code>verb :get</code> + <code>before_render</code> choosing a <code>Compony.path</code> by <code>current_user</code>.</p>
|
|
403
403
|
|
|
404
|
-
<h2 id="
|
|
404
|
+
<h2 id="14-custom-button-style">14. Custom button style</h2>
|
|
405
405
|
|
|
406
406
|
<p>Register one app button style and refer to it everywhere via <code>style:</code>.</p>
|
|
407
407
|
|
|
@@ -421,7 +421,7 @@
|
|
|
421
421
|
|
|
422
422
|
<p>Make a separate style per visual kind (dropdown item, pill, compact) and select with <code>render_intent(:show, @data, style: :compact)</code>.</p>
|
|
423
423
|
|
|
424
|
-
<h2 id="
|
|
424
|
+
<h2 id="15-inline-edit-card-with-a-turbo-frame">15. Inline-edit card with a Turbo Frame</h2>
|
|
425
425
|
|
|
426
426
|
<p>A Show panel where the Edit form swaps in place (no full-page nav) and swaps back on save. Wrap both the Show content and the Edit form in a <strong>same-named</strong> <code>turbo_frame_tag</code>; Turbo Drive then scopes navigation to that frame. Distinct from the <code>render_sub_comp(:list, …, turbo_frame:)</code> use in <a href="/doc/guide/nesting_md.html">nesting.md</a> (there the frame isolates a nested list’s own search/filter params; here it is the inline-edit boundary for one record’s Show/Edit pair).</p>
|
|
427
427
|
|
|
@@ -451,12 +451,12 @@
|
|
|
451
451
|
<span class='kw'>end</span>
|
|
452
452
|
</code></pre>
|
|
453
453
|
<ul><li>
|
|
454
|
-
<p>Frame name must match exactly; deriving it from the record id keeps it unique when
|
|
454
|
+
<p>Frame name must match exactly; deriving it from the record id keeps it unique when several cards render on one page.</p>
|
|
455
455
|
</li><li>
|
|
456
|
-
<p>A failed save re-renders Edit with HTTP 422 — keep the <code>turbo_frame_tag</code> wrapper in the
|
|
456
|
+
<p>A failed save re-renders Edit with HTTP 422 — keep the <code>turbo_frame_tag</code> wrapper in the Edit content so errors render in-frame too.</p>
|
|
457
457
|
</li></ul>
|
|
458
458
|
|
|
459
|
-
<h2 id="
|
|
459
|
+
<h2 id="16-multi-step-wizard-across-components">16. Multi-step wizard across components</h2>
|
|
460
460
|
|
|
461
461
|
<p>A create/edit flow split over several steps, each its own component, advancing on save. Chain steps with <code>on_updated_redirect_path</code> (or <code>on_created_redirect_path</code>) and render a step indicator via a shared mixin (same mechanism as the tabs mixin in §7).</p>
|
|
462
462
|
|
|
@@ -496,14 +496,14 @@
|
|
|
496
496
|
<span class='comment'># …ConfirmEdit redirects to Show when done.
|
|
497
497
|
</span></code></pre>
|
|
498
498
|
<ul><li>
|
|
499
|
-
<p>Each step is a normal resourceful component on the same model — partial validation per
|
|
499
|
+
<p>Each step is a normal resourceful component on the same model — partial validation per step is just per-step <code>schema_field</code>s in each step’s Form.</p>
|
|
500
500
|
</li><li>
|
|
501
|
-
<p>For a <em>non-persistent</em> wizard (nothing saved until the end), back the components with a
|
|
501
|
+
<p>For a <em>non-persistent</em> wizard (nothing saved until the end), back the components with a <a href="/doc/guide/virtual_models_md.html">VirtualModel</a> and carry state in its attributes (§12).</p>
|
|
502
502
|
</li><li>
|
|
503
503
|
<p><code>comp_name</code> drives the active-step highlight, so the mixin needs no per-step config.</p>
|
|
504
504
|
</li></ul>
|
|
505
505
|
|
|
506
|
-
<h2 id="
|
|
506
|
+
<h2 id="17-inline-patch-without-a-form-reorder--quick-toggle">17. Inline PATCH without a form (reorder / quick toggle)</h2>
|
|
507
507
|
|
|
508
508
|
<p>A JS front-end (drag-to-sort, an inline checkbox) issues a small PATCH that mutates state and returns no body. Add a <strong>named</strong> extra <code>standalone</code> with <code>verb :patch</code>, validate with Schemacop directly, and <code>head :ok</code>. No Form component involved.</p>
|
|
509
509
|
|
|
@@ -531,19 +531,19 @@
|
|
|
531
531
|
|
|
532
532
|
<p>The route is <code>reorder_show_orders_comp</code> (see <a href="/doc/guide/standalone_md.html#naming-of-exposed-routes">standalone naming</a>); point your Stimulus controller’s PATCH at <code>Compony.path(:show, @data, standalone_name: :reorder)</code>.</p>
|
|
533
533
|
<ul><li>
|
|
534
|
-
<p>This is the <a href="/doc/guide/gotchas_md.html#3-overriding-respond-skips-authorization">gotchas.md #3</a>
|
|
534
|
+
<p>This is the <a href="/doc/guide/gotchas_md.html#3-overriding-respond-skips-authorization">gotchas.md #3</a> case: the custom <code>respond</code> replaces the default that runs <code>authorize</code>, so authorize again inside it.</p>
|
|
535
535
|
</li><li>
|
|
536
|
-
<p>Keep companion endpoints in the <em>same</em> component as the screen they serve — what extra
|
|
536
|
+
<p>Keep companion endpoints in the <em>same</em> component as the screen they serve — what extra named <code>standalone</code>s are for (<a href="/doc/guide/standalone_md.html#exposing-multiple-paths-in-the-same-component-calling-standalone-multiple-times">standalone.md</a>), not a reason for a new component.</p>
|
|
537
537
|
</li><li>
|
|
538
538
|
<p>Return <code>head :ok</code> (or small JSON) — no Compony content to render for an ajax-only verb.</p>
|
|
539
539
|
</li></ul>
|
|
540
540
|
|
|
541
|
-
<h2 id="
|
|
541
|
+
<h2 id="18-signed-token-capability-links-auth-less-onboarding--magic-links">18. Signed-token capability links (auth-less onboarding / magic links)</h2>
|
|
542
542
|
|
|
543
|
-
<p>Goal: an emailed link that lets an unauthenticated visitor perform one bounded action —
|
|
543
|
+
<p>Goal: an emailed link that lets an unauthenticated visitor perform one bounded action —invite acceptance, magic login, password reset, email confirmation — without a session. The trick: override Compony’s <code>path do … end</code> to <strong>mint a signed JWT</strong> and carry it as a <code>token</code> query param, then gate a <code>skip_authentication!</code> standalone with <code>authorize { token_valid?(params) }</code>. A small mixin centralizes encode/decode.</p>
|
|
544
544
|
|
|
545
545
|
<blockquote>
|
|
546
|
-
<p><strong>Security — read before copying.</strong> Such a link <em>is</em> the capability; anyone holding the URL can perform the action. It is only safe if every one of these holds: - <strong>Expiry is mandatory.</strong> Put <code>exp</code> in the payload and verify it. A capability link
|
|
546
|
+
<p><strong>Security — read before copying.</strong> Such a link <em>is</em> the capability; anyone holding the URL can perform the action. It is only safe if every one of these holds: - <strong>Expiry is mandatory.</strong> Put <code>exp</code> in the payload and verify it. A capability link without a TTL is a permanent account-takeover primitive (it leaks via referrer headers, proxy logs, mail forwarding, browser history). Pair short TTLs with a resend flow. - <strong>Pin the algorithm and verify the signature</strong> — <code>JWT.decode(token, secret, true, { algorithm: 'HS512' })</code>. Never accept <code>alg: none</code>; never leave verification off. - <strong>Fail closed.</strong> Rescue <code>JWT::DecodeError</code> (its subclasses cover bad signature, malformed token and expiry) and return <code>nil</code>/<code>false</code> so <code>authorize</code> denies with 403 — not a 500. - <strong>Use a dedicated signing secret</strong>, not <code>secret_key_base</code>, so rotating it doesn’t also invalidate every session (and vice-versa). - Still provide an <code>authorize</code> block: <code>skip_authentication!</code> removes <em>authentication</em>, not authorization (<a href="/doc/guide/gotchas_md.html#14-public-endpoint-still-401redirecting">gotchas.md #14</a>).</p>
|
|
547
547
|
</blockquote>
|
|
548
548
|
|
|
549
549
|
<pre class="code ruby"><code class="ruby"><span class='comment'># app/component_mixins/with_token.rb
|
|
@@ -633,24 +633,24 @@
|
|
|
633
633
|
|
|
634
634
|
<p>Notes:</p>
|
|
635
635
|
<ul><li>
|
|
636
|
-
<p><code>Compony.path(:accept, :invites, account: some_account)</code> returns the full tokenized URL —
|
|
636
|
+
<p><code>Compony.path(:accept, :invites, account: some_account)</code> returns the full tokenized URL — email that. The token, not a session, authorizes the request.</p>
|
|
637
637
|
</li><li>
|
|
638
|
-
<p><code>path do</code> runs outside the request context; build URLs via
|
|
638
|
+
<p><code>path do</code> runs outside the request context; build URLs via <code>Rails.application.routes.url_helpers</code>, not <code>controller</code>/<code>helpers</code> (see <a href="/doc/guide/standalone_md.html#customizing-path-generation">standalone.md</a>).</p>
|
|
639
639
|
</li><li>
|
|
640
|
-
<p>Reuse the mixin for every link flow (magic login, password reset, email confirm); encode
|
|
640
|
+
<p>Reuse the mixin for every link flow (magic login, password reset, email confirm); encode a flow discriminator or rely on the per-component payload shape-check to stop a token minted for one flow being replayed against another.</p>
|
|
641
641
|
</li><li>
|
|
642
|
-
<p>One signed boolean in the payload (e.g. <code>confirmed: true</code>) is tamper-proof since the
|
|
642
|
+
<p>One signed boolean in the payload (e.g. <code>confirmed: true</code>) is tamper-proof since the client cannot re-sign — handy for multi-hop confirm flows.</p>
|
|
643
643
|
</li></ul>
|
|
644
644
|
|
|
645
|
-
<h2 id="
|
|
645
|
+
<h2 id="good-habits">Good habits</h2>
|
|
646
646
|
<ul><li>
|
|
647
|
-
<p><strong>CanCanCan everywhere:</strong> <code>authorize { can?(...) }</code>, scope with
|
|
647
|
+
<p><strong>CanCanCan everywhere:</strong> <code>authorize { can?(...) }</code>, scope with <code>Model.accessible_by(current_ability)</code>, bang form <code>authorize!(:read, record)</code> for ad-hoc checks in <code>load_data</code>.</p>
|
|
648
648
|
</li><li>
|
|
649
|
-
<p><strong>Always <code>Compony.path</code> / <code>render_intent</code>,</strong> never hardcoded routes or <code>button_to</code>
|
|
649
|
+
<p><strong>Always <code>Compony.path</code> / <code>render_intent</code>,</strong> never hardcoded routes or <code>button_to</code> (<a href="/doc/guide/gotchas_md.html#11-redirect_to-with-a-hardcoded-path">gotchas.md #11</a>, <a href="/doc/guide/gotchas_md.html#15-hand-rolled-endpoint-where-a-pre-built-crud-component-exists">#15</a>).</p>
|
|
650
650
|
</li><li>
|
|
651
|
-
<p><strong>Place a resourceful component in the family of the model it acts on,</strong> not the family
|
|
651
|
+
<p><strong>Place a resourceful component in the family of the model it acts on,</strong> not the family it is reached from; pass parent context via path params.</p>
|
|
652
652
|
</li><li>
|
|
653
|
-
<p><strong>Keep virtual/form-only fields off models</strong> — use ActiveType/VirtualModel
|
|
653
|
+
<p><strong>Keep virtual/form-only fields off models</strong> — use ActiveType/VirtualModel (<a href="/doc/guide/gotchas_md.html#16-attr_accessor-on-a-model-for-form-only-fields">gotchas.md #16</a>).</p>
|
|
654
654
|
</li><li>
|
|
655
655
|
<p><strong><code>concat</code></strong> around every <code>render_intent</code>/<code>render_sub_comp</code>/<code>field</code> in a block.</p>
|
|
656
656
|
</li></ul>
|
|
@@ -659,7 +659,7 @@
|
|
|
659
659
|
</div></div>
|
|
660
660
|
|
|
661
661
|
<div id="footer">
|
|
662
|
-
Generated on
|
|
662
|
+
Generated on Tue Aug 18 14:36:35 2026 by
|
|
663
663
|
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
|
664
664
|
0.9.34 (ruby-3.4.9).
|
|
665
665
|
</div>
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
<div id="content"><div id='filecontents'>
|
|
61
61
|
<p><a href="/README_md.html#guide--documentation">Back to the guide</a></p>
|
|
62
62
|
|
|
63
|
-
<h1 id="
|
|
63
|
+
<h1 id="pre-built-components-shipped-with-compony">Pre-built components shipped with Compony</h1>
|
|
64
64
|
|
|
65
65
|
<p>Compony comes with a few pre-built components that cover the most common cases that can be speed up development. They are meant to be inherited from and the easiest way to do this is by using the <a href="./generators_md.html">provided Rails generators</a> <code>rails g component ...</code>.</p>
|
|
66
66
|
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
</div></div>
|
|
90
90
|
|
|
91
91
|
<div id="footer">
|
|
92
|
-
Generated on
|
|
92
|
+
Generated on Tue Aug 18 14:36:34 2026 by
|
|
93
93
|
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
|
94
94
|
0.9.34 (ruby-3.4.9).
|
|
95
95
|
</div>
|
data/doc/file.resourceful.html
CHANGED
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
<div id="content"><div id='filecontents'>
|
|
61
61
|
<p><a href="/README_md.html#guide--documentation">Back to the guide</a></p>
|
|
62
62
|
|
|
63
|
-
<h1 id="
|
|
63
|
+
<h1 id="resourceful-components">Resourceful components</h1>
|
|
64
64
|
|
|
65
65
|
<p>So far, we have mainly seen how to present static content, without considering how loading and storing data is handled. Whenever a component is about data, be it a collection (e.g. index, list) or a single instance (e.g. new, show, edit, destroy, form), that component typically becomes resourceful. In order to implement a resourceful component, include the mixin <code>Compony::ComponentMixins::Resourceful</code>.</p>
|
|
66
66
|
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
<span class='kw'>end</span>
|
|
143
143
|
</code></pre>
|
|
144
144
|
|
|
145
|
-
<h2 id="
|
|
145
|
+
<h2 id="complete-resourceful-lifecycle">Complete resourceful lifecycle</h2>
|
|
146
146
|
|
|
147
147
|
<p>This graph documents a typical resourceful lifecycle according to which Compony’s <a href="./pre_built_components_md.html">pre-built components</a> are implemented.</p>
|
|
148
148
|
<ul><li>
|
|
@@ -161,9 +161,9 @@
|
|
|
161
161
|
<p><code>respond</code> typically shows a flash and redirects to another component.</p>
|
|
162
162
|
</li></ul>
|
|
163
163
|
|
|
164
|
-
<p><img src="/doc/resourceful_lifecycle.png"
|
|
164
|
+
<p><img src="/doc/resourceful_lifecycle.png" alt="Graph"> of the complete resourceful lifecycle</p>
|
|
165
165
|
|
|
166
|
-
<h2 id="
|
|
166
|
+
<h2 id="nesting-resourceful-components">Nesting resourceful components</h2>
|
|
167
167
|
|
|
168
168
|
<p>The <a href="/doc/guide/intents_md.html">Intent</a> API makes it very easy to pass a resource to nested components. For more details, refer to <a href="/doc/guide/nesting_md.html#rendering-list-as-sub-comp-in-show">this example</a>.</p>
|
|
169
169
|
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
</div></div>
|
|
172
172
|
|
|
173
173
|
<div id="footer">
|
|
174
|
-
Generated on
|
|
174
|
+
Generated on Tue Aug 18 14:36:33 2026 by
|
|
175
175
|
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
|
176
176
|
0.9.34 (ruby-3.4.9).
|
|
177
177
|
</div>
|
data/doc/file.show.html
CHANGED
|
@@ -63,19 +63,19 @@
|
|
|
63
63
|
<p><a href="/doc/guide/pre_built_components_md.html">List of pre-built components</a></p>
|
|
64
64
|
</li></ul>
|
|
65
65
|
|
|
66
|
-
<h1 id="
|
|
66
|
+
<h1 id="pre-built-components-show">Pre-built components: Show</h1>
|
|
67
67
|
|
|
68
68
|
<p><code>Compony::Components::Show</code> is a resourceful standalone component corresponding to a Rails <code>show</code> action. It loads <code>@data</code> by <code>:id</code> and presents its fields.</p>
|
|
69
69
|
|
|
70
|
-
<h2 id="
|
|
70
|
+
<h2 id="what-it-does-by-default">What it does by default</h2>
|
|
71
71
|
|
|
72
72
|
<p>From <code>lib/compony/components/show.rb</code>:</p>
|
|
73
73
|
<ul><li>
|
|
74
|
-
<p><strong>Route:</strong> <code>standalone path: "/#{family_name}/:id"</code> with an <code>:id</code> constraint that accepts
|
|
74
|
+
<p><strong>Route:</strong> <code>standalone path: "/#{family_name}/:id"</code> with an <code>:id</code> constraint that accepts integer <strong>or</strong> UUID ids, <code>verb :get</code> authorized by <code>can?(:show, @data)</code>.</p>
|
|
75
75
|
</li><li>
|
|
76
76
|
<p><strong>Labels:</strong> long = <code>data.label</code>; short = a generic translated “Show”.</p>
|
|
77
77
|
</li><li>
|
|
78
|
-
<p><strong>Exposed intents:</strong> <code>:edit</code> and <code>:destroy</code> for <code>@data</code>, plus a <code>:back_to_owner</code> intent
|
|
78
|
+
<p><strong>Exposed intents:</strong> <code>:edit</code> and <code>:destroy</code> for <code>@data</code>, plus a <code>:back_to_owner</code> intent if the model is <a href="/doc/guide/ownership_md.html">owned</a>.</p>
|
|
79
79
|
</li><li>
|
|
80
80
|
<p><strong>Content blocks:</strong></p>
|
|
81
81
|
</li><li>
|
|
@@ -89,9 +89,9 @@
|
|
|
89
89
|
<pre class="code ruby"><code class="ruby"><span class='kw'>class</span> <span class='const'><span class='object_link'><a href="Components.html" title="Components (module)">Components</a></span></span><span class='op'>::</span><span class='const'>Users</span><span class='op'>::</span><span class='const'>Show</span> <span class='op'><</span> <span class='const'><span class='object_link'><a href="Compony.html" title="Compony (module)">Compony</a></span></span><span class='op'>::</span><span class='const'><span class='object_link'><a href="Compony/Components.html" title="Compony::Components (module)">Components</a></span></span><span class='op'>::</span><span class='const'><span class='object_link'><a href="Compony/Components/Show.html" title="Compony::Components::Show (class)">Show</a></span></span><span class='semicolon'>;</span> <span class='kw'>end</span> <span class='comment'># fully functional
|
|
90
90
|
</span></code></pre>
|
|
91
91
|
|
|
92
|
-
<h2 id="
|
|
92
|
+
<h2 id="column-dsl">Column DSL</h2>
|
|
93
93
|
|
|
94
|
-
<p><code>Show</code> shares a <code>column</code>/<code>columns</code> DSL with href="./list_md.html"></a> (here “column” means an attribute row, since Show renders one record).</p>
|
|
94
|
+
<p><code>Show</code> shares a <code>column</code>/<code>columns</code> DSL with <a href="./list_md.html"><code>List</code></a> (here “column” means an attribute row, since Show renders one record).</p>
|
|
95
95
|
|
|
96
96
|
<table role="table">
|
|
97
97
|
<thead>
|
|
@@ -103,24 +103,24 @@
|
|
|
103
103
|
</thead>
|
|
104
104
|
<tbody>
|
|
105
105
|
<tr>
|
|
106
|
-
<td
|
|
107
|
-
<td
|
|
106
|
+
<td><code>column</code></td>
|
|
107
|
+
<td><code>column(:name, label: nil, class: nil, link_opts: {}, link_to_component: :show) { |record| ... }</code></td>
|
|
108
108
|
<td>Add/define one attribute row. Without a block, treated as a model field; the block (instance-exec’d per record) supplies the value.</td>
|
|
109
109
|
</tr>
|
|
110
110
|
<tr>
|
|
111
|
-
<td
|
|
112
|
-
<td
|
|
113
|
-
<td>Bulk
|
|
111
|
+
<td><code>columns</code></td>
|
|
112
|
+
<td><code>columns(:a, :b, **shared_kwargs)</code></td>
|
|
113
|
+
<td>Bulk <code>column</code>.</td>
|
|
114
114
|
</tr>
|
|
115
115
|
<tr>
|
|
116
|
-
<td
|
|
117
|
-
<td
|
|
116
|
+
<td><code>all_field_columns</code></td>
|
|
117
|
+
<td><code>all_field_columns(@data)</code></td>
|
|
118
118
|
<td>Add a column for every model field (the default when none declared).</td>
|
|
119
119
|
</tr>
|
|
120
120
|
<tr>
|
|
121
|
-
<td
|
|
122
|
-
<td
|
|
123
|
-
<td>Drop an inherited column. When nesting Show in a parent, prefer the constructor’s
|
|
121
|
+
<td><code>skip_column</code></td>
|
|
122
|
+
<td><code>skip_column(:name)</code></td>
|
|
123
|
+
<td>Drop an inherited column. When nesting Show in a parent, prefer the constructor’s <code>skip_columns:</code> kwarg.</td>
|
|
124
124
|
</tr>
|
|
125
125
|
</tbody>
|
|
126
126
|
</table>
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
</div></div>
|
|
149
149
|
|
|
150
150
|
<div id="footer">
|
|
151
|
-
Generated on
|
|
151
|
+
Generated on Tue Aug 18 14:36:34 2026 by
|
|
152
152
|
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
|
153
153
|
0.9.34 (ruby-3.4.9).
|
|
154
154
|
</div>
|
data/doc/file.standalone.html
CHANGED
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
<div id="content"><div id='filecontents'>
|
|
61
61
|
<p><a href="/README_md.html#guide--documentation">Back to the guide</a></p>
|
|
62
62
|
|
|
63
|
-
<h1 id="
|
|
63
|
+
<h1 id="standalone-routing-to-components">Standalone (routing to components)</h1>
|
|
64
64
|
|
|
65
65
|
<p>As stated earlier, Compony can generate routes to your components. This is achieved by using the standalone DSL inside the setup block. The first step is calling the method <code>standalone</code> with a path. Inside this block, you will then specify which HTTP verbs (e.g. GET, PATCH etc.) the component should listen to. As soon as both are specified, Compony will generate an appropriate route.</p>
|
|
66
66
|
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
</li></ul>
|
|
107
107
|
</li></ul>
|
|
108
108
|
|
|
109
|
-
<h2 id="
|
|
109
|
+
<h2 id="exposing-multiple-paths-in-the-same-component-calling-standalone-multiple-times">Exposing multiple paths in the same component (calling standalone multiple times)</h2>
|
|
110
110
|
|
|
111
111
|
<p>If your component loads data dynamically from a JavaScript front-end (e.g. implemented via Stimulus), you will find yourself in the situation where you need an extra route for a functionality that inherently belongs to the same component. Example use cases would be search fields that load data as the user types, maps that load tiles, dynamic photo galleries etc.</p>
|
|
112
112
|
|
|
@@ -135,16 +135,16 @@
|
|
|
135
135
|
|
|
136
136
|
<p>Please note that the idea here is to package things that belong together, not to provide different kinds of content in a single component. For displaying different pages, use multiple components and have each expose a single route.</p>
|
|
137
137
|
|
|
138
|
-
<h2 id="
|
|
138
|
+
<h2 id="naming-of-exposed-routes">Naming of exposed routes</h2>
|
|
139
139
|
|
|
140
|
-
<p>The routes to standalone components are named and you can point to them using Rails’ <code>..._path</code> and <code>..._url</code> helpers. The naming scheme is: <code>[standalone]
|
|
140
|
+
<p>The routes to standalone components are named and you can point to them using Rails’ <code>..._path</code> and <code>..._url</code> helpers. The naming scheme is: <code>[standalone]_[component]_[family]_comp</code>. Examples:</p>
|
|
141
141
|
<ul><li>
|
|
142
142
|
<p>Default standalone: <code>Components::Users::Index</code> exports <code>index_users_comp</code> and thus <code>index_users_comp_path</code> can be used.</p>
|
|
143
143
|
</li><li>
|
|
144
144
|
<p>Named standalone: If <code>standalone :foo, path: ...</code> is used within <code>Components::Users::Index</code>, the exported name is <code>foo_index_users_comp</code>.</p>
|
|
145
145
|
</li></ul>
|
|
146
146
|
|
|
147
|
-
<h2 id="
|
|
147
|
+
<h2 id="handling-formats">Handling formats</h2>
|
|
148
148
|
|
|
149
149
|
<p>Compony is capable of responding to formats like Rails does. This is useful to deliver PDFs, CSV files etc. to a user from within Compony. This can be achieved by specifying the <code>respond</code> block:</p>
|
|
150
150
|
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
<span class='kw'>end</span>
|
|
166
166
|
</code></pre>
|
|
167
167
|
|
|
168
|
-
<h2 id="
|
|
168
|
+
<h2 id="redirect-in-respond-or-in-beforerender">Redirect in <code>respond</code> or in <code>before_render</code>?</h2>
|
|
169
169
|
|
|
170
170
|
<p>Rails controller redirects can be issued both in a verb DSL’s <code>respond</code> block and in <code>before_render</code>. The rule of thumb that tells you which way to go is:</p>
|
|
171
171
|
<ul><li>
|
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
<p>If you want to redirect depending on params, state, time etc. <strong>independently of the HTTP verb</strong>, use <code>before_render</code>, as this is more convenient than writing a standalone -> verb -> respond tree.</p>
|
|
175
175
|
</li></ul>
|
|
176
176
|
|
|
177
|
-
<h2 id="
|
|
177
|
+
<h2 id="path-constraints">Path constraints</h2>
|
|
178
178
|
|
|
179
179
|
<p>When calling <code>standalone</code>, you may specify the keyword <code>constraints</code> that will be passed to the route. For example:</p>
|
|
180
180
|
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
</span><span class='id identifier rubyid_get'>get</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>:lang</span><span class='tstring_end'>'</span></span><span class='comma'>,</span> <span class='label'>constraints:</span> <span class='lbrace'>{</span> <span class='label'>lang:</span> <span class='tstring'><span class='regexp_beg'>/</span><span class='tstring_content'>([a-z]{2})?</span><span class='regexp_end'>/i</span></span> <span class='rbrace'>}</span>
|
|
186
186
|
</code></pre>
|
|
187
187
|
|
|
188
|
-
<h2 id="
|
|
188
|
+
<h2 id="passing-scopes">Passing scopes</h2>
|
|
189
189
|
|
|
190
190
|
<p>When calling <code>standalone</code>, you may specify the keyword <code>scope</code> to wrap the component’s Rails route into a route scope. Additionally, you may specify a hash <code>scope_args</code>, which will be passed as keyword arguments to the <code>scope</code> call in the route:</p>
|
|
191
191
|
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
<span class='kw'>end</span>
|
|
203
203
|
</code></pre>
|
|
204
204
|
|
|
205
|
-
<h2 id="
|
|
205
|
+
<h2 id="customizing-path-generation">Customizing path generation</h2>
|
|
206
206
|
|
|
207
207
|
<p>By implementing <code>path do ... end</code> inside the <code>setup</code> method of a component, you can override the way paths to that component are generated. Customizing the path generation will affect all mentioned methods mentioned here involving paths, such as <code>Compony.path</code>, <code>render_intent</code> etc.</p>
|
|
208
208
|
|
|
@@ -212,9 +212,9 @@
|
|
|
212
212
|
|
|
213
213
|
<p>Where overriding <code>path</code> is genuinely useful:</p>
|
|
214
214
|
<ul><li>
|
|
215
|
-
<p><strong>Inject a derived/looked-up param.</strong> Callers pass a high-level argument and the block
|
|
215
|
+
<p><strong>Inject a derived/looked-up param.</strong> Callers pass a high-level argument and the block turns it into concrete path params.</p>
|
|
216
216
|
</li><li>
|
|
217
|
-
<p><strong>Mint a signed token into the URL</strong> so an unauthenticated link can authorize itself —
|
|
217
|
+
<p><strong>Mint a signed token into the URL</strong> so an unauthenticated link can authorize itself — a full worked recipe is in <a href="/doc/guide/patterns_md.html#18-signed-token-capability-links-auth-less-onboarding--magic-links">Real-world patterns §18 (signed-token capability links)</a> (magic login, password reset, invite/confirm links).</p>
|
|
218
218
|
</li><li>
|
|
219
219
|
<p><strong>Custom slugs / vanity paths</strong> that differ from the Rails route helper’s default shape.</p>
|
|
220
220
|
</li></ul>
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
</div></div>
|
|
224
224
|
|
|
225
225
|
<div id="footer">
|
|
226
|
-
Generated on
|
|
226
|
+
Generated on Tue Aug 18 14:36:33 2026 by
|
|
227
227
|
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
|
228
228
|
0.9.34 (ruby-3.4.9).
|
|
229
229
|
</div>
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
<div id="content"><div id='filecontents'>
|
|
61
61
|
<p><a href="/README_md.html#guide--documentation">Back to the guide</a></p>
|
|
62
62
|
|
|
63
|
-
<h1 id="
|
|
63
|
+
<h1 id="unleashing-virtual-models-through-componys-activetype-integration">Unleashing virtual models through Compony’s <code>ActiveType</code> integration</h1>
|
|
64
64
|
|
|
65
65
|
<p>Compony explicitely supports using virtual models using the <code>active_type</code> gem for its resourceful components. However, when doing so, your model should inherit from <code>Compony::VirtualModel</code> rather than from <code>ActiveType::Object</code>.</p>
|
|
66
66
|
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
</div></div>
|
|
108
108
|
|
|
109
109
|
<div id="footer">
|
|
110
|
-
Generated on
|
|
110
|
+
Generated on Tue Aug 18 14:36:34 2026 by
|
|
111
111
|
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
|
112
112
|
0.9.34 (ruby-3.4.9).
|
|
113
113
|
</div>
|