ruby_ui_admin 0.1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +165 -0
- data/app/assets/stylesheets/ruby_ui_admin/application.tailwind.css +111 -0
- data/app/components/ruby_ui_admin/ui/base.rb +26 -0
- data/app/components/ruby_ui_admin/ui/icon.rb +79 -0
- data/app/components/ruby_ui_admin/ui/native_select.rb +37 -0
- data/app/components/ruby_ui_admin/ui/native_select_group.rb +18 -0
- data/app/components/ruby_ui_admin/ui/native_select_icon.rb +42 -0
- data/app/components/ruby_ui_admin/ui/native_select_option.rb +18 -0
- data/app/components/ruby_ui_admin/ui/pagination.rb +62 -0
- data/app/components/ruby_ui_admin/ui/pagination_content.rb +18 -0
- data/app/components/ruby_ui_admin/ui/pagination_ellipsis.rb +45 -0
- data/app/components/ruby_ui_admin/ui/pagination_item.rb +32 -0
- data/app/components/ruby_ui_admin/ui/select.rb +29 -0
- data/app/components/ruby_ui_admin/ui/toast_action.rb +30 -0
- data/app/components/ruby_ui_admin/ui/toast_cancel.rb +30 -0
- data/app/components/ruby_ui_admin/ui/toast_close.rb +43 -0
- data/app/components/ruby_ui_admin/ui/toast_description.rb +21 -0
- data/app/components/ruby_ui_admin/ui/toast_icon.rb +69 -0
- data/app/components/ruby_ui_admin/ui/toast_item.rb +75 -0
- data/app/components/ruby_ui_admin/ui/toast_region.rb +147 -0
- data/app/components/ruby_ui_admin/ui/toast_title.rb +21 -0
- data/app/components/ruby_ui_admin/ui.rb +12 -0
- data/app/components/ruby_ui_admin/views/action.rb +37 -0
- data/app/components/ruby_ui_admin/views/action_form.rb +104 -0
- data/app/components/ruby_ui_admin/views/action_trigger.rb +148 -0
- data/app/components/ruby_ui_admin/views/actions_menu.rb +34 -0
- data/app/components/ruby_ui_admin/views/base.rb +301 -0
- data/app/components/ruby_ui_admin/views/card_component.rb +69 -0
- data/app/components/ruby_ui_admin/views/dashboard.rb +40 -0
- data/app/components/ruby_ui_admin/views/docs.rb +91 -0
- data/app/components/ruby_ui_admin/views/field_input.rb +156 -0
- data/app/components/ruby_ui_admin/views/field_value.rb +301 -0
- data/app/components/ruby_ui_admin/views/filter_bar.rb +111 -0
- data/app/components/ruby_ui_admin/views/form.rb +151 -0
- data/app/components/ruby_ui_admin/views/home.rb +35 -0
- data/app/components/ruby_ui_admin/views/index.rb +324 -0
- data/app/components/ruby_ui_admin/views/path_helpers.rb +36 -0
- data/app/components/ruby_ui_admin/views/rails_helpers.rb +15 -0
- data/app/components/ruby_ui_admin/views/show.rb +77 -0
- data/app/components/ruby_ui_admin/views/show_fields.rb +37 -0
- data/app/components/ruby_ui_admin/views/show_tab.rb +58 -0
- data/app/components/ruby_ui_admin/views/structure_renderer.rb +148 -0
- data/app/components/ruby_ui_admin/views/translation.rb +12 -0
- data/app/controllers/ruby_ui_admin/actions_controller.rb +162 -0
- data/app/controllers/ruby_ui_admin/application_controller.rb +81 -0
- data/app/controllers/ruby_ui_admin/dashboards_controller.rb +12 -0
- data/app/controllers/ruby_ui_admin/docs_controller.rb +166 -0
- data/app/controllers/ruby_ui_admin/home_controller.rb +18 -0
- data/app/controllers/ruby_ui_admin/resources_controller.rb +259 -0
- data/config/locales/ruby_ui_admin.en.yml +59 -0
- data/config/locales/ruby_ui_admin.pt-BR.yml +59 -0
- data/config/routes/dynamic_routes.rb +11 -0
- data/config/routes.rb +23 -0
- data/docs/README.md +53 -0
- data/docs/actions/overview.md +112 -0
- data/docs/authorization/action-policy.md +72 -0
- data/docs/authorization/adapters.md +101 -0
- data/docs/authorization/explicit-mode.md +45 -0
- data/docs/authorization/field-authorization.md +53 -0
- data/docs/authorization/policies.md +62 -0
- data/docs/authorization/scopes.md +48 -0
- data/docs/customization/controllers.md +62 -0
- data/docs/customization/ejecting.md +55 -0
- data/docs/customization/javascript.md +161 -0
- data/docs/customization/theming-rubyui.md +80 -0
- data/docs/dashboards/overview.md +65 -0
- data/docs/fields/overview.md +94 -0
- data/docs/filters/overview.md +73 -0
- data/docs/generators-and-tasks/overview.md +42 -0
- data/docs/getting-started/authentication.md +98 -0
- data/docs/getting-started/configuration.md +174 -0
- data/docs/getting-started/installation.md +201 -0
- data/docs/getting-started/internationalization.md +50 -0
- data/docs/getting-started/practical-guide.md +310 -0
- data/docs/resources/associations.md +66 -0
- data/docs/resources/index-customization.md +68 -0
- data/docs/resources/overview.md +50 -0
- data/docs/resources/scopes.md +54 -0
- data/docs/resources/tabs-panels.md +65 -0
- data/lib/generators/ruby_ui_admin/action/action_generator.rb +21 -0
- data/lib/generators/ruby_ui_admin/action/templates/action.rb.tt +24 -0
- data/lib/generators/ruby_ui_admin/assets/assets_generator.rb +71 -0
- data/lib/generators/ruby_ui_admin/card/card_generator.rb +28 -0
- data/lib/generators/ruby_ui_admin/card/templates/chart_card.rb.tt +14 -0
- data/lib/generators/ruby_ui_admin/card/templates/metric_card.rb.tt +14 -0
- data/lib/generators/ruby_ui_admin/card/templates/partial_card.rb.tt +13 -0
- data/lib/generators/ruby_ui_admin/components/components_generator.rb +43 -0
- data/lib/generators/ruby_ui_admin/controller/controller_generator.rb +30 -0
- data/lib/generators/ruby_ui_admin/controller/templates/controller.rb.tt +24 -0
- data/lib/generators/ruby_ui_admin/dashboard/dashboard_generator.rb +18 -0
- data/lib/generators/ruby_ui_admin/dashboard/templates/dashboard.rb.tt +14 -0
- data/lib/generators/ruby_ui_admin/eject/eject_generator.rb +55 -0
- data/lib/generators/ruby_ui_admin/filter/filter_generator.rb +28 -0
- data/lib/generators/ruby_ui_admin/filter/templates/boolean_filter.rb.tt +13 -0
- data/lib/generators/ruby_ui_admin/filter/templates/select_filter.rb.tt +19 -0
- data/lib/generators/ruby_ui_admin/filter/templates/text_filter.rb.tt +13 -0
- data/lib/generators/ruby_ui_admin/install/install_generator.rb +62 -0
- data/lib/generators/ruby_ui_admin/install/templates/initializer.rb.tt +41 -0
- data/lib/generators/ruby_ui_admin/locales/locales_generator.rb +22 -0
- data/lib/generators/ruby_ui_admin/policy/policy_generator.rb +18 -0
- data/lib/generators/ruby_ui_admin/policy/templates/policy.rb.tt +24 -0
- data/lib/generators/ruby_ui_admin/resource/resource_generator.rb +84 -0
- data/lib/generators/ruby_ui_admin/resource/templates/resource.rb.tt +15 -0
- data/lib/generators/ruby_ui_admin/scope/scope_generator.rb +18 -0
- data/lib/generators/ruby_ui_admin/scope/templates/scope.rb.tt +11 -0
- data/lib/ruby_ui_admin/authorization/action_policy_adapter.rb +69 -0
- data/lib/ruby_ui_admin/authorization/adapter.rb +80 -0
- data/lib/ruby_ui_admin/authorization/can_can_can_adapter.rb +70 -0
- data/lib/ruby_ui_admin/authorization/pundit_adapter.rb +86 -0
- data/lib/ruby_ui_admin/authorization.rb +40 -0
- data/lib/ruby_ui_admin/base_action.rb +211 -0
- data/lib/ruby_ui_admin/base_dashboard.rb +70 -0
- data/lib/ruby_ui_admin/base_policy.rb +38 -0
- data/lib/ruby_ui_admin/base_resource.rb +503 -0
- data/lib/ruby_ui_admin/cards/base_card.rb +59 -0
- data/lib/ruby_ui_admin/cards/chart_card.rb +16 -0
- data/lib/ruby_ui_admin/cards/metric_card.rb +22 -0
- data/lib/ruby_ui_admin/cards/partial_card.rb +14 -0
- data/lib/ruby_ui_admin/configuration.rb +177 -0
- data/lib/ruby_ui_admin/current.rb +20 -0
- data/lib/ruby_ui_admin/dashboard_manager.rb +52 -0
- data/lib/ruby_ui_admin/engine.rb +80 -0
- data/lib/ruby_ui_admin/execution_context.rb +36 -0
- data/lib/ruby_ui_admin/fields/association_field.rb +67 -0
- data/lib/ruby_ui_admin/fields/badge_field.rb +19 -0
- data/lib/ruby_ui_admin/fields/base_field.rb +208 -0
- data/lib/ruby_ui_admin/fields/belongs_to_field.rb +49 -0
- data/lib/ruby_ui_admin/fields/boolean_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/boolean_group_field.rb +54 -0
- data/lib/ruby_ui_admin/fields/code_field.rb +18 -0
- data/lib/ruby_ui_admin/fields/date_field.rb +34 -0
- data/lib/ruby_ui_admin/fields/date_time_field.rb +37 -0
- data/lib/ruby_ui_admin/fields/field_manager.rb +27 -0
- data/lib/ruby_ui_admin/fields/file_field.rb +67 -0
- data/lib/ruby_ui_admin/fields/files_field.rb +62 -0
- data/lib/ruby_ui_admin/fields/has_and_belongs_to_many_field.rb +9 -0
- data/lib/ruby_ui_admin/fields/has_many_field.rb +24 -0
- data/lib/ruby_ui_admin/fields/has_one_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/hidden_field.rb +14 -0
- data/lib/ruby_ui_admin/fields/id_field.rb +19 -0
- data/lib/ruby_ui_admin/fields/key_value_field.rb +41 -0
- data/lib/ruby_ui_admin/fields/number_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/password_field.rb +21 -0
- data/lib/ruby_ui_admin/fields/record_link_field.rb +45 -0
- data/lib/ruby_ui_admin/fields/select_field.rb +71 -0
- data/lib/ruby_ui_admin/fields/status_field.rb +21 -0
- data/lib/ruby_ui_admin/fields/text_field.rb +14 -0
- data/lib/ruby_ui_admin/fields/textarea_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/url_field.rb +21 -0
- data/lib/ruby_ui_admin/filters/base_filter.rb +81 -0
- data/lib/ruby_ui_admin/filters/boolean_filter.rb +9 -0
- data/lib/ruby_ui_admin/filters/multiple_select_filter.rb +11 -0
- data/lib/ruby_ui_admin/filters/select_filter.rb +9 -0
- data/lib/ruby_ui_admin/filters/text_filter.rb +9 -0
- data/lib/ruby_ui_admin/menu/builder.rb +94 -0
- data/lib/ruby_ui_admin/resource_manager.rb +82 -0
- data/lib/ruby_ui_admin/scopes/base_scope.rb +107 -0
- data/lib/ruby_ui_admin/services/authorization_service.rb +38 -0
- data/lib/ruby_ui_admin/structure.rb +40 -0
- data/lib/ruby_ui_admin/tailwind_source.rb +65 -0
- data/lib/ruby_ui_admin/version.rb +5 -0
- data/lib/ruby_ui_admin/view.rb +34 -0
- data/lib/ruby_ui_admin.rb +110 -0
- data/lib/tasks/ruby_ui_admin.rake +71 -0
- data/public/ruby-ui-admin-assets/application.css +2 -0
- data/public/ruby-ui-admin-assets/controllers/index.js +36 -0
- data/public/ruby-ui-admin-assets/controllers/rua--bulk-select_controller.js +19 -0
- data/public/ruby-ui-admin-assets/controllers/rua--confirm_controller.js +51 -0
- data/public/ruby-ui-admin-assets/controllers/rua--dialog_controller.js +55 -0
- data/public/ruby-ui-admin-assets/controllers/rua--row-link_controller.js +16 -0
- data/public/ruby-ui-admin-assets/controllers/rua--tabs_controller.js +36 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--combobox_controller.js +197 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--sheet_content_controller.js +7 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--sheet_controller.js +15 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--sidebar_controller.js +67 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--toast_controller.js +151 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--toaster_controller.js +314 -0
- data/public/ruby-ui-admin-assets/vendor/stimulus.js +2567 -0
- data/public/ruby-ui-admin-assets/vendor/turbo.js +7178 -0
- metadata +379 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
> **The admin renders *your app's* RubyUI components and uses *your app's* assets.** So setup is
|
|
4
|
+
> mostly about pointing the admin at your existing RubyUI + Tailwind + JS bundler. If your app
|
|
5
|
+
> already uses RubyUI, most of this is already in place.
|
|
6
|
+
|
|
7
|
+
## 1. Add the gems
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# Gemfile
|
|
11
|
+
gem "ruby_ui_admin"
|
|
12
|
+
|
|
13
|
+
# Prerequisite — the UI is built from your app's RubyUI components.
|
|
14
|
+
gem "ruby_ui", github: "ruby-ui/ruby_ui", require: false
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bundle install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`ruby_ui_admin` depends on `phlex-rails`, `tailwind_merge`, `action_policy`, `pagy` and
|
|
22
|
+
`turbo-rails` (Bundler installs them automatically). It also expects a **Tailwind build** and a
|
|
23
|
+
**JavaScript bundler or importmap** in your app — the same setup RubyUI itself needs.
|
|
24
|
+
|
|
25
|
+
## 2. Install RubyUI and generate its components
|
|
26
|
+
|
|
27
|
+
If your app doesn't already use RubyUI, install it and generate the components the admin uses —
|
|
28
|
+
at minimum: `card`, `badge`, `button`, `link`, `typography` (provides `InlineLink`), `checkbox`,
|
|
29
|
+
`input`, `textarea`, `table`, `sidebar`, `sheet`, `combobox`.
|
|
30
|
+
|
|
31
|
+
> **Run `phlex:install` first.** RubyUI's installer only runs `phlex:install` (which creates
|
|
32
|
+
> `app/components/base.rb`) when `phlex-rails` isn't already in the bundle. Because `ruby_ui_admin`
|
|
33
|
+
> depends on `phlex-rails`, it skips that step and then aborts when `app/components/base.rb` is
|
|
34
|
+
> missing — leaving `RubyUI::Base` ungenerated (and `tw-animate-css` uninstalled), which breaks boot
|
|
35
|
+
> when you generate components. Running `phlex:install` yourself first avoids the whole chain.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bin/rails generate phlex:install # creates app/components/base.rb (see note above)
|
|
39
|
+
bin/rails generate ruby_ui:install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> `ruby_ui:install` **overwrites** your Tailwind entry CSS behind an interactive prompt — run it on
|
|
43
|
+
> its own (not pasted inside a larger block) and answer `Y`, then re-add your customizations (e.g. the
|
|
44
|
+
> `@source` line below). On **Tailwind v4** (tailwindcss-rails 3+, the current default) that file is
|
|
45
|
+
> `app/assets/tailwind/application.css`; older setups use
|
|
46
|
+
> `app/assets/stylesheets/application.tailwind.css`. It also installs `tw-animate-css`;
|
|
47
|
+
> if a later CSS build errors on it, add it manually (`yarn add tw-animate-css` / importmap pin) and
|
|
48
|
+
> ensure your CSS imports it.
|
|
49
|
+
|
|
50
|
+
Then generate the components the admin renders. The gem knows which ones it needs, so let it drive
|
|
51
|
+
RubyUI's generator for you (safe to re-run — existing components are skipped):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bin/rails generate ruby_ui_admin:components
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
<details><summary>What it runs (and why not one command)</summary>
|
|
58
|
+
|
|
59
|
+
RubyUI's generator takes **one component per command**, and **`InlineLink` is part of the
|
|
60
|
+
`Typography` group** (a bare `ruby_ui:component InlineLink` fails silently and the admin's index
|
|
61
|
+
later breaks, since it renders `RubyUI::InlineLink`). The generator loops over exactly the set the
|
|
62
|
+
admin uses:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
for c in Card Badge Button Checkbox Combobox Typography Link Input Textarea Table Sidebar Sheet; do
|
|
66
|
+
bin/rails generate ruby_ui:component $c --skip
|
|
67
|
+
done
|
|
68
|
+
# (or `bin/rails generate ruby_ui:component all` to generate everything)
|
|
69
|
+
```
|
|
70
|
+
</details>
|
|
71
|
+
|
|
72
|
+
This drops the components into `app/components/ruby_ui/*` (as `RubyUI::*`) and copies RubyUI's
|
|
73
|
+
Stimulus controllers into `app/javascript/controllers/ruby_ui/*`. **You own these files.**
|
|
74
|
+
|
|
75
|
+
## 3. Wire the assets
|
|
76
|
+
|
|
77
|
+
The admin doesn't ship CSS/JS — it reuses yours.
|
|
78
|
+
|
|
79
|
+
**a. Tailwind** — your build must generate the utility classes the admin's views use. Rather than
|
|
80
|
+
`@source`-ing the gem's `.rb` files (their absolute path differs between dev/CI/deploy, so it can't
|
|
81
|
+
be committed), extract the class inventory into your **own** app tree and source that:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
bin/rails ruby_ui_admin:tailwind_source
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
It writes `app/assets/tailwind/ruby_ui_admin_classes.html` (the classes scanned from the admin's
|
|
88
|
+
views) and prints the line to add to your Tailwind entry CSS:
|
|
89
|
+
|
|
90
|
+
```css
|
|
91
|
+
/* Tailwind v4: app/assets/tailwind/application.css (v3: app/assets/stylesheets/application.tailwind.css) */
|
|
92
|
+
@source "./ruby_ui_admin_classes.html";
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Commit the generated file** — that stable, relative path works in every environment. **Re-run the
|
|
96
|
+
task after upgrading the gem** so new classes are picked up. Also make sure your RubyUI theme defines
|
|
97
|
+
the `success` / `warning` design tokens (the admin's badges/toasts use them — RubyUI ships them).
|
|
98
|
+
|
|
99
|
+
**b. Stimulus controllers** — the admin needs two sets registered in your Stimulus app:
|
|
100
|
+
|
|
101
|
+
1. **The admin's own `rua--*` controllers** (`tabs`, `dialog`, `bulk-select`, `row-link`,
|
|
102
|
+
`confirm`).
|
|
103
|
+
2. **`ruby-ui--toaster` + `ruby-ui--toast`** — the admin renders RubyUI's **Toast** for flash
|
|
104
|
+
messages **even if your app doesn't use RubyUI Toast**, so you likely won't have these from
|
|
105
|
+
step 2. (The other `ruby-ui--*` it uses — combobox, sheet, sidebar — *do* come from your own
|
|
106
|
+
RubyUI in step 2.)
|
|
107
|
+
|
|
108
|
+
The gem ships both sets, flat, under `public/ruby-ui-admin-assets/controllers/` (the toaster
|
|
109
|
+
controller is patched for server-rendered flash toasts — use the gem's copy), alongside an
|
|
110
|
+
`index.js` that imports and **registers each one under the identifier its markup expects**. Run the
|
|
111
|
+
generator — it does the right thing for your JS setup and prints the **one line** you add:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
bin/rails generate ruby_ui_admin:assets
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Bundler (esbuild / jsbundling / vite).** It copies the directory to `app/javascript/ruby_ui_admin/`
|
|
118
|
+
(a sibling of `controllers/`, so `eagerLoadControllersFrom("controllers", …)` doesn't re-register
|
|
119
|
+
them under bogus names). The bundler inlines `index.js`'s relative imports, so:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
// app/javascript/application.js
|
|
123
|
+
import "./ruby_ui_admin"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Importmap + Propshaft.** It does **not** copy — copied files get content-digested, and `index.js`'s
|
|
127
|
+
relative `./x.js` imports would then resolve to undigested URLs Propshaft won't serve (404). Instead
|
|
128
|
+
it pins the copy the engine already serves **undigested** (via Rack::Static), where relative imports
|
|
129
|
+
work and `@hotwired/stimulus`/`@hotwired/turbo` resolve through your own import map:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
# config/importmap.rb
|
|
133
|
+
pin "ruby_ui_admin", to: "/ruby-ui-admin-assets/controllers/index.js"
|
|
134
|
+
# then in app/javascript/application.js: import "ruby_ui_admin"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Either way that single import registers every controller (`rua--*`, `ruby-ui--toaster`/`ruby-ui--toast`,
|
|
138
|
+
plus combobox/sheet/sidebar) — **no `stimulus:manifest:update`**. Rebuild your JS (bundler) afterwards.
|
|
139
|
+
Don't hand-translate the files into `controllers/rua/…` folders — the manifest would derive the wrong
|
|
140
|
+
identifiers.
|
|
141
|
+
|
|
142
|
+
> If flash toasts don't appear after an action, it's almost always the `ruby-ui--toaster` /
|
|
143
|
+
> `ruby-ui--toast` controllers missing from your bundle — re-run the generator and re-add the import.
|
|
144
|
+
|
|
145
|
+
RubyUI's combobox/popover controllers depend on `@floating-ui/dom` — already present if you use
|
|
146
|
+
RubyUI's combobox.
|
|
147
|
+
|
|
148
|
+
## 4. Mount the engine
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
# config/routes.rb
|
|
152
|
+
Rails.application.routes.draw do
|
|
153
|
+
mount_ruby_ui_admin at: "/admin"
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Draws a RESTful route set for every resource you define (`resources_posts_path`,
|
|
158
|
+
`new_resources_post_path`, `edit_resources_post_path`, …).
|
|
159
|
+
|
|
160
|
+
## 5. Configure
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
# config/initializers/ruby_ui_admin.rb
|
|
164
|
+
RubyUIAdmin.configure do |config|
|
|
165
|
+
config.app_name = "My Admin"
|
|
166
|
+
config.per_page = 24
|
|
167
|
+
|
|
168
|
+
# Resolve the signed-in admin (the block runs in the controller context).
|
|
169
|
+
config.current_user_method { current_user }
|
|
170
|
+
|
|
171
|
+
# Optional gate run before every admin request.
|
|
172
|
+
config.authenticate_with { redirect_to main_app.login_path unless current_user&.admin? }
|
|
173
|
+
|
|
174
|
+
config.authorization_client = :action_policy
|
|
175
|
+
config.explicit_authorization = false
|
|
176
|
+
|
|
177
|
+
# REQUIRED — the admin layout's <head>. Point it at your app's compiled CSS + JS (the same
|
|
178
|
+
# tags your application layout uses). Evaluated in the Rails view context.
|
|
179
|
+
config.head_assets = lambda do
|
|
180
|
+
safe_join([
|
|
181
|
+
stylesheet_link_tag("application", "data-turbo-track": "reload"),
|
|
182
|
+
# importmap host: use `javascript_importmap_tags` instead of the line below.
|
|
183
|
+
javascript_include_tag("application", "data-turbo-track": "reload", defer: true)
|
|
184
|
+
])
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
> Without `config.head_assets`, the admin pages render with **no styling and no JavaScript**.
|
|
190
|
+
|
|
191
|
+
## 6. Define a resource
|
|
192
|
+
|
|
193
|
+
Create `app/ruby_ui_admin/resources/post.rb` (see [Resources → Overview](../resources/overview.md)).
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
`bin/rails generate ruby_ui_admin:install` scaffolds steps **4–5** (the initializer + mount), and
|
|
198
|
+
`ruby_ui_admin:assets` copies the Stimulus controllers from step **3**. The rest of steps **2–3**
|
|
199
|
+
(RubyUI components + Tailwind `@source`) depend on your app's bundler/Tailwind setup and are
|
|
200
|
+
manual — see [Theming with RubyUI](../customization/theming-rubyui.md) and
|
|
201
|
+
[JavaScript](../customization/javascript.md).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Internationalization (i18n)
|
|
2
|
+
|
|
3
|
+
The admin is localized through Rails i18n. Each admin request runs inside
|
|
4
|
+
`I18n.with_locale(config.locale)` when a locale is configured.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
RubyUIAdmin.configure do |config|
|
|
8
|
+
config.locale = :"pt-BR"
|
|
9
|
+
# or resolve per request from your controller, e.g. config.locale = I18n.locale
|
|
10
|
+
end
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
There are two layers of translation:
|
|
14
|
+
|
|
15
|
+
## 1. Framework strings (bundled)
|
|
16
|
+
|
|
17
|
+
Built-in UI text — buttons, table headers, the empty state, pagination, boolean badges — is
|
|
18
|
+
translated under the `ruby_ui_admin.*` namespace. The gem ships **English** and
|
|
19
|
+
**Brazilian Portuguese** out of the box, auto-loaded into `I18n`.
|
|
20
|
+
|
|
21
|
+
Copy them into your app to edit or add locales:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
rails g ruby_ui_admin:locales # -> config/locales/ruby_ui_admin.en.yml, ruby_ui_admin.pt-BR.yml
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Add another language by creating `config/locales/ruby_ui_admin.<locale>.yml` with the same keys.
|
|
28
|
+
|
|
29
|
+
## 2. Domain labels (your app's translations)
|
|
30
|
+
|
|
31
|
+
Resource and field labels use standard Rails i18n, so they read from **your** locale files:
|
|
32
|
+
|
|
33
|
+
- **Field labels** use `Model.human_attribute_name`, i.e. `activerecord.attributes.<model>.<attr>`.
|
|
34
|
+
- They fall back to a humanized name when there's no translation.
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
# config/locales/pt-BR.yml
|
|
38
|
+
"pt-BR":
|
|
39
|
+
activerecord:
|
|
40
|
+
attributes:
|
|
41
|
+
post:
|
|
42
|
+
title: "Título"
|
|
43
|
+
published: "Publicado"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
With the locale set to `pt-BR`, the Post resource's `title` field renders as “Título”.
|
|
47
|
+
|
|
48
|
+
> Action names are plain strings you set with `self.name`; localize them by setting the name
|
|
49
|
+
> from a translation in your action (e.g. `self.name = I18n.t("...")` is evaluated at load —
|
|
50
|
+
> prefer a fixed string or your own lookup if you need per-request locales).
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Practical guide: a working admin from scratch
|
|
2
|
+
|
|
3
|
+
This is a copy‑paste walkthrough that takes a brand‑new Rails app to a functional admin with
|
|
4
|
+
two associated models, configured resources, and a couple of filters. It uses a small
|
|
5
|
+
**Bookshelf** domain: an `Author` that `has_many :books`.
|
|
6
|
+
|
|
7
|
+
The demo skips authentication and authorization so it "just works" — see the
|
|
8
|
+
[Authentication](authentication.md) and [Authorization](../authorization/action-policy.md)
|
|
9
|
+
docs to wire those up for real.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Create the Rails app
|
|
14
|
+
|
|
15
|
+
The admin renders RubyUI (Phlex) components styled with Tailwind, so start the app with a
|
|
16
|
+
Tailwind build and a JS bundler:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
rails new bookshelf --css tailwind --javascript esbuild
|
|
20
|
+
cd bookshelf
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 2. Add the gem
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
# Gemfile
|
|
27
|
+
gem "ruby_ui_admin"
|
|
28
|
+
|
|
29
|
+
# The admin's UI is built from your app's RubyUI components.
|
|
30
|
+
gem "ruby_ui", require: false
|
|
31
|
+
|
|
32
|
+
# Optional: renders the gem's docs at /admin/docs in development (see step 9).
|
|
33
|
+
group :development do
|
|
34
|
+
gem "kramdown"
|
|
35
|
+
gem "kramdown-parser-gfm"
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> Until it's published to RubyGems, point at a local checkout or Git instead, e.g.
|
|
40
|
+
> `gem "ruby_ui_admin", path: "../ruby_ui_admin"` or `gem "ruby_ui_admin", github: "you/ruby_ui_admin"`.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bundle install
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Generate RubyUI and the components the admin uses (they land in `app/components/ruby_ui/*` and
|
|
47
|
+
their Stimulus controllers in `app/javascript/controllers/ruby_ui/*` — you own them).
|
|
48
|
+
|
|
49
|
+
> **Run `phlex:install` first.** RubyUI's installer only runs `phlex:install` (which creates
|
|
50
|
+
> `app/components/base.rb`) when `phlex-rails` isn't already in the bundle. Because `ruby_ui_admin`
|
|
51
|
+
> depends on `phlex-rails`, the installer skips that step, then aborts when it can't find
|
|
52
|
+
> `app/components/base.rb` — leaving `RubyUI::Base` ungenerated and breaking boot. Running
|
|
53
|
+
> `phlex:install` yourself first sidesteps it.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
bin/rails generate phlex:install # creates app/components/base.rb (see note above)
|
|
57
|
+
bin/rails generate ruby_ui:install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> RubyUI's `install` overwrites your Tailwind entry CSS with an **interactive prompt** — run it on
|
|
61
|
+
> its own (don't paste a whole block at once) and answer `Y`, then re-add any customizations. On
|
|
62
|
+
> Tailwind v4 (the current default) that file is `app/assets/tailwind/application.css`; older setups
|
|
63
|
+
> use `app/assets/stylesheets/application.tailwind.css`. If the CSS build later errors on
|
|
64
|
+
> `tw-animate-css`, install it (`yarn add tw-animate-css` or pin it for importmap) and make sure your
|
|
65
|
+
> CSS imports it — RubyUI's installer adds this for you when it completes successfully.
|
|
66
|
+
|
|
67
|
+
Then generate the components the admin uses. The gem knows the set, so let it drive RubyUI's
|
|
68
|
+
generator (safe to re-run — existing components are skipped):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
bin/rails generate ruby_ui_admin:components
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
> Under the hood it loops over the set the admin renders, one per command (RubyUI's generator takes
|
|
75
|
+
> one at a time), using the `Typography` group for `InlineLink` — a bare `ruby_ui:component
|
|
76
|
+
> InlineLink` fails silently and the index would break later. You can also run
|
|
77
|
+
> `bin/rails generate ruby_ui:component all` to generate everything.
|
|
78
|
+
|
|
79
|
+
## 3. Generate two scaffolds with an association
|
|
80
|
+
|
|
81
|
+
`author:references` makes `Book` belong to an `Author` (adds the foreign key and the
|
|
82
|
+
`belongs_to :author`).
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
bin/rails generate scaffold Author name:string email:string
|
|
86
|
+
bin/rails generate scaffold Book title:string published:boolean author:references
|
|
87
|
+
bin/rails db:migrate
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 4. Complete the association
|
|
91
|
+
|
|
92
|
+
The scaffold added `belongs_to :author` to `Book`. Add the other side to `Author`:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# app/models/author.rb
|
|
96
|
+
class Author < ApplicationRecord
|
|
97
|
+
has_many :books, dependent: :destroy
|
|
98
|
+
|
|
99
|
+
def to_label = name # used as the record's display label in the admin
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
# app/models/book.rb
|
|
105
|
+
class Book < ApplicationRecord
|
|
106
|
+
belongs_to :author
|
|
107
|
+
|
|
108
|
+
def to_label = title
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 5. Mount and configure the admin
|
|
113
|
+
|
|
114
|
+
Run the install generator — it creates the initializer and mounts the engine at `/admin`:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
bin/rails generate ruby_ui_admin:install
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Then replace the generated initializer with this demo configuration (no auth, no policies):
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
# config/initializers/ruby_ui_admin.rb
|
|
124
|
+
RubyUIAdmin.configure do |config|
|
|
125
|
+
config.app_name = "Bookshelf Admin"
|
|
126
|
+
config.root_path = "/admin"
|
|
127
|
+
config.per_page = 20
|
|
128
|
+
|
|
129
|
+
# This demo has no authentication. Returning nil and disabling authorization lets the
|
|
130
|
+
# admin run without a current_user or any policies. In a real app, wire current_user_method
|
|
131
|
+
# to your auth (e.g. Devise) and set authorization_client = :action_policy + add policies.
|
|
132
|
+
config.current_user_method { nil }
|
|
133
|
+
config.authorization_client = nil
|
|
134
|
+
|
|
135
|
+
# REQUIRED — point the admin's <head> at your app's compiled CSS + JS (esbuild + Tailwind here).
|
|
136
|
+
config.head_assets = lambda do
|
|
137
|
+
safe_join([
|
|
138
|
+
stylesheet_link_tag("application", "data-turbo-track": "reload"),
|
|
139
|
+
javascript_include_tag("application", "data-turbo-track": "reload", defer: true)
|
|
140
|
+
])
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Two more bits of wiring so the admin is styled and interactive (see
|
|
146
|
+
[Installation › Wire the assets](installation.md#3-wire-the-assets)):
|
|
147
|
+
|
|
148
|
+
- **Tailwind** — extract the admin's class inventory into your app and source it (the gem path
|
|
149
|
+
varies per environment, so don't hardcode it). Commit the generated file; re-run on gem upgrade:
|
|
150
|
+
```bash
|
|
151
|
+
bin/rails ruby_ui_admin:tailwind_source # writes app/assets/tailwind/ruby_ui_admin_classes.html
|
|
152
|
+
```
|
|
153
|
+
```css
|
|
154
|
+
/* Tailwind v4: app/assets/tailwind/application.css (v3: app/assets/stylesheets/application.tailwind.css) */
|
|
155
|
+
@source "./ruby_ui_admin_classes.html";
|
|
156
|
+
```
|
|
157
|
+
- **Stimulus** — copy the admin's controllers in with the generator, then add the one import it
|
|
158
|
+
prints to `app/javascript/application.js` (for esbuild here, that's `import "./ruby_ui_admin"`):
|
|
159
|
+
```bash
|
|
160
|
+
bin/rails generate ruby_ui_admin:assets
|
|
161
|
+
```
|
|
162
|
+
It brings the admin's own `rua--*` controllers **and** `ruby-ui--toaster`/`ruby-ui--toast` (the
|
|
163
|
+
admin uses RubyUI Toast for flash even though the component list above doesn't include it), all
|
|
164
|
+
registered under the right identifiers — no `stimulus:manifest:update` needed. Rebuild afterwards.
|
|
165
|
+
(On **Importmap + Propshaft** the generator pins the engine-served entrypoint instead of copying —
|
|
166
|
+
see [Installation › Wire the assets](installation.md#3-wire-the-assets).)
|
|
167
|
+
|
|
168
|
+
The generator also added this line to your routes (mount it manually if you skipped the
|
|
169
|
+
generator):
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
# config/routes.rb
|
|
173
|
+
Rails.application.routes.draw do
|
|
174
|
+
mount_ruby_ui_admin at: "/admin"
|
|
175
|
+
|
|
176
|
+
# ...your scaffolded routes (resources :authors, :books) stay as they are
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 6. Define the resources
|
|
181
|
+
|
|
182
|
+
Resources live in `app/ruby_ui_admin/resources/` and are auto‑discovered — no registration
|
|
183
|
+
needed. The class name maps to the model (`Author` → `Author`).
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
# app/ruby_ui_admin/resources/author.rb
|
|
187
|
+
module RubyUIAdmin
|
|
188
|
+
module Resources
|
|
189
|
+
class Author < RubyUIAdmin::BaseResource
|
|
190
|
+
self.title = :name # display label for an Author record
|
|
191
|
+
|
|
192
|
+
def fields
|
|
193
|
+
field :id, as: :id
|
|
194
|
+
field :name, as: :text, link_to_record: true, sortable: true
|
|
195
|
+
field :email, as: :text
|
|
196
|
+
field :books, as: :has_many # listed on the Author's show page
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
# app/ruby_ui_admin/resources/book.rb
|
|
205
|
+
module RubyUIAdmin
|
|
206
|
+
module Resources
|
|
207
|
+
class Book < RubyUIAdmin::BaseResource
|
|
208
|
+
self.title = :title
|
|
209
|
+
self.includes = [:author] # eager-load to avoid N+1 on the index
|
|
210
|
+
|
|
211
|
+
def filters
|
|
212
|
+
filter RubyUIAdmin::Filters::PublishedFilter
|
|
213
|
+
filter RubyUIAdmin::Filters::AuthorFilter
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def fields
|
|
217
|
+
field :title, as: :text, link_to_record: true, sortable: true
|
|
218
|
+
field :published, as: :boolean
|
|
219
|
+
field :author, as: :belongs_to # dropdown on forms, link on show/index
|
|
220
|
+
field :created_at, as: :date_time, only_on: %i[index show]
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## 7. Add a couple of filters
|
|
228
|
+
|
|
229
|
+
Filters live in `app/ruby_ui_admin/filters/` and render above the index table.
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
# app/ruby_ui_admin/filters/published_filter.rb
|
|
233
|
+
module RubyUIAdmin
|
|
234
|
+
module Filters
|
|
235
|
+
class PublishedFilter < RubyUIAdmin::Filters::BooleanFilter
|
|
236
|
+
self.name = "Published"
|
|
237
|
+
|
|
238
|
+
def apply(_request, query, value)
|
|
239
|
+
query.where(published: value == "true")
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
# app/ruby_ui_admin/filters/author_filter.rb
|
|
248
|
+
module RubyUIAdmin
|
|
249
|
+
module Filters
|
|
250
|
+
class AuthorFilter < RubyUIAdmin::Filters::SelectFilter
|
|
251
|
+
self.name = "Author"
|
|
252
|
+
|
|
253
|
+
# options is { value => label }.
|
|
254
|
+
def options
|
|
255
|
+
::Author.order(:name).pluck(:id, :name).to_h
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def apply(_request, query, value)
|
|
259
|
+
query.where(author_id: value)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## 8. Add some data (optional)
|
|
267
|
+
|
|
268
|
+
So the admin isn't empty:
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
# db/seeds.rb
|
|
272
|
+
rowling = Author.create!(name: "J. K. Rowling", email: "jk@example.com")
|
|
273
|
+
tolkien = Author.create!(name: "J. R. R. Tolkien", email: "jrr@example.com")
|
|
274
|
+
|
|
275
|
+
rowling.books.create!(title: "Harry Potter", published: true)
|
|
276
|
+
tolkien.books.create!(title: "The Hobbit", published: true)
|
|
277
|
+
tolkien.books.create!(title: "Unfinished Tales", published: false)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
bin/rails db:seed
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## 9. Run it
|
|
285
|
+
|
|
286
|
+
Use `bin/dev` (not `bin/rails server`) so the esbuild + Tailwind watchers build your assets:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
bin/dev
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Open <http://localhost:3000/admin>. You'll get a styled admin with
|
|
293
|
+
**Authors** and **Books** in the sidebar. On the Books index you'll see the **Published** and
|
|
294
|
+
**Author** filters; opening a Book shows its author as a link, and an Author shows its books.
|
|
295
|
+
|
|
296
|
+
> **Read the docs inside the app.** With the `kramdown` gems from step 2 installed, the admin serves
|
|
297
|
+
> this documentation at <http://localhost:3000/admin/docs> (development only, by default). Without
|
|
298
|
+
> those gems the page shows a notice telling you to add them. See
|
|
299
|
+
> [Configuration › In-app docs browser](configuration.md#in-app-docs-browser).
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Where to go next
|
|
304
|
+
|
|
305
|
+
- Group fields with [tabs & panels](../resources/tabs-panels.md), add [scopes](../resources/scopes.md)
|
|
306
|
+
and [index customization](../resources/index-customization.md).
|
|
307
|
+
- Add [custom actions](../actions/overview.md) (e.g. "Publish selected books").
|
|
308
|
+
- Wire real [authentication](authentication.md) and [authorization](../authorization/action-policy.md)
|
|
309
|
+
(set `authorization_client = :action_policy` and add policies).
|
|
310
|
+
- Browse the full [field catalog](../fields/overview.md) and [filter types](../filters/overview.md).
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Associations
|
|
2
|
+
|
|
3
|
+
Declare associations as fields. They render as links to the related records.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
def fields
|
|
7
|
+
field :user, as: :belongs_to # parent record
|
|
8
|
+
field :profile, as: :has_one
|
|
9
|
+
field :comments, as: :has_many
|
|
10
|
+
field :tags, as: :has_and_belongs_to_many
|
|
11
|
+
end
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Display
|
|
15
|
+
|
|
16
|
+
| Type | Renders |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `belongs_to` | a link to the parent record's show page; an editable select on forms |
|
|
19
|
+
| `has_one` | a link to the associated record's show page |
|
|
20
|
+
| `has_many` / `has_and_belongs_to_many` | a full-width **table** of the related records (capped, with “+N more”) |
|
|
21
|
+
|
|
22
|
+
Links resolve through the related model's registered resource (so a `Comment` row links to
|
|
23
|
+
`/admin/comments/:id`). If no resource is registered for the related model, the label is shown
|
|
24
|
+
without a link.
|
|
25
|
+
|
|
26
|
+
### Collection tables (`has_many` / `has_and_belongs_to_many`)
|
|
27
|
+
|
|
28
|
+
Collection associations render as a RubyUI table whose **columns are the related resource's
|
|
29
|
+
index fields** (so a `comments` association shows the same columns as the Comments index), with
|
|
30
|
+
each row linking to the record's show page. On the show view the table spans the full width,
|
|
31
|
+
with the association name and description above it.
|
|
32
|
+
|
|
33
|
+
Limit and order the columns with the `fields:` option — a list of field ids from the related
|
|
34
|
+
resource:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
field :suppliers, as: :has_many, fields: %i[name identifier state buyer]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Without `fields:`, all of the related resource's index columns are shown. If the related model
|
|
41
|
+
has no registered resource, the table falls back to a single column of record links.
|
|
42
|
+
|
|
43
|
+
By default `has_one`/`has_many`/`has_and_belongs_to_many` appear **only on the show view**
|
|
44
|
+
(they're hidden on index and forms); `belongs_to` appears everywhere. Override with
|
|
45
|
+
`only_on:` / `hide_on:` like any field.
|
|
46
|
+
|
|
47
|
+
Give related models a `to_label` (or `name`/`title`) for friendly link text; otherwise a
|
|
48
|
+
`"<Model> #<id>"` label is used.
|
|
49
|
+
|
|
50
|
+
## Authorization
|
|
51
|
+
|
|
52
|
+
Associations are gated like any field — define `view_<association>?` (or per-view
|
|
53
|
+
`show_<association>?`) on the resource policy:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
class PostPolicy < RubyUIAdmin::BasePolicy
|
|
57
|
+
def view_comments? = user.admin? # hide the comments association from non-admins
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
See [Field-level authorization](../authorization/field-authorization.md) for the full
|
|
62
|
+
resolution rules. As with any field, an undefined rule means the association stays visible.
|
|
63
|
+
|
|
64
|
+
> Inline association **management** (attach/detach controls and pagination within the table)
|
|
65
|
+
> is planned for a later phase. Today collection associations are read-only tables on the show
|
|
66
|
+
> view (capped, with a “+N more” note).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Index customization
|
|
2
|
+
|
|
3
|
+
## Custom header controls (`index_controls`)
|
|
4
|
+
|
|
5
|
+
Add your own buttons/links to the index header with `self.index_controls`, a block rendered
|
|
6
|
+
in the index view:
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
class Post < RubyUIAdmin::BaseResource
|
|
10
|
+
self.index_controls = -> { control_link("Invite buyer", invite_path) }
|
|
11
|
+
end
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The block is evaluated inside the index component, so it has access to:
|
|
15
|
+
|
|
16
|
+
- **`control_link(label, href, variant: :outline)`** — a button-styled link (`:outline` or
|
|
17
|
+
`:primary`). The common case.
|
|
18
|
+
- **`create_button(label: nil)`** — a primary "New" link for this resource.
|
|
19
|
+
- **Phlex element methods** (`a`, `button`, `div`, …) for full control.
|
|
20
|
+
- **Route helpers** via the mounted engine proxy, e.g. `ruby_ui_admin.resources_users_path`.
|
|
21
|
+
- **`@resource`** — the current resource instance.
|
|
22
|
+
|
|
23
|
+
## Per-row controls (`row_controls`)
|
|
24
|
+
|
|
25
|
+
`self.row_controls = ->(record) { ... }` adds controls to each row's actions cell. The block
|
|
26
|
+
receives the row's `record` and is evaluated in the index component, so it has the same helpers
|
|
27
|
+
plus **`show_button(record, label: nil)`** and **`edit_button(record, label: nil)`**:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
self.row_controls = ->(record) { show_button(record, label: "Preview") }
|
|
31
|
+
|
|
32
|
+
# Layout of the controls cell:
|
|
33
|
+
self.row_controls_config = { placement: :right, float: true, show_on_hover: true }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
# Two controls, mixing the helper and raw Phlex:
|
|
40
|
+
self.index_controls = lambda do
|
|
41
|
+
control_link("Export CSV", "/exports/posts.csv")
|
|
42
|
+
a(href: ruby_ui_admin.resources_users_path, class: "text-sm text-primary hover:underline") { "Manage users" }
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Custom controls render alongside the built-in **New** button and any standalone actions.
|
|
47
|
+
|
|
48
|
+
> Use a `lambda`/`proc` with **no arguments** and read `@resource` directly inside the block.
|
|
49
|
+
|
|
50
|
+
## Bulk selection
|
|
51
|
+
|
|
52
|
+
When a resource has bulk actions, the index shows per-row selection checkboxes (plus a
|
|
53
|
+
"select all"). `self.record_selector` (default `true`) controls this — set it to `false` to
|
|
54
|
+
hide the selection column even when bulk actions exist:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
self.record_selector = false
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
With no bulk actions, there's nothing to select, so no checkboxes render regardless.
|
|
61
|
+
|
|
62
|
+
## Reserved options (no effect yet)
|
|
63
|
+
|
|
64
|
+
| Option | Intended purpose |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `self.default_view_type` | Default index layout (`:table`, `:grid`, `:map`). Only `:table` is implemented. |
|
|
67
|
+
|
|
68
|
+
Setting it is harmless; it simply has no effect today.
|