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
@@ -3,4 +3,67 @@
3
3
 
4
4
  # Pre-built components: Index
5
5
 
6
- This stanalone component is resourceful, holds a collection of records and corresponds to Rail's `index` controller action. It is a wrapper for the [`List` component](./list.md).
6
+ `Compony::Components::Index` is a resourceful standalone component corresponding to Rails'
7
+ `index` controller action. It holds a collection of records and is a thin wrapper that
8
+ nests the [`List`](./list.md) component of the same family.
9
+
10
+ ## What it does by default
11
+
12
+ The shipped `setup` (see `lib/compony/components/index.rb`) is deliberately minimal:
13
+
14
+ - **Route:** `standalone path: family_name` with a `verb :get` authorized by
15
+ `can?(:index, data_class)`, e.g. `/users`.
16
+ - **Label:** `label(:all) { data_class.model_name.human(count: 2) }` — e.g. "Users".
17
+ - **Data:** `load_data { @data = data_class.accessible_by(controller.current_ability) }`
18
+ — the full [CanCanCan](https://github.com/CanCanCommunity/cancancan)-scoped collection.
19
+ - **Exposed intent:** adds a `:new` intent (unless the model is
20
+ [owned](/doc/guide/ownership.md) by another).
21
+ - **Content:** `concat render_sub_comp(:list, @data)` — delegates all rendering to the
22
+ family's `List`.
23
+
24
+ So with both an `Index` and a `List` component present, a family lists out of the box:
25
+
26
+ ```ruby
27
+ class Components::Users::Index < Compony::Components::Index; end
28
+ class Components::Users::List < Compony::Components::List
29
+ setup { columns :name, :email }
30
+ end
31
+ ```
32
+
33
+ ## Typical overrides
34
+
35
+ Narrow or order the collection:
36
+
37
+ ```ruby
38
+ class Components::Users::Index < Compony::Components::Index
39
+ setup do
40
+ load_data { @data = User.accessible_by(current_ability).active.order(:name) }
41
+ end
42
+ end
43
+ ```
44
+
45
+ Customize the action toolbar via [exposed intents](/doc/guide/intents.md#exposed-intents):
46
+
47
+ ```ruby
48
+ setup do
49
+ exposed_intents do
50
+ add :index, :users, label: 'CSV', name: :csv, path: { format: :csv }
51
+ add :import, :users, method: :post, before: :new
52
+ end
53
+ end
54
+ ```
55
+
56
+ Add a second standalone route serving an alternative `List`:
57
+
58
+ ```ruby
59
+ setup do
60
+ standalone path: 'users/archived'
61
+ content :main, hidden: true do
62
+ concat render_sub_comp(:list, @data.archived)
63
+ end
64
+ end
65
+ ```
66
+
67
+ In practice apps put the layout/markup chrome in a `BaseComponents::Index` and inherit
68
+ from that — see [Real-world patterns](../patterns.md#3-index--load_data-scope--nested-list).
69
+ For column/filter/sort configuration, see [`List`](./list.md).
@@ -3,12 +3,116 @@
3
3
 
4
4
  # Pre-built components: List
5
5
 
6
- This resourceful component displays a table / list of records. It is meant to be nested within another component, typically [`Index`](./index.md) of the same family or [`Show`](./show.md) of another family. Compony's implementation of this component features:
6
+ `Compony::Components::List` is a resourceful component that renders a table/list of
7
+ records. It is **not** standalone — it is meant to be nested, typically inside
8
+ [`Index`](./index.md) of the same family or [`Show`](./show.md) of an owning family, via
9
+ `render_sub_comp(:list, collection)`.
7
10
 
8
- - Inferrence of rows from model fields as well as custom rows
9
- - Row actions for each displayed record
10
- - Pagination
11
- - Sorting: if the Ransack gem is installed and at least one sorting column has been specified, the component can automatically generate a select input for sorting as well as sorting links.
12
- - Filtering / Searching: if the Ransack gem is installed and at least one filter has been specified, the component can automatically generate a filter / search form that works with Ransack.
11
+ Features: field-inferred or custom columns, per-row intents, pagination, and when the
12
+ [Ransack](https://github.com/activerecord-hackery/ransack) gem is present and at least one
13
+ sort/filter is declared — sorting links, a sort select, and a filter/search form.
13
14
 
14
- This component serves as a base block for building powerful management interfaces. Consult the component's class to learn about the various methods you can use in `setup` in order to customize the behavior. You will likely want to implement your own custom base component based on this component and overwrite the `content` blocks that you would like to customize.
15
+ ## Column DSL
16
+
17
+ | Method | Signature | Description |
18
+ | --- | --- | --- |
19
+ | `column` | `column(:name, label: nil, class: nil, link_opts: {}) { \|record\| ... }` | Add/define a column. No block → model-field column (auto label, value via `value_for`, only if `:index` permitted). Block is instance-exec'd per row and renders the cell. |
20
+ | `columns` | `columns(:a, :b, as_title: false, **kw)` | Bulk `column`. `as_title: true` marks title columns (shown as the card heading in mobile/card layouts). |
21
+ | `skip_column` | `skip_column(:name)` | Hide a (possibly inherited) column. |
22
+
23
+ ```ruby
24
+ class Components::Orders::List < Compony::Components::List
25
+ setup do
26
+ columns :number, :customer, as_title: true
27
+ columns :total, :created_at
28
+ column :status, class: 'text-end' do |order|
29
+ span order.status.label, class: "badge bg-#{order.status.key}"
30
+ end
31
+ end
32
+ end
33
+ ```
34
+
35
+ ## Filtering & sorting (Ransack)
36
+
37
+ | Method | Signature | Description |
38
+ | --- | --- | --- |
39
+ | `filter` | `filter(:name, label: nil) { \|f\| ... }` | Add a filter. No block → field filter or a Ransack predicate string (e.g. `:id_eq`). Block gets the Ransack search form and renders label + input. |
40
+ | `filters` | `filters(:a, :b, **kw)` | Bulk `filter`. |
41
+ | `sort` | `sort(:name, label: nil)` | Add a sort criterion (must be Ransack-sortable). Generates one sort link + asc/desc entries. |
42
+ | `sorts` | `sorts(:a, :b)` | Bulk `sort`. |
43
+ | `default_sorting` | `default_sorting('id desc')` | Default Ransack sort applied when none chosen. |
44
+
45
+ ```ruby
46
+ setup do
47
+ filters :number, :status
48
+ filter :overdue, label: 'Overdue' do |f|
49
+ concat f.check_box(:overdue_eq, {}, true, false)
50
+ end
51
+ sorts :number, :created_at
52
+ default_sorting 'created_at desc'
53
+ end
54
+ ```
55
+
56
+ ## Per-row intents
57
+
58
+ `row_intents` opens the [intent management DSL](/doc/guide/intents.md#exposed-intents)
59
+ (`add`/`remove`, `before:`) applied to each row's record:
60
+
61
+ ```ruby
62
+ setup do
63
+ row_intents do
64
+ remove :destroy
65
+ add :archive, ->(record) { record }, method: :patch, before: :edit
66
+ end
67
+ end
68
+ ```
69
+
70
+ ## Toggles, paging, styling
71
+
72
+ All have matching constructor kwargs so a nesting parent can override per render
73
+ (`render_sub_comp(:list, coll, skip_pagination: true, skip_columns: [:order])`).
74
+
75
+ | DSL | Default | Purpose |
76
+ | --- | --- | --- |
77
+ | `pagination(bool)` | on | Enable/disable paging (off loads all rows). |
78
+ | `results_per_page(n)` | 20 | Rows per page. |
79
+ | `filtering(bool)` | on | Enable/disable the filter form. |
80
+ | `sorting(bool)` / `sorting_in_filter(bool)` / `sorting_links(bool)` | on | Toggle sort UIs. |
81
+ | `filter_label_class` / `filter_input_class` / `filter_select_class` / `filter_item_wrapper_class` | — | CSS classes for filter form elements. |
82
+
83
+ Constructor `skip_*` kwargs: `skip_pagination`, `skip_filtering`, `skip_sorting`,
84
+ `skip_sorting_in_filter`, `skip_sorting_links`, `skip_columns:`, `skip_row_intents:`,
85
+ `skip_filters:`, `results_per_page:`, `default_sorting:`.
86
+
87
+ ## Customizing rendering
88
+
89
+ `List` exposes named `content` blocks (`:data`, `:filter`, `:pagination`, `:sorting_links`,
90
+ …) that you override — almost always once, in an app `BaseComponents::List`, to fit your UI
91
+ framework, then inherited everywhere:
92
+
93
+ ```ruby
94
+ module BaseComponents
95
+ class List < Compony::Components::List
96
+ setup do
97
+ filter_input_class 'form-control'
98
+ content :filter, hidden: true do
99
+ # Bootstrap-styled filter form wrapper
100
+ end
101
+ content :data, hidden: true do
102
+ # Bootstrap table / responsive cards
103
+ end
104
+ end
105
+ end
106
+ end
107
+ ```
108
+
109
+ Embedding a child list inside a Show, dropping the FK column and preserving the active tab
110
+ across filter submits:
111
+
112
+ ```ruby
113
+ concat render_sub_comp(:list, @data.line_items,
114
+ skip_columns: [:order],
115
+ params_in_filter: [param_name('tab')])
116
+ ```
117
+
118
+ See [Real-world patterns](../patterns.md#4-list-customization) for the recurring app setup.
@@ -3,6 +3,61 @@
3
3
 
4
4
  # Pre-built components: Show
5
5
 
6
- This resourceful component corresponds to a typical Rails controller's `show` action and presents `@data` which is typically a model instance.
6
+ `Compony::Components::Show` is a resourceful standalone component corresponding to a Rails
7
+ `show` action. It loads `@data` by `:id` and presents its fields.
7
8
 
8
- To use it, create a component of the style `Components::Users::Show` and inherit from `Compony::Components::Show`. By default, this will display all permitted fields along with their labels. Consult the component's class to learn about the methods you can use in `setup` in order to customize the behavior.
9
+ ## What it does by default
10
+
11
+ From `lib/compony/components/show.rb`:
12
+
13
+ - **Route:** `standalone path: "/#{family_name}/:id"` with an `:id` constraint that accepts
14
+ integer **or** UUID ids, `verb :get` authorized by `can?(:show, @data)`.
15
+ - **Labels:** long = `data.label`; short = a generic translated "Show".
16
+ - **Exposed intents:** `:edit` and `:destroy` for `@data`, plus a `:back_to_owner` intent
17
+ if the model is [owned](/doc/guide/ownership.md).
18
+ - **Content blocks:**
19
+ - `:label` → `h2 component.label`
20
+ - `:main` → renders the `:data` block (override `:main` to wrap `:data` in a card etc.)
21
+ - `:data` (hidden) → if no columns were declared, calls `all_field_columns(@data)`, then
22
+ renders a two-column table of label/value per permitted field.
23
+
24
+ ```ruby
25
+ class Components::Users::Show < Compony::Components::Show; end # fully functional
26
+ ```
27
+
28
+ ## Column DSL
29
+
30
+ `Show` shares a `column`/`columns` DSL with [`List`](./list.md) (here "column" means an
31
+ attribute row, since Show renders one record).
32
+
33
+ | Method | Signature | Description |
34
+ | --- | --- | --- |
35
+ | `column` | `column(:name, label: nil, class: nil, link_opts: {}, link_to_component: :show) { \|record\| ... }` | Add/define one attribute row. Without a block, treated as a model field; the block (instance-exec'd per record) supplies the value. |
36
+ | `columns` | `columns(:a, :b, **shared_kwargs)` | Bulk `column`. |
37
+ | `all_field_columns` | `all_field_columns(@data)` | Add a column for every model field (the default when none declared). |
38
+ | `skip_column` | `skip_column(:name)` | Drop an inherited column. When nesting Show in a parent, prefer the constructor's `skip_columns:` kwarg. |
39
+
40
+ Field columns are only rendered if the current ability permits `:show` on that attribute;
41
+ a `nil` value hides the row.
42
+
43
+ ```ruby
44
+ class Components::Users::Show < Compony::Components::Show
45
+ setup do
46
+ columns :name, :email, :created_at
47
+ column :status do |user| # custom computed row
48
+ user.active? ? 'Active' : 'Disabled'
49
+ end
50
+ skip_column :created_at # if inherited and unwanted
51
+
52
+ content do # wrap :data in app chrome
53
+ div class: 'card card-body' do
54
+ content :data
55
+ end
56
+ end
57
+ end
58
+ end
59
+ ```
60
+
61
+ UUID/string ids work out of the box thanks to the route constraint. For nesting a Show (or
62
+ its `:data`) inside another component see [Nesting](/doc/guide/nesting.md); for the app
63
+ base-layer approach see [Real-world patterns](../patterns.md#1-the-app-base-component-layer).
@@ -3,16 +3,63 @@
3
3
 
4
4
  # Pre-built components: WithForm
5
5
 
6
- `Compony::Components::WithForm` is an abstract base class for components that render a form. Those components can further be resourceful, but don't have to be. If a component inherits from WithForm, it is always twinned with another component that will provide the form.
6
+ `Compony::Components::WithForm` is the abstract base for components that render and submit
7
+ a form. It is **twinned** with a [`Form`](./form.md) component: WithForm provides the
8
+ route, authorization and resource handling; the Form provides the inputs and param schema.
9
+ [`New`](./new.md) and [`Edit`](./edit.md) both inherit from WithForm — you rarely subclass
10
+ it directly, but understanding the twinning explains how they work.
7
11
 
8
- WithForm adds the following DSL methods:
12
+ A WithForm component may be resourceful (New/Edit are) but does not have to be.
9
13
 
10
- - `form_comp_class` sets the class that will be instantiated by `form_comp`
11
- - `form_comp` returns an instance of the Form component twinned with this component. If `form_comp_class` was never set, it will default to loading the component named `Form` in the same family as this component.
12
- - `submit_verb` takes a symbol containing a verb, e.g. `:patch`. It defines this component's standalone verb that should be called when the twinned Form component is submitted.
13
- - `submit_path` defaults to this component's standalone path. You can override this to submit the form to another component, should you need it.
14
+ ## How the twinning works
14
15
 
15
- The following other pre-built components implement `WithForm`:
16
+ - `form_comp` returns the Form instance, built lazily. It defaults to the component named
17
+ `Form` in the **same family**, instantiated with this component as `parent_comp` and
18
+ passed `submit_verb`, `submit_path` and `cancancan_action`.
19
+ - The Form renders inside this component's `content` (e.g. New/Edit do
20
+ `concat form_comp.render(controller, data: @data)`).
21
+ - The form's `<form>` posts back to `submit_path` using `submit_verb`; that same component
22
+ handles the submit verb (POST for New, PATCH for Edit) and runs the resourceful
23
+ lifecycle (`assign_attributes` → `store_data` → `respond`).
16
24
 
17
- - [`New`](new.md)
18
- - [`Edit`](edit.md)
25
+ ## DSL methods
26
+
27
+ | Method | Signature | Description |
28
+ | --- | --- | --- |
29
+ | `submit_verb` | `submit_verb(:patch)` | HTTP verb the twinned form submits with. Mandatory (New sets `:post`, Edit `:patch`). |
30
+ | `form_comp_class` | `form_comp_class(Components::Users::SignupForm)` | Use a specific Form class instead of the same-family `Form`. |
31
+ | `submit_path` | `submit_path { Compony.path(:create, :users) }` | Override where the form submits. Block is given the controller; defaults to this component's own path. |
32
+ | `form_cancancan_action` | `form_cancancan_action(:edit)` | CanCanCan action used by the Form for per-field `permitted_attributes` (New sets `:new`, Edit `:edit`). Pass `nil` to disable per-field auth. |
33
+ | `form_comp` | `form_comp` | (Not DSL — a reader.) The Form instance; override in a subclass for full control. |
34
+
35
+ ## Example: a custom non-default form
36
+
37
+ ```ruby
38
+ class Components::Users::Signup < Compony::Components::New
39
+ setup do
40
+ standalone path: 'signup' do
41
+ skip_authentication!
42
+ verb :get do authorize { true } end
43
+ verb :post do authorize { true } end
44
+ end
45
+ form_comp_class Components::Users::SignupForm # not the default Users::Form
46
+ on_created_redirect_path { Compony.path(:show, @data) }
47
+ end
48
+ end
49
+
50
+ class Components::Users::SignupForm < Compony::Components::Form
51
+ setup do
52
+ form_fields do
53
+ concat field(:email)
54
+ concat pw_field(:password)
55
+ end
56
+ schema_field :email
57
+ schema_pw_field :password
58
+ end
59
+ end
60
+ ```
61
+
62
+ For the full submit/redirect behavior see [`New`](./new.md) and [`Edit`](./edit.md); for
63
+ the form input/schema DSL see [`Form`](./form.md); for the lifecycle hooks see
64
+ [Resourceful](/doc/guide/resourceful.md). Multi-step and clone flows that reuse this
65
+ machinery are in [Real-world patterns](../patterns.md#11-non-crud-job-dispatch-toggles-clone).
@@ -4,7 +4,12 @@
4
4
 
5
5
  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 [provided Rails generators](./generators.md) `rails g component ...`.
6
6
 
7
- The pre-built components can be found in the module `Compony::Components`. As you can see, there is no Show and no Index component. The reason is that these will depend a lot on your application's UI framework (e.g. Bootstrap) and thus the benefits a UI-agnostic base component can provide are minimal. Additionally, these components are very easy to implement, as is illustrated in the example at the beginning of this documentation.
7
+ The pre-built components can be found in the module `Compony::Components`. They ship a
8
+ sensible, UI-agnostic default (plain HTML, no styling) — you typically inherit from them
9
+ in an app base layer that adds your UI framework's markup (see
10
+ [Real-world patterns](./patterns.md#1-the-app-base-component-layer)). `Show` and `Index`
11
+ are intentionally minimal because their presentation depends heavily on your UI framework;
12
+ they are easy to override (see the [example](./example.md)).
8
13
 
9
14
  In the following, the pre-built components currently shipped with Compony are presented:
10
15
 
@@ -15,6 +20,6 @@ In the following, the pre-built components currently shipped with Compony are pr
15
20
  - [WithForm](./pre_built_components/with_form.md): A base class for components containing and submitting forms
16
21
  - [Form](./pre_built_components/form.md): Compony's equivalent to Rail's `_form` partial
17
22
  - [New](./pre_built_components/new.md): Compony's equivalent to Rail's `new` and `create` controller action
18
- - [Edit](./pre_built_components/new.md): Compony's equivalent to Rail's `edit` and `update` controller action
23
+ - [Edit](./pre_built_components/edit.md): Compony's equivalent to Rail's `edit` and `update` controller action
19
24
 
20
25
  [Guide index](/README.md#guide--documentation)
@@ -139,6 +139,21 @@ end
139
139
 
140
140
  By implementing `path do ... end` inside the `setup` 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 `Compony.path`, `render_intent` etc.
141
141
 
142
- This is an advanced usage. Refer to the default implementation of `Component`'s `path_block` to see an example.
142
+ The block runs **outside** the request context, so build URLs via
143
+ `Rails.application.routes.url_helpers` (not `controller`/`helpers`). It is given an optional
144
+ model, positional path-helper args, the `standalone_name:` kwarg, and any extra kwargs.
145
+
146
+ This is an advanced usage. Refer to the default implementation of `Component`'s `path_block`
147
+ to see the baseline example.
148
+
149
+ Where overriding `path` is genuinely useful:
150
+
151
+ - **Inject a derived/looked-up param.** Callers pass a high-level argument and the block
152
+ turns it into concrete path params.
153
+ - **Mint a signed token into the URL** so an unauthenticated link can authorize itself —
154
+ a full worked recipe is in
155
+ [Real-world patterns §18 (signed-token capability links)](/doc/guide/patterns.md#18-signed-token-capability-links-auth-less-onboarding--magic-links)
156
+ (magic login, password reset, invite/confirm links).
157
+ - **Custom slugs / vanity paths** that differ from the Rails route helper's default shape.
143
158
 
144
159
  [Guide index](/README.md#guide--documentation)
data/doc/index.html CHANGED
@@ -90,7 +90,7 @@
90
90
  </li><li>
91
91
  <p>Compony seamlessly integrates with Rails and does not interfere with existing code. Using Compony, you <strong>can</strong> write your application as components, but it is still possible to have regular routes, controllers and views side-to-side to it. This way, you can migrate your applications to Compony little by little and enter and leave the Compony world as you please. It is also possible to render Compony components from regular views and vice versa.</p>
92
92
  </li><li>
93
- <p>Compony is built for Rails 7, 7.1 and 8, and fully supports Stimulus and Turbo Drive. It is also compatible Turbo Frames and Streams, but there are not many helpers specifically targetting theme at this point..</p>
93
+ <p>Compony requires Rails &gt;= 7.2.1 and Ruby &gt;= 3.3.5 (see <a href="./doc/integrations_md.html">doc/integrations.md</a> for all dependencies), and fully supports Stimulus and Turbo Drive. It is also compatible Turbo Frames and Streams, but there are not many helpers specifically targetting theme at this point..</p>
94
94
  </li><li>
95
95
  <p>Compony uses <a href="https://github.com/CanCanCommunity/cancancan">CanCanCan</a> for authorization but does not provide an authentication mechanism. You can easily build your own by creating login/logout components that manage cookies, and configure Compony to enforce authentication using the <code>Compony.authentication_before_action</code> setter. I have also successfully tested Compony to work with <a href="https://github.com/heartcombo/devise">Devise</a>.</p>
96
96
  </li></ul>
@@ -109,8 +109,29 @@
109
109
  <ul><li>
110
110
  <p><a href="./doc/guide/example_md.html">Self-contained example</a> for those who like to dive straight into code</p>
111
111
  </li><li>
112
+ <p><a href="./doc/guide/example_advanced_md.html">Advanced example</a>: custom form, feasibility, CSV export, virtual-model launch form</p>
113
+ </li><li>
112
114
  <p><a href="./doc/guide/installation_md.html">Installation</a> (start here)</p>
113
115
  </li><li>
116
+ <p>Quick reference (also handy for AI coding assistants):</p>
117
+ <ul><li>
118
+ <p><a href="./doc/guide/dsl_reference_md.html">DSL reference</a>: every DSL method with signature and context</p>
119
+ </li><li>
120
+ <p><a href="./doc/guide/glossary_md.html">Glossary</a>: one-line definitions of Compony vocabulary</p>
121
+ </li><li>
122
+ <p><a href="./doc/guide/gotchas_md.html">Gotchas</a>: known anti-patterns with symptom, cause, fix</p>
123
+ </li><li>
124
+ <p><a href="./doc/guide/patterns_md.html">Real-world patterns</a>: idioms distilled from production apps</p>
125
+ </li><li>
126
+ <p><a href="./doc/guide/cookbook_md.html">Cookbook</a>: recipes indexed by task (“I want to do X”)</p>
127
+ </li><li>
128
+ <p><a href="./doc/integrations_md.html">Integrations</a>: companion gems — required, optional, app-side</p>
129
+ </li><li>
130
+ <p><a href="./CLAUDE_md.html">Agent primer</a> and <a href="./doc/llms.txt">doc/llms.txt</a>: orientation for LLM-based tools</p>
131
+ </li><li>
132
+ <p><a href="./doc/guide/maintaining_md.html">Maintaining</a>: release &amp; docs policy (for gem contributors)</p>
133
+ </li></ul>
134
+ </li><li>
114
135
  <p>Concepts and usage:</p>
115
136
  <ul><li>
116
137
  <p><a href="./doc/guide/basic_component_md.html">A basic component</a>: Basic concepts relevant for all components</p>
@@ -135,7 +156,7 @@
135
156
  </li><li>
136
157
  <p><a href="./doc/guide/internal_datastructures_md.html">Internal datastructures</a>: Noteworthy datastructures provided by Compony</p>
137
158
  </li><li>
138
- <p><a href="./doc/guide/internal_datastructures_md.html">Virtual models</a>: Unleashing non-persistent interactions through Compony’s <code>ActiveType</code> integration</p>
159
+ <p><a href="./doc/guide/virtual_models_md.html">Virtual models</a>: Unleashing non-persistent interactions through Compony’s <code>ActiveType</code> integration</p>
139
160
  </li></ul>
140
161
  </li><li>
141
162
  <p>Pre-built components shipped with Compony</p>
@@ -156,7 +177,7 @@
156
177
  </li><li>
157
178
  <p><a href="./doc/guide/pre_built_components/new_md.html">New</a>: Compony’s equivalent to Rail’s <code>new</code> and <code>create</code> controller action</p>
158
179
  </li><li>
159
- <p><a href="./doc/guide/pre_built_components/new_md.html">Edit</a>: Compony’s equivalent to Rail’s <code>edit</code> and <code>update</code> controller action</p>
180
+ <p><a href="./doc/guide/pre_built_components/edit_md.html">Edit</a>: Compony’s equivalent to Rail’s <code>edit</code> and <code>update</code> controller action</p>
160
181
  </li></ul>
161
182
  </li></ul>
162
183
 
@@ -199,7 +220,7 @@
199
220
  </div></div>
200
221
 
201
222
  <div id="footer">
202
- Generated on Fri May 15 10:29:33 2026 by
223
+ Generated on Mon May 18 13:55:32 2026 by
203
224
  <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
204
225
  0.9.34 (ruby-3.3.5).
205
226
  </div>
@@ -0,0 +1,61 @@
1
+ [Back to the guide](/README.md#guide--documentation)
2
+
3
+ # Integrations & companion gems
4
+
5
+ What Compony pulls in, what it optionally lights up, and what you are expected to add
6
+ yourself. The authoritative source for hard dependencies and their version constraints is
7
+ the `:gemspec` task in [`Rakefile`](/Rakefile) (it generates `compony.gemspec`); the table
8
+ below mirrors it and must be updated together with it (see
9
+ [maintaining.md](/doc/guide/maintaining.md)).
10
+
11
+ ## Hard runtime dependencies (installed automatically)
12
+
13
+ | Gem | Constraint | Why Compony needs it |
14
+ | --- | --- | --- |
15
+ | `rails` | `>= 7.2.1` | Routing, controllers, views Compony plugs into. |
16
+ | `request_store` | `>= 1.7` | Per-request storage (e.g. `Compony.root_comp`). |
17
+ | `dyny` | `>= 0.0.3` | HTML-as-Ruby templating used in `content` blocks. |
18
+ | `schemacop` | `>= 3.0.17` | Strong-param schema validation behind `schema_field` / `schema_line`. |
19
+ | `simple_form` | `>= 5.3.1` | The form builder behind the Form component's `field`. |
20
+ | `dslblend` | `>= 0.0.3` | Powers the `RequestContext` multi-provider DSL. |
21
+ | `anchormodel` | `>= 0.3.0` | `anchormodel` model-field type + enum-like associations. |
22
+ | `cancancan` | `~> 3.6.1` | Authorization: `authorize { can?(...) }`, `accessible_by`, per-field `permitted_attributes`. |
23
+
24
+ The Ruby floor is `>= 3.3.5` (`required_ruby_version`).
25
+
26
+ > Version note: the gemspec requires `rails >= 7.2.1`. If the README's prose mentions an
27
+ > older range, the gemspec wins — keep them in sync when bumping (see maintaining.md).
28
+
29
+ ## Optional — presence unlocks a feature
30
+
31
+ These are **not** declared dependencies; Compony detects them and enables behavior if they
32
+ are in the host app's bundle.
33
+
34
+ | Gem | Unlocks |
35
+ | --- | --- |
36
+ | `active_type` | `Compony::VirtualModel` (loaded only if `ActiveType::Object` is defined). Non-persistent / upload / wizard forms — [patterns §12](/doc/guide/patterns.md#12-virtual-model-for-non-persistent--upload-forms), [virtual_models.md](/doc/guide/virtual_models.md). |
37
+ | `ransack` | List filtering, sorting links and the sort select in [`List`](/doc/guide/pre_built_components/list.md). Without it, declare no `filter`/`sort` and those UIs stay off. |
38
+
39
+ ## App-side companions (you add these)
40
+
41
+ Compony is UI- and auth-agnostic; these are conventional choices in real apps, pulled in
42
+ by your app, not by Compony.
43
+
44
+ | Concern | Typical choice | Used by |
45
+ | --- | --- | --- |
46
+ | Authentication | Devise, or a custom login component + `Compony.authentication_before_action=` | Compony ships **no** auth ([README](/README.md)); token-link flows → [patterns §18](/doc/guide/patterns.md#18-signed-token-capability-links-auth-less-onboarding--magic-links) |
47
+ | Turbo / Stimulus | `turbo-rails`, `stimulus-rails` | Compony fully supports Turbo Drive; inline-edit → [patterns §15](/doc/guide/patterns.md#15-inline-edit-card-with-a-turbo-frame), ajax PATCH → [patterns §17](/doc/guide/patterns.md#17-inline-patch-without-a-form-reorder--quick-toggle) |
48
+ | Select / date inputs | TomSelect, Flatpickr (as registered `simple_form` inputs) | `field(:x, as: :tom_select/:flatpickr_*)` — [patterns §5–6](/doc/guide/patterns.md#5-custom-form--schemacop-kept-in-sync) |
49
+ | i18n | Rails I18n and/or FastGettext | Component labels; gem ships `config/locales/{de,en,fr}.yml` |
50
+ | File handling | ActiveStorage (+ a variant/processor) | Attachment fields, virtual-model uploads — [patterns §12](/doc/guide/patterns.md#12-virtual-model-for-non-persistent--upload-forms) |
51
+ | PDF / CSV | Prawn / wicked_pdf, Ruby `CSV` | Export endpoints — [patterns §10](/doc/guide/patterns.md#10-csv--pdf-via-respond-format) |
52
+ | Signed tokens | `jwt` | Capability links — [patterns §18](/doc/guide/patterns.md#18-signed-token-capability-links-auth-less-onboarding--magic-links) |
53
+
54
+ Incompatibility: `tailwindcss-rails` purges Compony component CSS (component HTML is not in
55
+ `app/views`); see [gotchas.md #13](/doc/guide/gotchas.md#13-tailwindcss-rails-purges-compony-styles).
56
+
57
+ ## Development dependencies
58
+
59
+ `yard >= 0.9.28` (docs), `rubocop >= 1.48`, `rubocop-rails >= 2.18.0` (lint).
60
+
61
+ [Guide index](/README.md#guide--documentation)
data/doc/llms.txt ADDED
@@ -0,0 +1,62 @@
1
+ # Compony
2
+
3
+ > Compony is a Ruby on Rails gem for writing apps in component style: one Ruby class
4
+ > bundles a route, a controller action, and a view (Dyny). Components are subclassable,
5
+ > giving inheritance for views and controller logic, DRY CRUD via pre-built components,
6
+ > a model mixin for fields and feasibility, and an intent system for linking components.
7
+
8
+ Read CLAUDE.md first for the mental model and the public API surface. Source of truth for
9
+ behavior is the Ruby source under lib/compony/; the guide pages below explain intent.
10
+
11
+ ## Agent entrypoints
12
+
13
+ - [Agent primer](/CLAUDE.md): mental model, public API table, source layout, conventions.
14
+ - [DSL reference](/doc/guide/dsl_reference.md): every setup/standalone/form/list DSL method with signature and context.
15
+ - [Glossary](/doc/guide/glossary.md): one-line definitions of Compony vocabulary.
16
+ - [Gotchas](/doc/guide/gotchas.md): numbered anti-patterns — symptom, cause, fix.
17
+ - [Real-world patterns](/doc/guide/patterns.md): 18 recurring idioms from production apps (base layer, thin leaves, tabs, exports, jobs, virtual models, webhooks, turbo-frame inline edit, multi-step wizard, inline PATCH, signed-token capability links).
18
+ - [Cookbook](/doc/guide/cookbook.md): pure task→pointer index ("I want to do X") into patterns/guide; no duplicated content.
19
+ - [Integrations](/doc/integrations.md): companion gems — hard runtime deps (versions), optional feature-unlocking gems, app-side companions.
20
+ - [Maintaining](/doc/guide/maintaining.md): contributor/AI policy — CHANGELOG per change, release steps, .yardopts, dependency + anonymization rules.
21
+
22
+ ## Guide — concepts
23
+
24
+ - [README](/README.md): what Compony is, key aspects, caveats, guide index.
25
+ - [Self-contained example](/doc/guide/example.md): full small app (model, Show, Destroy, New, Form, Edit, Index).
26
+ - [Advanced example](/doc/guide/example_advanced.md): custom form, virtual fields, CSV export, exposed intents, feasibility, before_render guard.
27
+ - [Installation](/doc/guide/installation.md): setup steps.
28
+ - [Basic component](/doc/guide/basic_component.md): naming, setup, labelling, content blocks, before_render.
29
+ - [Standalone](/doc/guide/standalone.md): routing, verbs, authorize, respond, formats, scopes, constraints.
30
+ - [Inheritance](/doc/guide/inheritance.md): DRYing via base components.
31
+ - [Nesting](/doc/guide/nesting.md): sub_comp, embedding components.
32
+ - [Resourceful](/doc/guide/resourceful.md): @data auto-loading, load_data.
33
+ - [Intents](/doc/guide/intents.md): Compony.path, render_intent, render_sub_comp, buttons/styles, exposed_intents.
34
+ - [Feasibility](/doc/guide/feasibility.md): prevent, feasible?, disabling buttons.
35
+ - [Ownership](/doc/guide/ownership.md): owned_by, owner-aware redirects.
36
+ - [Model fields](/doc/guide/model_fields.md): field types, value_for, auto-generated UI.
37
+ - [Generators](/doc/guide/generators.md): rails g component / components.
38
+ - [Internal datastructures](/doc/guide/internal_datastructures.md): RequestContext, MethodAccessibleHash.
39
+ - [Virtual models](/doc/guide/virtual_models.md): non-persistent ActiveType-backed models.
40
+
41
+ ## Guide — pre-built components
42
+
43
+ - [Introduction](/doc/guide/pre_built_components.md): overview of shipped CRUD components.
44
+ - [Show](/doc/guide/pre_built_components/show.md): Rails show equivalent.
45
+ - [Index](/doc/guide/pre_built_components/index.md): Rails index equivalent.
46
+ - [List](/doc/guide/pre_built_components/list.md): Rails _list partial equivalent (columns, filters, sorts, pagination).
47
+ - [Destroy](/doc/guide/pre_built_components/destroy.md): Rails destroy equivalent.
48
+ - [WithForm](/doc/guide/pre_built_components/with_form.md): base for form-submitting components.
49
+ - [Form](/doc/guide/pre_built_components/form.md): Rails _form partial equivalent (form_fields, schema_*).
50
+ - [New](/doc/guide/pre_built_components/new.md): Rails new+create equivalent.
51
+ - [Edit](/doc/guide/pre_built_components/edit.md): Rails edit+update equivalent.
52
+
53
+ ## Source (behavior source of truth)
54
+
55
+ - lib/compony.rb: Compony.* module methods (public API).
56
+ - lib/compony/component.rb: base Component DSL (setup, content, before_render, exposed_intents).
57
+ - lib/compony/intent.rb: Intent — path, label, feasibility, button rendering.
58
+ - lib/compony/component_mixins/resourceful.rb: @data lifecycle hooks.
59
+ - lib/compony/component_mixins/default/standalone/: routing DSL (standalone/verb/respond/authorize).
60
+ - lib/compony/components/: pre-built component implementations.
61
+ - lib/compony/model_mixin.rb: model-side field/prevent/owned_by.
62
+ - lib/compony/virtual_model.rb: VirtualModel base.
@@ -102,7 +102,7 @@
102
102
  </div>
103
103
 
104
104
  <div id="footer">
105
- Generated on Fri May 15 10:29:33 2026 by
105
+ Generated on Mon May 18 13:55:34 2026 by
106
106
  <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
107
107
  0.9.34 (ruby-3.3.5).
108
108
  </div>
@@ -123,7 +123,12 @@ module Compony
123
123
  # Overrides how the path to this component should be generated.
124
124
  # The block will be given the following args: a model (optional), pos. args for the path helper, the kwarg `standalone_name` and kwargs for the path helper.
125
125
  # The block is expected to return a Rails path. It is not given `controller` or `helpers`, instead use: `Rails.application.routes.url_helpers`.
126
- # For an example, refer to the initializer of this class, where the default block is defined.
126
+ # For the default block, refer to the initializer of this class.
127
+ # Useful when callers should pass a higher-level argument that is translated into path params here, e.g. minting a
128
+ # signed token into the URL so an unauthenticated link can authorize itself. Worked examples:
129
+ # see `doc/guide/standalone.md` ("Customizing path generation") and `doc/guide/patterns.md` §18 (signed-token capability links).
130
+ # @return [void] when defining (block given); otherwise the generated Rails path String.
131
+ # @api public
127
132
  def path(*, **, &block)
128
133
  if block_given?
129
134
  # Assignment via DSL
@@ -214,8 +219,8 @@ module Compony
214
219
  end
215
220
 
216
221
  # DSL method
217
- # If a block is given: Enters the DSL where exposed intents can be added or removed (use from {Component#setup} within the component).
218
- # If no block is given: Builds the declared intents and returns them (use from a {RequestContest} outside the component).
222
+ # If a block is given: Enters the DSL where exposed intents can be added or removed (use from {Compony::Component.setup} within the component).
223
+ # If no block is given: Builds the declared intents and returns them (use from a {Compony::RequestContext} outside the component).
219
224
  def exposed_intents(&block)
220
225
  if block_given?
221
226
  # Enter DSL