compony 0.7.1 → 0.8.0
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/CHANGELOG.md +34 -0
- data/Gemfile.lock +1 -1
- data/README.md +10 -14
- data/VERSION +1 -1
- data/compony.gemspec +4 -4
- data/doc/ComponentGenerator.html +1 -1
- data/doc/Components.html +1 -1
- data/doc/ComponentsGenerator.html +1 -1
- data/doc/Compony/Component.html +193 -457
- 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 +3 -3
- data/doc/Compony/ComponentMixins/Default/Standalone/VerbDsl.html +1 -1
- data/doc/Compony/ComponentMixins/Default/Standalone.html +187 -1
- data/doc/Compony/ComponentMixins/Default.html +1 -1
- data/doc/Compony/ComponentMixins/Resourceful.html +2 -2
- data/doc/Compony/ComponentMixins.html +1 -1
- data/doc/Compony/Components/Button.html +2 -2
- data/doc/Compony/Components/Buttons/CssButton.html +282 -0
- data/doc/Compony/Components/Buttons/Link.html +252 -0
- data/doc/Compony/Components/Buttons.html +126 -0
- data/doc/Compony/Components/Destroy.html +11 -11
- data/doc/Compony/Components/Edit.html +14 -14
- data/doc/Compony/Components/Form.html +100 -100
- data/doc/Compony/Components/Index.html +2 -2
- data/doc/Compony/Components/List.html +3 -3
- data/doc/Compony/Components/New.html +2 -2
- data/doc/Compony/Components/Show.html +24 -24
- data/doc/Compony/Components/WithForm.html +3 -3
- data/doc/Compony/Components.html +5 -3
- data/doc/Compony/ControllerMixin.html +2 -2
- data/doc/Compony/Engine.html +1 -1
- data/doc/Compony/ExposedIntentsDsl.html +403 -0
- data/doc/Compony/Intent.html +1503 -0
- data/doc/Compony/MethodAccessibleHash.html +1 -1
- data/doc/Compony/ModelFields/Anchormodel.html +1 -1
- data/doc/Compony/ModelFields/Association.html +2 -2
- data/doc/Compony/ModelFields/Attachment.html +1 -1
- data/doc/Compony/ModelFields/Base.html +1 -1
- data/doc/Compony/ModelFields/Boolean.html +1 -1
- data/doc/Compony/ModelFields/Color.html +1 -1
- data/doc/Compony/ModelFields/Currency.html +1 -1
- data/doc/Compony/ModelFields/Date.html +1 -1
- data/doc/Compony/ModelFields/Datetime.html +1 -1
- data/doc/Compony/ModelFields/Decimal.html +1 -1
- data/doc/Compony/ModelFields/Email.html +1 -1
- data/doc/Compony/ModelFields/Float.html +1 -1
- data/doc/Compony/ModelFields/Integer.html +1 -1
- data/doc/Compony/ModelFields/Percentage.html +1 -1
- data/doc/Compony/ModelFields/Phone.html +1 -1
- data/doc/Compony/ModelFields/RichText.html +1 -1
- data/doc/Compony/ModelFields/String.html +1 -1
- data/doc/Compony/ModelFields/Text.html +1 -1
- data/doc/Compony/ModelFields/Time.html +1 -1
- data/doc/Compony/ModelFields/Url.html +1 -1
- data/doc/Compony/ModelFields.html +1 -1
- data/doc/Compony/ModelMixin.html +1 -1
- data/doc/Compony/NaturalOrdering.html +1 -1
- data/doc/Compony/RequestContext.html +177 -14
- data/doc/Compony/Version.html +1 -1
- data/doc/Compony/ViewHelpers.html +15 -272
- data/doc/Compony/VirtualModel.html +1 -1
- data/doc/Compony.html +303 -837
- data/doc/ComponyController.html +1 -1
- data/doc/_index.html +30 -2
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +11 -18
- data/doc/guide/basic_component.md +12 -8
- data/doc/guide/example.md +17 -17
- data/doc/guide/feasibility.md +4 -2
- data/doc/guide/generators.md +4 -2
- data/doc/guide/inheritance.md +4 -2
- data/doc/guide/installation.md +4 -2
- data/doc/guide/intents.md +167 -0
- data/doc/guide/internal_datastructures.md +4 -2
- data/doc/guide/model_fields.md +4 -2
- data/doc/guide/nesting.md +5 -3
- data/doc/guide/ownership.md +5 -3
- data/doc/guide/pre_built_components/destroy.md +3 -3
- data/doc/guide/pre_built_components/edit.md +1 -1
- data/doc/guide/pre_built_components/form.md +1 -1
- data/doc/guide/pre_built_components/index.md +1 -1
- data/doc/guide/pre_built_components/list.md +1 -1
- data/doc/guide/pre_built_components/new.md +2 -2
- data/doc/guide/pre_built_components/show.md +1 -1
- data/doc/guide/pre_built_components/with_form.md +1 -1
- data/doc/guide/pre_built_components.md +4 -3
- data/doc/guide/resourceful.md +5 -3
- data/doc/guide/standalone.md +10 -2
- data/doc/guide/virtual_models.md +4 -2
- data/doc/index.html +11 -18
- data/doc/method_list.html +273 -161
- data/doc/top-level-namespace.html +1 -1
- data/lib/compony/component.rb +19 -48
- data/lib/compony/component_mixins/default/standalone/standalone_dsl.rb +2 -2
- data/lib/compony/component_mixins/default/standalone.rb +16 -0
- data/lib/compony/component_mixins/resourceful.rb +1 -1
- data/lib/compony/components/buttons/css_button.rb +32 -0
- data/lib/compony/components/buttons/link.rb +31 -0
- data/lib/compony/components/destroy.rb +9 -8
- data/lib/compony/components/edit.rb +5 -4
- data/lib/compony/components/form.rb +7 -1
- data/lib/compony/components/index.rb +2 -2
- data/lib/compony/components/list.rb +4 -4
- data/lib/compony/components/new.rb +1 -1
- data/lib/compony/components/show.rb +8 -11
- data/lib/compony/components/with_form.rb +1 -1
- data/lib/compony/exposed_intents_dsl.rb +29 -0
- data/lib/compony/intent.rb +145 -0
- data/lib/compony/model_fields/association.rb +1 -1
- data/lib/compony/request_context.rb +21 -0
- data/lib/compony/view_helpers.rb +5 -48
- data/lib/compony.rb +63 -149
- metadata +12 -6
- data/doc/guide/helpers.md +0 -156
- data/doc/guide/pre_built_components/button.md +0 -8
- data/doc/guide/root_actions.md +0 -67
- data/lib/compony/components/button.rb +0 -61
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
|
+
|
|
3
|
+
# Intents
|
|
4
|
+
|
|
5
|
+
An intent is a gateway to a component, along with relevant context. It encapsulates tools used to generate paths, checking [feasibility](/doc/guide/feasibility.md) and rendering links and buttons pointing other components within your application.
|
|
6
|
+
|
|
7
|
+
To instanciate an intent, use either the raw `Compony.intent` method or one of the [helpers](#helpers). All methods that use intents under the hood have a similar interface, especially regarding the first two positional arguments. The most commonly used forms are:
|
|
8
|
+
|
|
9
|
+
- `Compony.intent(:index, :users)`: point to a component by its comp and family name
|
|
10
|
+
- `Compony.intent(:show, User.first)`: pass the intent a single model and let it figure out the family name from `model_name`
|
|
11
|
+
- `Compony.intent(:list, current_user.quotes)`: pass the intent an active record collection and let it figure out the family name from `model_name`
|
|
12
|
+
- `Compony.intent(Components::Users::Index)`: point to a component by giving its class as a single argument
|
|
13
|
+
|
|
14
|
+
The returned intent can then be used to:
|
|
15
|
+
|
|
16
|
+
- Retrieve the target component class using `intent.comp_class`
|
|
17
|
+
- Build an instance of the target component using `intent.comp`
|
|
18
|
+
- If a model was given when building the intent, such as in the second form above, the comp instance will contain it as `@data`.
|
|
19
|
+
- Retrieve the `path` to the target component (only works for standalone components; will automatically set ID parameter if a model was given)
|
|
20
|
+
- Retrieve the `name` that can be used to store the intent in a hash or similar
|
|
21
|
+
- Retrieve the `label` that can be used to refer to the component (if a model was given, passes it to the target component's label block)
|
|
22
|
+
- Check for [feasibility](/doc/guide/feasibility.md) using `feasible?`
|
|
23
|
+
- Render a [button](#buttons-and-styles) to the target component which automatically includes all of the above along with the suitable behavior using `render` and passing a controller
|
|
24
|
+
|
|
25
|
+
An intent's behavior can be customized by passing some of the following keyword arguments when building it:
|
|
26
|
+
|
|
27
|
+
- `standalone_name` allows you to point to another endpoint within your target component, which is especially useful for generating paths. Keep in mind that within each standalone name, multiple HTTP methods can exist. This argument defaults to `nil`, which is the main endpoint created by `standalone`.
|
|
28
|
+
- `method` defines the HTTP verb within the standalone configuration that should be addressed. Defaults to `:get`, but can be overriden to be `:patch`, `:put`, `:post` or `:delete`. When generating a button, the `turbo_method` will be automatically be derived from this argument.
|
|
29
|
+
- `name` overrides the auto-generated name of the intent, affecting the result of the reader of the same name.
|
|
30
|
+
- `label` accepts two forms:
|
|
31
|
+
- When passing a String, it will be returned as-is when returning the label, also affecting buttons generated by this intent.
|
|
32
|
+
- When passing a Hash, any included key-value pair will be used as keyword arguments to the `label` reader method.
|
|
33
|
+
- `path` allows altering generated paths and accepts either a String or a Hash just like `label`.
|
|
34
|
+
- `data` and `data_class` will be given to the target component if/when it gets instanciated. This is used to point to [resourceful](/doc/guide/resourceful.md) components. If a model was passed as the second positional argument (instead of a family name), it will become `data` and this argument can be omitted. If `data` responds to `model_name`, it will be considered a model-like class.
|
|
35
|
+
- `feasibility_target` and `feasibility_action` can be given to alter the behavior of the `feasible?` reader.
|
|
36
|
+
- Any further arguments are passed to the initializer of the button if/when the intent gets rendered.
|
|
37
|
+
|
|
38
|
+
## Helpers
|
|
39
|
+
|
|
40
|
+
In practice, you will rarely call `Compony.intent` directly, and likely never `Compony::Intent.new`. Instead, you will be interacting with one of the following helpers that will instanciate an intent under the hood and thus all accept similar arguments as those described above:
|
|
41
|
+
|
|
42
|
+
### `Compony.path`
|
|
43
|
+
|
|
44
|
+
This helper is useful when generating paths without rendering any HTML, such as when redirecting. Internally, this builds an intent which will in turn use the target component's `path` block to generate a Rails path (String) pointing to the correct location. Any keyword arguments are passed to the intent's `path` method, allowing you to override the model (to refer to [resourceful](/doc/guide/resourceful.md) target components), as well as specifying a `standalone_name` or pass extra arguments to the target component's `path` block.
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
redirect_to Compony.path(:index, :users) # Redirects to /users
|
|
50
|
+
redirect_to Compony.path(:show, @data.author) # Redirects to something like /authors/42
|
|
51
|
+
redirect_to Compony.path(:thank_you, @data) # Redirects to something like /feedbacks/42/thank_you
|
|
52
|
+
redirect_to Compony.path(:step_2, :registrations, accept_terms: :yes) # Redirects to something like /registrations/step_2?accept_terms=yes
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `render_intent`
|
|
56
|
+
|
|
57
|
+
This is the preferred way of quickly rendering [links or buttons to components](#buttons-and-styles) from other parts of your application. The method is implemented twice:
|
|
58
|
+
|
|
59
|
+
- When called from within a `content` block of a component, the method implemented in the `RequestContext` is used, which automatically detects the component from which it is called and passes it as `parent_comp` to the rendered button.
|
|
60
|
+
- When called from a regular Rails view, the Rails helper method is used, which instanciates a button without passing a `parent_comp`.
|
|
61
|
+
|
|
62
|
+
Use the `button` argument to customize the generated button. Example:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
setup do
|
|
66
|
+
content do
|
|
67
|
+
div render_intent(:show, User.first, button: { style: :link, label: { format: :short } })`
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
In the example above, there is a lot more going on than it seems. Due to the [specified style](#buttons-and-styles), a plain HTML link will be generated, pointing to the component `Components::Users::Show` and instanciating it with the first user as `@data`. Assuming that component inherits from `Compony::Components::Show` and did not override label or path, the link will be labelled "Show" (which is the default short format label generated by Compony's default [pre-built Show component](/doc/guide/pre_built_components/show.md)), and the `href` will be `/users/:id` where ID will automatically be `User.first`'s ID (e.g. `/users/1`). However, if `:show` was [prevented](/doc/guide/feasibility.md), the link will be strikethrough, non-clickable, greyed out and have a title explaining why it can't be clicked. Further, if the current user does not have [authorization](/doc/guide/standalone.md) to display the target user, the link will not show up at all, and no HTML will be generated within the `div`.
|
|
73
|
+
|
|
74
|
+
### `render_sub_comp`
|
|
75
|
+
|
|
76
|
+
This is used within a component's `content` block to instanciate another component and [nest it within](/doc/guide/nesting.md). Internally, the current component's `sub_comp` method is used and all arguments are passed to that.
|
|
77
|
+
|
|
78
|
+
For example, let us consider you want to build your user management's Show component to include the list of quotes belonging to the displayed user. Assuming you have already built `Components::Quotes::List` for quotes' Index component, this helper allows you to simply write:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
class Components::Users::Show < Compony::Components::Show
|
|
82
|
+
setup do
|
|
83
|
+
# ...
|
|
84
|
+
content :quotes do
|
|
85
|
+
concat render_sub_comp(:list, @data.quotes)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This implicitely builds an intent which auto-detects the family name `:quotes` from `@data.quotes`, which is an active record collection and thus implements `model_name`. The List component is then instanciated with its `@data` being that very collection and the users's Show component as `parent_comp`, resulting in the proper nesting and display of the desired resources.
|
|
92
|
+
|
|
93
|
+
### `Compony.comp_class_for`
|
|
94
|
+
|
|
95
|
+
This helper is useful for checking whether a component is implemented. For instance, when implementing [abstract components to inherit from later](/doc/guide/inheritance.md), you can check for `if Compony.comp_class_for(:destroy, family_name)` to only provide some functionality of a Destroy component exists for the current family.
|
|
96
|
+
|
|
97
|
+
This method also has its sibling `Compony.comp_class_for!`, which will fail if no such component could be found. It is however mostly used internally.
|
|
98
|
+
|
|
99
|
+
## Buttons and styles
|
|
100
|
+
|
|
101
|
+
Button components are used as presenters for intents, hyperlinks to other components or submit buttons. They are a central way to define how buttons all over the application should look like. Their interface is adapted to intents, creating a standardized "slim waist" that greatly simplifies linking between components. Note that the term "button" refers to how they look and not how they are actually implemented - actual HTML buttons have several disadvantages (e.g. requiring drop-in forms and not responding to Ctrl+Click or middle-click when the user would prefer a new tab).
|
|
102
|
+
|
|
103
|
+
Compony comes with two button styles:
|
|
104
|
+
|
|
105
|
+
- `:css_button` is the default button style and creates a div that looks similar to a HTML button rendered in Firefox. If the class `disabled` is given, it is greyed out.
|
|
106
|
+
- `:link` is mostly just a regular `<a>` tag, but will appear greyed out and strikethrough if the class `disabled` is given.
|
|
107
|
+
|
|
108
|
+
All button styles support the following keyword arguments to their initializer:
|
|
109
|
+
|
|
110
|
+
- `label` is a String which will be displayed as the text of the link or button.
|
|
111
|
+
- `href` is the url/path that the button points to. If `nil`, will change to `javascript:void(0)`.
|
|
112
|
+
- `method` takes a HTTP verb will generate a suitable `turbo_method` data attribute.
|
|
113
|
+
- `class` will mostly let be as-is, but checked for the `disabled` class - if given, the style will be ovewritten.
|
|
114
|
+
|
|
115
|
+
Note that since buttons are full components, they can be [nested](/doc/guide/nesting.md) into another component by providing `parent_comp`. If called from within a component's `content` block, the helper `render_intent` does this automatically.
|
|
116
|
+
|
|
117
|
+
As implicitely mentioned above, Compony buttons are referenced to by a name called a style (`:css_button` actually points to `Compony::Components::Buttons::CssButton`). When rendering an intent, the style can be passed as an argument: `render_intent(:show, User.first, button: { style: :link })` and the intent will automatically instanciate the desired component class.
|
|
118
|
+
|
|
119
|
+
Note: it is possible to use a button component to submit a form. In order to achieve this, you must implement a hidden submit button (for handling keyboard Enter and Return), as well as pass `onclick: "this.closest('form').requestSubmit(); return false;"` as an argument. See the pre-built Form component's implementation for an example.
|
|
120
|
+
|
|
121
|
+
### Adding your own styles
|
|
122
|
+
|
|
123
|
+
In your application, you will likely want to implement your own button styles. Create a component (e.g. `Components::Commons::MyButton`) and inherit from `Compony::Components::Buttons::Link`. Override the method `prepare_opts!` and don't forget to call `super` first. Then, go through any `@comp_args` that might be of interest to you and mutate `@comp_args[:style]` and/or `@comp_args[:class]` to suit your needs. Make sure to handle the class `disabled`, as intents will set them if the intent is not feasible. Note that if a user is lacking authorization to perform an intent, the intent will not even instanciate the button.
|
|
124
|
+
|
|
125
|
+
Once your button class is ready, register it in `config/initializers/compony.rb` with: `Compony.register_button_style :my_button, '::Components::Commons::MyButton'`. You can also change the default button style there using: `Compony.default_button_style = :my_button`.
|
|
126
|
+
|
|
127
|
+
If you have multiple kinds of buttons (e.g. dropdown items, pill-style buttons, compact forms etc.), you should create a separate style and button component class for every kind. This will make it easy to refer to them by supplying something like `button: { style: :dropdown_item }` in `render_intent`.
|
|
128
|
+
|
|
129
|
+
## Exposed intents
|
|
130
|
+
|
|
131
|
+
Components can expose a set of intents to be displayed elsewhere. Those can either be rendered by the parent comp, or by the application layout itself in case the component exposing them is currently `root_comp` (see the chapter about [standalone](/doc/guide/standalone.md)). This is useful if you have something like an actions toolbar that changes depending on the currently contained component.
|
|
132
|
+
|
|
133
|
+
To expose an intent, proceed as shown in the following example:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
# ...
|
|
137
|
+
class Components::Quotes::Show < Compony::Components::Show
|
|
138
|
+
setup do
|
|
139
|
+
exposed_intents do
|
|
140
|
+
add :index, family_name, label: 'Show all', name: :index
|
|
141
|
+
remove :destroy
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
In this example, the shown component exposes an intent pointing to the Index component of it's own family (`:users`). The `:name` argument causes the new intent to be just named `:index` rather than `:index_users`. This is for the sake of the example and would allow a component inheriting from this component to use `exposed_intents { remove :index }` rather than `exposed_intents { remove :index_users }`.
|
|
148
|
+
|
|
149
|
+
Similarly, this component removes the exposed intent `:destroy` which it whould otherwise inherit from `Comopony::Components::Show`. Every call to `exposed_intents` overrides properties set in previous calls by the same component, primarly useful for [reusing components by inheritance](/doc/guide/inheritance.md).
|
|
150
|
+
|
|
151
|
+
In order to replace an existing intent defined by a previous call to `exposed_intents` (e.g. in a parent class), simply call `add` again and make sure the intent's name matches that of the one to override. `add` also accepts the `before:` keyword, allowing you to reorder intents or insert a new one into a specific place in the intent list.
|
|
152
|
+
|
|
153
|
+
Note that the `add` method has full intent argument support and thus also accepts parameters related to the button (e.g. `style`), path generation, feasibility etc.
|
|
154
|
+
|
|
155
|
+
### Rendering exposed intents
|
|
156
|
+
|
|
157
|
+
You can render exposed intents in the parent component or in the application layout. To do so, call `component.exposed_intents`, loop across them and call `.render(controller)` on each (perhaps inside a `div` tag or whatever suits your needs).
|
|
158
|
+
|
|
159
|
+
Example in `layouts/application.html.erb`
|
|
160
|
+
|
|
161
|
+
```erb
|
|
162
|
+
<% Compony.root_comp&.exposed_intents&.each do |intent| %>
|
|
163
|
+
<div class="root-intent"><%= intent.render(controller) %>
|
|
164
|
+
<% end %>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
[Guide index](/README.md#guide--documentation)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[Back to the guide](/README.md#guide)
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
2
|
|
|
3
3
|
# Internal datastructures
|
|
4
4
|
|
|
@@ -42,4 +42,6 @@ RequestContext further provides the following methods on its own:
|
|
|
42
42
|
- `evaluate_with_backfire` is `evaluate` with enabled backfiring.
|
|
43
43
|
- `component` returns the component the RequestContext was instantiated with.
|
|
44
44
|
- `request_context` returns self. This is for disambiguation purposes.
|
|
45
|
-
- Any call to an unknown method will first be evaluated as a potential hit in `locals`. Only if no matching local is found, Dslblend takes over.
|
|
45
|
+
- Any call to an unknown method will first be evaluated as a potential hit in `locals`. Only if no matching local is found, Dslblend takes over.
|
|
46
|
+
|
|
47
|
+
[Guide index](/README.md#guide--documentation)
|
data/doc/guide/model_fields.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[Back to the guide](/README.md#guide)
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
2
|
|
|
3
3
|
# Model fields
|
|
4
4
|
|
|
@@ -59,4 +59,6 @@ Example:
|
|
|
59
59
|
Compony.model_field_namespaces = ['MyCustomModelFields', 'Compony::ModelFields']
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
You can then implement `MyCustomModelFields::Animal`, `MyCustomModelFields::String` etc. You can then use `field :fav_animal, :animal` in your model.
|
|
62
|
+
You can then implement `MyCustomModelFields::Animal`, `MyCustomModelFields::String` etc. You can then use `field :fav_animal, :animal` in your model.
|
|
63
|
+
|
|
64
|
+
[Guide index](/README.md#guide--documentation)
|
data/doc/guide/nesting.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[Back to the guide](/README.md#guide)
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
2
|
|
|
3
3
|
# Nesting
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ Nesting occurs when a component is being rendered. It is perfectly feasible to u
|
|
|
12
12
|
|
|
13
13
|
Note that only the root component runs authentication and authorization. Thus, be careful which components you nest.
|
|
14
14
|
|
|
15
|
-
To create a sub-component, use `
|
|
15
|
+
To create a sub-component, use `render_sub_comp` in a component's content block. Any keyword arguments given will be passed to the sub-component. It is strictly recommended to exclusively use `render_sub_comp`, `sub_comp` or its [resourceful](./resourceful.md#nesting-resourceful-components) pendent to nest components, as this method makes a component aware of its exact nesting.
|
|
16
16
|
|
|
17
17
|
Here is a simple example of a component that displays numbers as binary:
|
|
18
18
|
|
|
@@ -129,4 +129,6 @@ The number 4 has the binary form 100. Enter a number and press ENTER: [4]
|
|
|
129
129
|
The number 8 has the binary form 1000. Enter a number and press ENTER: [8]
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
Note that this example is completely stateless, as all the info is encoded in the URL.
|
|
132
|
+
Note that this example is completely stateless, as all the info is encoded in the URL.
|
|
133
|
+
|
|
134
|
+
[Guide index](/README.md#guide--documentation)
|
data/doc/guide/ownership.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[Back to the guide](/README.md#guide)
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
2
|
|
|
3
3
|
# Ownership
|
|
4
4
|
|
|
@@ -11,11 +11,13 @@ In Compony, if a model class is owned by another, it means that:
|
|
|
11
11
|
|
|
12
12
|
- The owned model has a non-optional `belongs_to` relation ship to its owner.
|
|
13
13
|
- The owned model class has no Index component.
|
|
14
|
-
- Pre-built components
|
|
14
|
+
- [Pre-built components](/doc/guide/pre_built_components.md) offer [exposed intents](/doc/guide/intents.md#exposed-intents) to the owner model and redirect to its Show component instead of to the current object's Index component.
|
|
15
15
|
|
|
16
16
|
To mark a model as owned by another, write the following code **in the model**:
|
|
17
17
|
|
|
18
18
|
```ruby
|
|
19
19
|
# app/models/permission.rb
|
|
20
20
|
owned_by :user
|
|
21
|
-
```
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
[Guide index](/README.md#guide--documentation)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
- [Back to the guide](/README.md#guide)
|
|
1
|
+
- [Back to the guide](/README.md#guide--documentation)
|
|
2
2
|
- [List of pre-built components](/doc/guide/pre_built_components.md)
|
|
3
3
|
|
|
4
4
|
# Pre-built components: Destroy
|
|
@@ -12,11 +12,11 @@ This component is the Compony equivalent to a typical Rails controller's `destro
|
|
|
12
12
|
- if present: the data's Show component
|
|
13
13
|
- otherwise: the data's Index component
|
|
14
14
|
|
|
15
|
-
Authorization checks for `destroy` even in GET. The reason is that users that aren't able to destroy a resource shouldn't even arrive at the page asking them whether they want to do so, unable to click the only button due to lacking permissions. This also causes any
|
|
15
|
+
Authorization checks for `destroy` even in GET. The reason is that users that aren't able to destroy a resource shouldn't even arrive at the page asking them whether they want to do so, unable to click the only button due to lacking permissions. This also causes any [intents](/doc/guide/intents.md) to Destroy components to be hidden if the user is unable to destroy the corresponding resource.
|
|
16
16
|
|
|
17
17
|
This component largely follows the [resourceful lifecycle](/doc/guide/resourceful.md#complete-resourceful-lifecycle). As can be expected, the resource is loaded by `Resourceful`'s default load block and `store_data` is implemented to destroy the resource.
|
|
18
18
|
|
|
19
|
-
If the resource is [owned](/doc/guide/ownership.md), the component provides a `:back_to_owner`
|
|
19
|
+
If the resource is [owned](/doc/guide/ownership.md), the component provides a `:back_to_owner` [exposed intent](/doc/guide/intents.md#exposed-intents) in the form of a cancel button.
|
|
20
20
|
|
|
21
21
|
The following DSL methods are implemented to allow for convenient overrides of default logic:
|
|
22
22
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
- [Back to the guide](/README.md#guide)
|
|
1
|
+
- [Back to the guide](/README.md#guide--documentation)
|
|
2
2
|
- [List of pre-built components](/doc/guide/pre_built_components.md)
|
|
3
3
|
|
|
4
4
|
# Pre-built components: New
|
|
@@ -13,7 +13,7 @@ This component is the Compony equivalent to a typical Rails controller's `new` a
|
|
|
13
13
|
- otherwise, if the resource is owned by another resource class: the owner's Show component
|
|
14
14
|
- otherwise, the data's Index component
|
|
15
15
|
|
|
16
|
-
Authorization checks for `create` even in GET. The reason is that it makes no sense to present an empty form to a user who cannot create a new record. This also causes any
|
|
16
|
+
Authorization checks for `create` even in GET. The reason is that it makes no sense to present an empty form to a user who cannot create a new record. This also causes any [intents](/doc/guide/intents.md) to New components to be hidden to users lacking the permission.
|
|
17
17
|
|
|
18
18
|
This component follows the [resourceful lifecycle](/doc/guide/resourceful.md#complete-resourceful-lifecycle). `load_data` is set to create a new record and `store_data` attempts to create it. Parameters are validated in `assign_attributes` using a Schemacop schema that is generated from the form. The schema corresponds to Rail's typical strong parameter structure for forms. For example, a user's New component would look for a parameter `user` holding a hash of attributes (e.g. `user[first_name]=Tom`).
|
|
19
19
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[Back to the guide](/README.md#guide)
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
2
|
|
|
3
3
|
# Pre-built components shipped with Compony
|
|
4
4
|
|
|
@@ -8,7 +8,6 @@ The pre-built components can be found in the module `Compony::Components`. As yo
|
|
|
8
8
|
|
|
9
9
|
In the following, the pre-built components currently shipped with Compony are presented:
|
|
10
10
|
|
|
11
|
-
- [Button](./pre_built_components/button.md): This component class gets instanciated whenever using `Compony.button` or `compony_button`.
|
|
12
11
|
- [Show](./pre_built_components/show.md): Compony's equivalent to Rail's `show` controller action
|
|
13
12
|
- [Index](./pre_built_components/index.md): Compony's equivalent to Rail's `index` controller action
|
|
14
13
|
- [List](./pre_built_components/list.md): Compony's equivalent to Rail's `_list` partial
|
|
@@ -16,4 +15,6 @@ In the following, the pre-built components currently shipped with Compony are pr
|
|
|
16
15
|
- [WithForm](./pre_built_components/with_form.md): A base class for components containing and submitting forms
|
|
17
16
|
- [Form](./pre_built_components/form.md): Compony's equivalent to Rail's `_form` partial
|
|
18
17
|
- [New](./pre_built_components/new.md): Compony's equivalent to Rail's `new` and `create` controller action
|
|
19
|
-
- [Edit](./pre_built_components/new.md): Compony's equivalent to Rail's `edit` and `update` controller action
|
|
18
|
+
- [Edit](./pre_built_components/new.md): Compony's equivalent to Rail's `edit` and `update` controller action
|
|
19
|
+
|
|
20
|
+
[Guide index](/README.md#guide--documentation)
|
data/doc/guide/resourceful.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[Back to the guide](/README.md#guide)
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
2
|
|
|
3
3
|
# Resourceful components
|
|
4
4
|
|
|
@@ -66,7 +66,7 @@ class Components::Users::Destroy < Compony::Component
|
|
|
66
66
|
label(:long) { |data| "Delete #{data.label}" }
|
|
67
67
|
content do
|
|
68
68
|
h1 "Are you sure to delete #{@data.label}?"
|
|
69
|
-
div
|
|
69
|
+
div render_intent(:destroy, @data, label: 'Yes, delete', method: :delete)
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
end
|
|
@@ -98,4 +98,6 @@ The rule of thumb thus becomes:
|
|
|
98
98
|
|
|
99
99
|
- When a resourceful component instantiates a resourceful sub-component, use `resourceful_sub_comp` in the parent component.
|
|
100
100
|
- When a resourceful component instantiates a non-resourceful sub-component, use `sub_comp`.
|
|
101
|
-
- The situation where a non-resourceful component instantiates a resourceful component should not occur. Instead, make your parent component resourceful, even if it doesn't use the data itself. By housing a resourceful sub-comp, the parent component's nature inherently becomes resourceful and you should use the Resourceful mixin.
|
|
101
|
+
- The situation where a non-resourceful component instantiates a resourceful component should not occur. Instead, make your parent component resourceful, even if it doesn't use the data itself. By housing a resourceful sub-comp, the parent component's nature inherently becomes resourceful and you should use the Resourceful mixin.
|
|
102
|
+
|
|
103
|
+
[Guide index](/README.md#guide--documentation)
|
data/doc/guide/standalone.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[Back to the guide](/README.md#guide)
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
2
|
|
|
3
3
|
# Standalone (routing to components)
|
|
4
4
|
|
|
@@ -133,4 +133,12 @@ end
|
|
|
133
133
|
scope '(:lang)', lang: /([a-z]{2})?/i do
|
|
134
134
|
get 'welcome', to: 'compony#your_component'
|
|
135
135
|
end
|
|
136
|
-
```
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Customizing path generation
|
|
139
|
+
|
|
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
|
+
|
|
142
|
+
This is an advanced usage. Refer to the default implementation of `Component`'s `path_block` to see an example.
|
|
143
|
+
|
|
144
|
+
[Guide index](/README.md#guide--documentation)
|
data/doc/guide/virtual_models.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[Back to the guide](/README.md#guide)
|
|
1
|
+
[Back to the guide](/README.md#guide--documentation)
|
|
2
2
|
|
|
3
3
|
# Unleashing virtual models through Compony's `ActiveType` integration
|
|
4
4
|
|
|
@@ -26,4 +26,6 @@ Why this works: As your `Components::Reports::Request` inherits from Compony's `
|
|
|
26
26
|
|
|
27
27
|
Note: it is even possible to combine this pattern with Rails' `accepts_nested_attributes_for` and `simple_form`'s `f.simple_fields_for` call, where the nested object is a real database-backed model. Even though the component's resource is purely virtual, Rails will create or update the nested model when Compony calls `save` on the parent resource. This allows for very fast implementation of business logic creating multiple objects from a single form post by wrapping the resources in a virtual model.
|
|
28
28
|
|
|
29
|
-
If you intend to use this technique in combination with `ActiveStorage`, you must also override the `store_data` block to just validate the model instead of saving it, as the hook creating the attachment is bound to fail (the virtual model does not exist in the database and thus cannot be referenced from `ActiveStorage::Attachment`). For the same reason, you cannot call `blob.download`, but must find the file's tempfile in the request parameters in order to process the file attached by the user.
|
|
29
|
+
If you intend to use this technique in combination with `ActiveStorage`, you must also override the `store_data` block to just validate the model instead of saving it, as the hook creating the attachment is bound to fail (the virtual model does not exist in the database and thus cannot be referenced from `ActiveStorage::Attachment`). For the same reason, you cannot call `blob.download`, but must find the file's tempfile in the request parameters in order to process the file attached by the user.
|
|
30
|
+
|
|
31
|
+
[Guide index](/README.md#guide--documentation)
|
data/doc/index.html
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
|
|
72
72
|
<h1 id="label-About+Compony">About Compony</h1>
|
|
73
73
|
|
|
74
|
-
<p>Compony is a Gem that allows you to write your Rails application in component-style fashion. It combines a controller action and route along with its view into a single Ruby class.
|
|
74
|
+
<p>Compony is a Gem that allows you to write your Rails application in <strong>component-style</strong> fashion. It combines a controller action and route along with its view into a single Ruby class. Along with the DSL approach and a powerful model mixin, Compony <strong>makes your application’s code more semantic</strong>, allows writing <strong>much DRYer code</strong>, using inheritance even in views and <strong>much easier refactoring</strong> for your Rails applications, helping you to <strong>keep the code clean as the application evolves</strong>.</p>
|
|
75
75
|
|
|
76
76
|
<p>Compony’s key aspects:</p>
|
|
77
77
|
<ul><li>
|
|
@@ -79,17 +79,18 @@
|
|
|
79
79
|
</li><li>
|
|
80
80
|
<p>Refactor common logic into your own components and inherit from them to DRY up your code.</p>
|
|
81
81
|
</li><li>
|
|
82
|
-
<p>Compony’s
|
|
83
|
-
|
|
82
|
+
<p>Compony’s model mixin allows you to define metadata in your models and react to them, resulting in more semantic code. Examples:</p>
|
|
83
|
+
<ul><li>
|
|
84
84
|
<p>Compony fields capture attributes that should be made visible in your UI. They allow you to implement formatting behavior and parameter sanitization for various types, e.g. URLs, phone numbers, colors etc. ready to be used in your lists, detail panels, or forms.</p>
|
|
85
85
|
</li><li>
|
|
86
86
|
<p>Compony’s feasibility framework allows you to prohibit actions based on conditions, along with an error message. This causes all buttons pointing to that action to be disabled with a meaningful error message.</p>
|
|
87
|
+
</li></ul>
|
|
87
88
|
</li><li>
|
|
88
89
|
<p>Compony only structures your code, but provides no style whatsoever. It is like a bookshelf rather than a reader’s library. You still implement your own layouts, CSS and Javascript to define the behavior of your front-end.</p>
|
|
89
90
|
</li><li>
|
|
90
|
-
<p>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>
|
|
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>
|
|
91
92
|
</li><li>
|
|
92
|
-
<p>Compony is built for Rails 7 and fully supports Stimulus and Turbo Drive. Turbo Frames and Streams are not yet targeted,
|
|
93
|
+
<p>Compony is built for Rails 7, 7.1 and 8, and fully supports Stimulus and Turbo Drive. Turbo Frames and Streams are not yet targeted, but can be used alongside, just like with any Rails application.</p>
|
|
93
94
|
</li><li>
|
|
94
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>
|
|
95
96
|
</li></ul>
|
|
@@ -98,7 +99,7 @@
|
|
|
98
99
|
|
|
99
100
|
<p>I am actively using this framework in various applications and both performance and reliability are good. However, the project is experimental and lacking peer reviews and especially automatic testing, such as unit and integration tests. Also, expect there to be (<a href="/CHANGELOG_md.html">documented</a>) breaking changes in the future, as the API will likely be further refined, resulting in renamings and deprecation of various methods.</p>
|
|
100
101
|
|
|
101
|
-
<h2 id="label-
|
|
102
|
+
<h2 id="label-Other+projects+exploring+similar+concepts">Other projects exploring similar concepts</h2>
|
|
102
103
|
|
|
103
104
|
<p>A project with a similar aim, but a different approach, is <a href="https://github.com/phlex-ruby/phlex">Phlex</a>.</p>
|
|
104
105
|
|
|
@@ -122,11 +123,9 @@
|
|
|
122
123
|
</li><li>
|
|
123
124
|
<p><a href="./doc/guide/resourceful_md.html">Resourceful components</a>: How to create components that deal with Rails-style resources</p>
|
|
124
125
|
</li><li>
|
|
125
|
-
<p><a href="./doc/guide/
|
|
126
|
-
</li><li>
|
|
127
|
-
<p><a href="./doc/guide/root_actions_md.html">Root actions</a>: How to provide context-sensitive buttons to your application</p>
|
|
126
|
+
<p><a href="./doc/guide/intents_md.html">Intents</a>: How to point to a component and provide custom components for rendering intents</p>
|
|
128
127
|
</li><li>
|
|
129
|
-
<p><a href="./doc/guide/feasibility_md.html">Feasibility</a>: Disabiling
|
|
128
|
+
<p><a href="./doc/guide/feasibility_md.html">Feasibility</a>: Disabiling intents based on context (contains <code>prevent</code>)</p>
|
|
130
129
|
</li><li>
|
|
131
130
|
<p><a href="./doc/guide/ownership_md.html">Ownership</a>: Informing Compony that a resource is conceptually part of another resource</p>
|
|
132
131
|
</li><li>
|
|
@@ -143,8 +142,6 @@
|
|
|
143
142
|
<ul><li>
|
|
144
143
|
<p><a href="./doc/guide/pre_built_components_md.html">Introduction</a></p>
|
|
145
144
|
</li><li>
|
|
146
|
-
<p><a href="./doc/guide/pre_built_components/button_md.html">Button</a>: This component class gets instanciated whenever using <code>Compony.button</code> or <code>compony_button</code>.</p>
|
|
147
|
-
</li><li>
|
|
148
145
|
<p><a href="./doc/guide/pre_built_components/show_md.html">Show</a>: Compony’s equivalent to Rail’s <code>show</code> controller action</p>
|
|
149
146
|
</li><li>
|
|
150
147
|
<p><a href="./doc/guide/pre_built_components/index_md.html">Index</a>: Compony’s equivalent to Rail’s <code>index</code> controller action</p>
|
|
@@ -189,11 +186,7 @@
|
|
|
189
186
|
</li><li>
|
|
190
187
|
<p>At this point, I haven’t gotten into Turbo Streams and Turbo Frames. It would be interesting to extend Compony such it also makes writing applications using these features much easier.</p>
|
|
191
188
|
</li><li>
|
|
192
|
-
<p>
|
|
193
|
-
</li><li>
|
|
194
|
-
<p>The feasibility framework does not yet enforce prevention, but only has effects on buttons. Actions should be structured more explicitly such that prevention becomes as tight as authorization.</p>
|
|
195
|
-
</li><li>
|
|
196
|
-
<p>Feasibility for links is not yet implemented.</p>
|
|
189
|
+
<p>The feasibility framework does not yet enforce prevention, but only has effects on buttons.</p>
|
|
197
190
|
</li><li>
|
|
198
191
|
<p>Compony is not compatible with <code>tailwindcss-rails</code>. This is likely due to Tailwind automatically removing any CSS that is not used by the application and the usage detection not picking up Compony components, as their content is not provided in views.</p>
|
|
199
192
|
</li></ul>
|
|
@@ -206,7 +199,7 @@
|
|
|
206
199
|
</div></div>
|
|
207
200
|
|
|
208
201
|
<div id="footer">
|
|
209
|
-
Generated on Thu Nov
|
|
202
|
+
Generated on Thu Nov 27 16:02:21 2025 by
|
|
210
203
|
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
|
211
204
|
0.9.34 (ruby-3.3.5).
|
|
212
205
|
</div>
|