rails_admin_next 1.1.2 → 1.2.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/Gemfile +4 -0
- data/README.md +16 -0
- data/Rakefile +2 -0
- data/docs/UPGRADING.md +463 -0
- data/docs/security.md +67 -0
- data/docs/upstream-parity.md +101 -0
- data/lib/rails_admin_next/version.rb +2 -2
- data/skills/rails-admin-next/SKILL.md +103 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fbd1b1c7b37f7d5d89eb5f9ef44dc086bedf1ecbd47f908c545afbb35e8f8bc1
|
|
4
|
+
data.tar.gz: 9358676100cad7935f07c91a757e27a761fe463e1a2fc6b291f8d8a0dc63df0d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8b999764cff3b8b068b54f5bba5ac2107d9f4545584a76be7764c63b24cfc7135560ce7372fc953bae2aab8a5ef5e3e09735e3e592125b93d21ee70f6f5662a8
|
|
7
|
+
data.tar.gz: 477419b70e7056875a32b0790422a1310423d93cd43d91bc68007e918dace9b30acd3a6ad55c38eff654df59fbbe11bb87e5a8141c669807849897519a2ad8d0
|
data/Gemfile
CHANGED
|
@@ -19,6 +19,10 @@ group :test do
|
|
|
19
19
|
gem "generator_spec"
|
|
20
20
|
gem "image_processing"
|
|
21
21
|
gem "pundit"
|
|
22
|
+
# Pinned within 0.10: Gemfile.lock is not committed here, so CI resolves fresh on every run
|
|
23
|
+
# and no Dependabot PR stages a release first, and the discovery spec drives Hyperdrive's
|
|
24
|
+
# pre-1.0 discovery API, which has renamed keys within 0.x before.
|
|
25
|
+
gem "rails-hyperdrive", "~> 0.10.1", require: false
|
|
22
26
|
gem "rspec-rails"
|
|
23
27
|
gem "rspec-retry"
|
|
24
28
|
gem "ruby-vips"
|
data/README.md
CHANGED
|
@@ -83,6 +83,22 @@ The install generator mounts the engine, writes
|
|
|
83
83
|
`config/initializers/rails_admin_next.rb`, and does nothing else — the engine ships its
|
|
84
84
|
own importmap and CSS, so there is no `yarn add`, no asset build, and no generated pins.
|
|
85
85
|
|
|
86
|
+
### Agent skill
|
|
87
|
+
|
|
88
|
+
RailsAdminNext is a [Rails Hyperdrive][hyperdrive] companion gem. It ships a
|
|
89
|
+
`rails-admin-next` skill that teaches coding agents this fork's API rather than upstream
|
|
90
|
+
RailsAdmin's. In an app running Hyperdrive, sync installs the skill, and each later
|
|
91
|
+
release updates it:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
bin/rails hyperdrive:sync
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The skill points agents at the `docs/*.md` files packaged in the gem, so its references
|
|
98
|
+
always match the installed version.
|
|
99
|
+
|
|
100
|
+
[hyperdrive]: https://github.com/rails-hyperdrive/rails-hyperdrive
|
|
101
|
+
|
|
86
102
|
## Features
|
|
87
103
|
|
|
88
104
|
- CRUD any data with ease
|
data/Rakefile
CHANGED
data/docs/UPGRADING.md
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# Upgrading from rails_admin to rails_admin_next
|
|
2
|
+
|
|
3
|
+
`rails_admin_next` is the modernized fork of `rails_admin`: Rails 8.1 / Ruby 4.0 floor,
|
|
4
|
+
ActiveRecord only, an importmap + Propshaft asset pipeline, a Stimulus + Turbo
|
|
5
|
+
frontend (no jQuery, jQuery-UI, Bootstrap JS, or `@rails/ujs`), vanilla CSS with a
|
|
6
|
+
latest-Chrome/Safari browser floor, and a hardened security posture. This guide covers
|
|
7
|
+
the one-time changes a host app needs.
|
|
8
|
+
|
|
9
|
+
The Ruby namespace is renamed **`RailsAdmin` → `RailsAdminNext`** (and `rails_admin` →
|
|
10
|
+
`rails_admin_next`), so your initializer, engine mount, model macros, and route helpers
|
|
11
|
+
need a mechanical rename. The **authorization contract is deliberately unchanged** — the
|
|
12
|
+
`:access, :rails_admin` subject and the `admin.*` I18n namespace stay exactly as they were,
|
|
13
|
+
so your CanCanCan abilities, Pundit policies, and translations need no edits.
|
|
14
|
+
|
|
15
|
+
## 1. Gemfile
|
|
16
|
+
|
|
17
|
+
Replace the upstream `rails_admin` gem with the fork (distributed via `git:`):
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# Gemfile
|
|
21
|
+
gem 'rails_admin_next', git: 'https://github.com/6temes/rails_admin_next'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Remove the old `gem 'rails_admin'` line, then `bundle install`.
|
|
25
|
+
|
|
26
|
+
## 2. Application code — rename the namespace
|
|
27
|
+
|
|
28
|
+
Rename `RailsAdmin` → `RailsAdminNext` and `rails_admin` → `rails_admin_next` in your
|
|
29
|
+
initializer, mount, per-model blocks, and route-helper calls:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
# config/initializers/rails_admin_next.rb (renamed from config/initializers/rails_admin.rb)
|
|
33
|
+
RailsAdminNext.config do |config|
|
|
34
|
+
config.model User do
|
|
35
|
+
# …
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# DELETE this line if your old initializer had it — the option was removed
|
|
39
|
+
# (there is a single importmap + Propshaft pipeline now; leaving it in place
|
|
40
|
+
# only logs a deprecation warning):
|
|
41
|
+
# config.asset_source = :importmap
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# config/routes.rb
|
|
45
|
+
mount RailsAdminNext::Engine => '/admin', as: 'rails_admin_next'
|
|
46
|
+
|
|
47
|
+
# per-model blocks
|
|
48
|
+
class Team < ApplicationRecord
|
|
49
|
+
rails_admin_next do
|
|
50
|
+
field :name
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# route helpers — these also hide in mailers and regular views
|
|
55
|
+
# (`rails_admin_url(...)` → `rails_admin_next_url(...)`) and inside field blocks
|
|
56
|
+
# (`bindings[:view].rails_admin.show_path(...)` → `bindings[:view].rails_admin_next.show_path(...)`)
|
|
57
|
+
rails_admin_next.dashboard_path
|
|
58
|
+
rails_admin_next.edit_path(model_name: 'team', id: team.id)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Keep the mount alias `rails_admin_next`.** The engine's own views resolve routes through
|
|
62
|
+
the `rails_admin_next` route proxy, so the mount must expose that helper name — either
|
|
63
|
+
`as: 'rails_admin_next'` as above, or no `as:` at all (it defaults to the engine name).
|
|
64
|
+
Aliasing the mount to something else to preserve old helper names (e.g. `as: 'rails_admin'`)
|
|
65
|
+
breaks every engine page at render time with
|
|
66
|
+
`undefined local variable or method 'rails_admin_next'`.
|
|
67
|
+
|
|
68
|
+
Find every host-side call site that needs the rename with:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
grep -rnE 'rails_admin_url|rails_admin_path|\.rails_admin\.' app config
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Running `rails g rails_admin_next:install` writes the new initializer and mount for you.
|
|
75
|
+
|
|
76
|
+
## 3. Icons (`link_icon` / `navigation_icon`)
|
|
77
|
+
|
|
78
|
+
Font Awesome is **no longer shipped** (no icon font, no `font-awesome.css`). Icons now
|
|
79
|
+
render as inline SVG, and the icon options take a **logical icon name** (a Symbol or
|
|
80
|
+
String) instead of a Font Awesome class string:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
# Before
|
|
84
|
+
RailsAdminNext.config do |config|
|
|
85
|
+
config.model Team do
|
|
86
|
+
navigation_icon 'fas fa-trophy'
|
|
87
|
+
end
|
|
88
|
+
config.actions do
|
|
89
|
+
# …
|
|
90
|
+
my_custom_action { link_icon 'fas fa-rocket' }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# After
|
|
95
|
+
RailsAdminNext.config do |config|
|
|
96
|
+
config.model Team do
|
|
97
|
+
navigation_icon :list # a logical name (see the list below)
|
|
98
|
+
end
|
|
99
|
+
config.actions do
|
|
100
|
+
my_custom_action { link_icon :show_in_app }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Available logical names (see `RailsAdminNext::Icons::GLYPHS`):
|
|
106
|
+
|
|
107
|
+
`:dashboard` (alias `:home`), `:list`, `:show` (alias `:info`), `:new` (alias `:add`/`:plus`),
|
|
108
|
+
`:edit` (alias `:pencil`), `:delete` (aliases `:cancel`/`:times`), `:export`,
|
|
109
|
+
`:history` (alias `:book`), `:show_in_app` (alias `:eye`), `:bulk_delete` (alias `:question`),
|
|
110
|
+
`:collapse`, `:expand`, `:calendar`, `:check`, `:minus`, `:refresh` (alias `:sync`),
|
|
111
|
+
`:trash`, `:move_right`, `:move_left`, `:move_up`, `:move_down`.
|
|
112
|
+
|
|
113
|
+
**Legacy Font Awesome strings still given to `link_icon`/`navigation_icon` are handled
|
|
114
|
+
gracefully**: the glyph is best-effort mapped to the nearest logical name and a deprecation
|
|
115
|
+
warning is emitted (`RailsAdminNext.deprecator`); a value that can't be mapped renders no
|
|
116
|
+
icon rather than raising. Migrate to logical names to silence the warning. If you relied on
|
|
117
|
+
Font Awesome classes elsewhere in your own admin views, vendor Font Awesome yourself — the
|
|
118
|
+
engine no longer provides it.
|
|
119
|
+
|
|
120
|
+
## Authorization (no change needed)
|
|
121
|
+
|
|
122
|
+
The authorization contract is held stable — **do not touch your abilities or policies**:
|
|
123
|
+
|
|
124
|
+
- The access subject is `:rails_admin`.
|
|
125
|
+
- Every action's authorization key (`:dashboard`, `:index`, `:show`, `:new`, `:edit`,
|
|
126
|
+
`:export`, `:destroy`, `:history`, `:show_in_app`) is unchanged.
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
# This CanCanCan ability keeps working unchanged:
|
|
130
|
+
can :access, :rails_admin
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The access subject is deliberately frozen and locked by a spec in the gem — a renamed
|
|
134
|
+
subject can fail *open* (a `cannot` rule that no longer matches silently grants access).
|
|
135
|
+
|
|
136
|
+
## Asset prerequisites
|
|
137
|
+
|
|
138
|
+
RailsAdminNext ships its frontend over **importmap + Propshaft** only — there is no
|
|
139
|
+
build step and no Sprockets/Webpacker/Webpack/Vite support. The host app must use:
|
|
140
|
+
|
|
141
|
+
- **Propshaft** (`config.assets.*` via `propshaft`), and
|
|
142
|
+
- **importmap-rails**.
|
|
143
|
+
|
|
144
|
+
Optional:
|
|
145
|
+
|
|
146
|
+
- **Thruster** in front of the app for edge caching of the fingerprinted assets.
|
|
147
|
+
- Set `config.assets.integrity_hash_algorithm` if you want Subresource Integrity on the
|
|
148
|
+
engine's self-hosted modules (off by default; same-origin hosting makes SRI low-value).
|
|
149
|
+
|
|
150
|
+
## Browser support
|
|
151
|
+
|
|
152
|
+
The frontend targets the **latest stable Chrome and Safari only**. This fork is an internal
|
|
153
|
+
tool for teams that control their admin users' browsers; the old "Baseline 2023 /
|
|
154
|
+
Firefox ≥ 111" contract is retired deliberately, and no legacy-browser fallbacks ship. The
|
|
155
|
+
engine's CSS and JS assume `:has()`, `<dialog>`, popovers, CSS nesting, `oklch()`,
|
|
156
|
+
`color-mix()`, `light-dark()`, `@starting-style`, and CSS anchor positioning.
|
|
157
|
+
|
|
158
|
+
Two features are newer still: `closedby="any"` (backdrop light-dismissal of the inline-edit
|
|
159
|
+
dialog) needs Chromium 134+ / Safari 26, and CSS anchor positioning needs Chromium 128+ /
|
|
160
|
+
Safari 26 (dropdown menus are positioned by anchor CSS alone — no JS fallback ships).
|
|
161
|
+
`field-sizing: content` (auto-growing textareas) is a Chromium-only enhancement — other
|
|
162
|
+
browsers keep fixed-height textareas.
|
|
163
|
+
|
|
164
|
+
## Styling: cascade layers and design tokens
|
|
165
|
+
|
|
166
|
+
Bootstrap's SCSS is gone. Styling is hand-maintained vanilla CSS delivered through three
|
|
167
|
+
cascade layers, declared in `rails_admin.css` (the layer names are a documented contract):
|
|
168
|
+
|
|
169
|
+
- `ra.tokens` — design tokens (CSS custom properties)
|
|
170
|
+
- `ra.framework` — the trimmed ex-Bootstrap component CSS
|
|
171
|
+
- `ra.skin` — the engine's own widgets, icons, and admin skin
|
|
172
|
+
|
|
173
|
+
Because all engine CSS is layered, **any unlayered host rule beats it** — a plain rule in
|
|
174
|
+
your own stylesheet overrides the admin without `!important` or specificity games:
|
|
175
|
+
|
|
176
|
+
```css
|
|
177
|
+
/* host stylesheet — no layer, so it wins over everything the engine ships */
|
|
178
|
+
.badge {
|
|
179
|
+
border-radius: 7px;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Supported override tokens (public API)
|
|
184
|
+
|
|
185
|
+
Hosts may override these tokens (unlayered, e.g. `:root { --ra-primary: #663399; }`) and
|
|
186
|
+
every derived color — button hover/active states, focus rings, alert tints, table tints,
|
|
187
|
+
links — follows automatically:
|
|
188
|
+
|
|
189
|
+
| Token | Role |
|
|
190
|
+
| --- | --- |
|
|
191
|
+
| `--ra-primary`, `--ra-secondary`, `--ra-success`, `--ra-info`, `--ra-warning`, `--ra-danger`, `--ra-light`, `--ra-dark` | brand and status colors |
|
|
192
|
+
| `--ra-body-bg`, `--ra-body-color` | body surface and text |
|
|
193
|
+
| `--ra-link-color` | link color (defaults to a tint of `--ra-primary`) |
|
|
194
|
+
|
|
195
|
+
A single-color override applies to both light and dark schemes; hosts that want a tuned dark
|
|
196
|
+
mode should override with their own pair:
|
|
197
|
+
|
|
198
|
+
```css
|
|
199
|
+
:root {
|
|
200
|
+
--ra-primary: light-dark(#663399, #a97fd6);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Every other `--ra-*` name is internal and may change without notice.**
|
|
205
|
+
|
|
206
|
+
### Dark mode and the `color_scheme` knob
|
|
207
|
+
|
|
208
|
+
The admin now follows the OS color-scheme preference automatically: every color token is a
|
|
209
|
+
`light-dark()` pair, resolved against a `<meta name="color-scheme">` the layout renders from
|
|
210
|
+
the new config knob:
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
RailsAdminNext.config do |config|
|
|
214
|
+
config.color_scheme = :auto # default; :light or :dark pins the scheme
|
|
215
|
+
end
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Pin `:light` to keep the old always-light rendering. Two deliberate exceptions: the navbar is
|
|
219
|
+
scheme-locked (`navbar_css_classes` express host intent — `navbar-dark bg-primary` stays a
|
|
220
|
+
dark navbar under both schemes), and the Trix (ActionText) editor stays light-only for now.
|
|
221
|
+
|
|
222
|
+
### Token mapping: `--bs-*` → `--ra-*`
|
|
223
|
+
|
|
224
|
+
The Bootstrap `--bs-*` custom properties are gone. Renamed with the same meaning — read or
|
|
225
|
+
override the new name:
|
|
226
|
+
|
|
227
|
+
| Old | New | Note |
|
|
228
|
+
| --- | --- | --- |
|
|
229
|
+
| `--bs-body-bg` | `--ra-body-bg` | |
|
|
230
|
+
| `--bs-body-color` | `--ra-body-color` | |
|
|
231
|
+
| `--bs-body-font-family` | `--ra-body-font-family` | |
|
|
232
|
+
| `--bs-body-font-size` | `--ra-body-font-size` | |
|
|
233
|
+
| `--bs-body-font-weight` | `--ra-body-font-weight` | |
|
|
234
|
+
| `--bs-body-line-height` | `--ra-body-line-height` | |
|
|
235
|
+
| `--bs-body-text-align` | `--ra-body-text-align` | still undeclared by default; set it to opt in, exactly like Bootstrap's knob |
|
|
236
|
+
| `--bs-breadcrumb-divider` | `--ra-breadcrumb-divider` | |
|
|
237
|
+
| `--bs-font-monospace` | `--ra-font-monospace` | |
|
|
238
|
+
| `--bs-font-sans-serif` | `--ra-font-sans-serif` | |
|
|
239
|
+
| `--bs-gradient` | `--ra-gradient` | |
|
|
240
|
+
| `--bs-gutter-x` / `--bs-gutter-y` | `--ra-gutter-x` / `--ra-gutter-y` | grid-scoped |
|
|
241
|
+
| `--bs-table-bg` | `--ra-table-bg` | table-scoped; likewise `-accent-bg`, `-striped-bg`, `-striped-color`, `-active-bg`, `-active-color`, `-hover-bg`, `-hover-color` |
|
|
242
|
+
|
|
243
|
+
Renamed with a value-space change (number → percentage):
|
|
244
|
+
|
|
245
|
+
| Old | New |
|
|
246
|
+
| --- | --- |
|
|
247
|
+
| `--bs-bg-opacity: 0.5` | `--ra-bg-opacity: 50%` |
|
|
248
|
+
| `--bs-text-opacity: 0.5` | `--ra-text-opacity: 50%` |
|
|
249
|
+
|
|
250
|
+
Previously declared but consumed by nothing — now live semantic tokens (overriding the
|
|
251
|
+
`--bs-*` name never did anything; the `--ra-*` name works): `--bs-primary` →
|
|
252
|
+
`--ra-primary`, `--bs-secondary` → `--ra-secondary`, `--bs-success` → `--ra-success`,
|
|
253
|
+
`--bs-info` → `--ra-info`, `--bs-warning` → `--ra-warning`, `--bs-danger` → `--ra-danger`,
|
|
254
|
+
`--bs-light` → `--ra-light`, `--bs-dark` → `--ra-dark`.
|
|
255
|
+
|
|
256
|
+
Removed — replaced by derivation (never override these; override the base token instead):
|
|
257
|
+
the whole `-rgb` family (`--bs-primary-rgb` … `--bs-dark-rgb`, `--bs-white-rgb`,
|
|
258
|
+
`--bs-black-rgb`, `--bs-body-color-rgb`, `--bs-body-bg-rgb`) is gone; utilities now derive
|
|
259
|
+
translucency via `color-mix(in srgb, var(--ra-*) <pct>, transparent)`.
|
|
260
|
+
|
|
261
|
+
Removed without replacement (declared but never consumed): the raw color ladder
|
|
262
|
+
(`--bs-blue`, `--bs-indigo`, `--bs-purple`, `--bs-pink`, `--bs-red`, `--bs-orange`,
|
|
263
|
+
`--bs-yellow`, `--bs-green`, `--bs-teal`, `--bs-cyan` — the code-pink role of `--bs-pink`
|
|
264
|
+
lives on as the internal `--ra-code-color`), `--bs-white`, `--bs-gray`, `--bs-gray-dark`,
|
|
265
|
+
`--bs-gray-100` … `--bs-gray-900` (the grays the CSS actually uses exist as internal
|
|
266
|
+
`--ra-gray-*` tokens), and `--bs-position` (set by `.dropdown-menu-end` for Bootstrap's
|
|
267
|
+
dropdown JS, which this engine does not ship).
|
|
268
|
+
|
|
269
|
+
### Removed component CSS
|
|
270
|
+
|
|
271
|
+
The flattened Bootstrap CSS was trimmed to what the engine actually renders. These component
|
|
272
|
+
families were deleted as unreachable:
|
|
273
|
+
|
|
274
|
+
| Removed family | If you relied on it |
|
|
275
|
+
| --- | --- |
|
|
276
|
+
| Carousel | No engine view renders one; ship your own carousel CSS/JS in custom actions. |
|
|
277
|
+
| Tooltip (`.tooltip`, `.bs-tooltip-*`) | Bootstrap tooltips needed Bootstrap JS, which the engine does not ship; use the native `title` attribute or your own library. |
|
|
278
|
+
| Popover component (`.popover`, `.bs-popover-*`) | Same as tooltips. (The native `popover` *attribute* is what the engine now uses for its dropdown menus.) |
|
|
279
|
+
| Toasts | Engine flash messages render as alerts; `alert-*` stays supported. |
|
|
280
|
+
| Offcanvas | Never emitted by engine views. |
|
|
281
|
+
| Accordion | Sidebar groups are native `<details>`/`<summary>` now. |
|
|
282
|
+
| Spinners (`.spinner-border`, `.spinner-grow`) | The engine's loading indicator is the `#loading` badge. |
|
|
283
|
+
| Placeholders (loading skeletons) | Never emitted by engine views. |
|
|
284
|
+
| Floating labels (`.form-floating`) | Engine forms use stacked labels. |
|
|
285
|
+
| `.form-range` | No engine form renders a range input. |
|
|
286
|
+
| Form validation (`.is-valid` / `.is-invalid` / `*-feedback` / `*-tooltip`) | Engine forms render server-side errors through their own `.error` + `.help-inline.text-danger` markup, which is kept. |
|
|
287
|
+
| `.form-check` / `.form-switch` wrapper styling | Checkboxes/radios are native, tinted via `accent-color`; `.form-check-input` itself is still styled. |
|
|
288
|
+
| jQuery-UI chrome | jQuery-UI is gone. |
|
|
289
|
+
| wysihtml5 editor chrome | The editor was dropped; use ActionText/Trix. |
|
|
290
|
+
| `.collapsing` | Bootstrap JS's collapse-transition class; sidebar collapse is a native `<details>`. |
|
|
291
|
+
| `[data-bs-popper]` rules | Popper.js is not shipped. |
|
|
292
|
+
| `.dropup` / `.dropend` / `.dropstart` | Engine menus only drop down (and flip above the toggle automatically near the viewport edge). |
|
|
293
|
+
| Responsive tiers `sm` / `lg` / `xl` / `xxl` | The `.col-sm-*` grid columns engine forms emit survive; the responsive display/spacing/text utilities and `container-*` tiers are gone (a couple of `md` utilities remain). |
|
|
294
|
+
| Print utilities (`.d-print-*`) | Gone; add your own print CSS if you print admin pages. |
|
|
295
|
+
|
|
296
|
+
### The styled vocabulary (keep-list)
|
|
297
|
+
|
|
298
|
+
The stylesheet keeps a deliberate keep-list even where no engine view currently emits the
|
|
299
|
+
class: vocabularies that are host-facing configuration API remain styled and supported —
|
|
300
|
+
|
|
301
|
+
- `.navbar-light` / `.navbar-dark` — the `navbar_css_classes` value space,
|
|
302
|
+
- `alert-*` — the `flash_alert_class` value space,
|
|
303
|
+
- `bg-*` utilities — badges, indicators, and the host navbar,
|
|
304
|
+
- `.badge` colors,
|
|
305
|
+
- `table-*` contextual rows — `row_css_class` conventions.
|
|
306
|
+
|
|
307
|
+
Class names the stylesheet does not style are out of contract and may stay unstyled.
|
|
308
|
+
|
|
309
|
+
## Markup changes
|
|
310
|
+
|
|
311
|
+
Three widgets moved from Bootstrap markup to native platform primitives. Hand-rolled host CSS
|
|
312
|
+
or JS that targeted the old DOM needs updating — `.modal.fade`, `.dropdown .show`, and the
|
|
313
|
+
sidebar's `.collapse` no longer appear:
|
|
314
|
+
|
|
315
|
+
| Widget | Was | Now |
|
|
316
|
+
| --- | --- | --- |
|
|
317
|
+
| Inline-edit modal | `.modal.fade` + `.modal-dialog` divs | native `<dialog id="modal" closedby="any">` |
|
|
318
|
+
| Dropdown menus (filters, bulk actions) | `<a role="button">` toggles + `.dropdown .show` | `<button popovertarget>` toggles + `popover="auto"` menus, positioned with CSS anchor positioning |
|
|
319
|
+
| Sidebar navigation groups | `.collapse` + Bootstrap collapse classes | native `<details>`/`<summary>` disclosures |
|
|
320
|
+
|
|
321
|
+
## Required `belongs_to` associations
|
|
322
|
+
|
|
323
|
+
A `belongs_to` your model actually requires is now rendered as required. Previously only a
|
|
324
|
+
presence or numericality validator on the association or its foreign key was detected, and
|
|
325
|
+
Rails registers the validator it adds for a required `belongs_to` with an `if:` condition that
|
|
326
|
+
the detection skipped.
|
|
327
|
+
|
|
328
|
+
`belongs_to_required_by_default` has been on since `load_defaults 5.0`. On `load_defaults 7.1`
|
|
329
|
+
or later Rails also sets `belongs_to_required_validates_foreign_key = false`, which is what makes
|
|
330
|
+
the presence validator conditional and invisible to the old detection — so this affects every
|
|
331
|
+
plain `belongs_to` whose foreign key carries no validator of its own. For those fields:
|
|
332
|
+
|
|
333
|
+
- the help text reads "Required." instead of "Optional."
|
|
334
|
+
- a plain association select carries `required`, so the browser blocks submission while it is
|
|
335
|
+
empty; polymorphic association fields change help text and filter operators only, because
|
|
336
|
+
their partial does not apply the field's HTML attributes
|
|
337
|
+
- the association select no longer offers its clear-selection entry
|
|
338
|
+
- the index filter loses the "is present" and "is blank" operators
|
|
339
|
+
|
|
340
|
+
An association declared with `default:` is left optional, since Rails fills it in a
|
|
341
|
+
`before_validation` callback. If a model fills the association server-side some other way, the
|
|
342
|
+
browser will now block a form the server would have accepted. Mark the field optional:
|
|
343
|
+
|
|
344
|
+
```ruby
|
|
345
|
+
RailsAdminNext.config Order do
|
|
346
|
+
edit do
|
|
347
|
+
field :customer do
|
|
348
|
+
optional true
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Nested forms hide the link back to the parent
|
|
355
|
+
|
|
356
|
+
A nested subform no longer renders the child's association back to the record being edited.
|
|
357
|
+
Editing a `FieldTest` with `has_one :comment, as: :commentable` used to ask, from inside that
|
|
358
|
+
form, which record the comment belonged to — a choice the parent had already made and that the
|
|
359
|
+
nested save overwrote anyway.
|
|
360
|
+
|
|
361
|
+
This widened along with the association's notion of an inverse. `inverse_of` now answers with
|
|
362
|
+
the declared `inverse_of:`, otherwise the inverse ActiveRecord resolves for the reflection,
|
|
363
|
+
otherwise a polymorphic `as:`. It previously answered only the declared option, so a pair that
|
|
364
|
+
declared nothing on either side — the conventional case Rails resolves on its own — reported no
|
|
365
|
+
inverse and the back-reference stayed on screen. Combined with required `belongs_to` detection
|
|
366
|
+
above, that field could be marked required while being one only the nested save can fill.
|
|
367
|
+
|
|
368
|
+
Two consequences worth checking in a host app:
|
|
369
|
+
|
|
370
|
+
- A subform for a pair that relies on Rails' automatic inverse detection now hides one more
|
|
371
|
+
field than before, and its key is no longer permitted inside `<assoc>_attributes[...]` either.
|
|
372
|
+
Only at nested depth: editing the child on its own still permits its own association, which is
|
|
373
|
+
a legitimate way to set it.
|
|
374
|
+
- `field.inverse_of` in your own configuration blocks now returns a symbol for associations
|
|
375
|
+
that previously returned `nil`.
|
|
376
|
+
|
|
377
|
+
**Narrowing the allowlist is a security fix, for collections.** Saving a singular association re-applies
|
|
378
|
+
the owner to the child, so a submitted back-reference never survived there. A collection does not:
|
|
379
|
+
`assign_nested_attributes_for_collection_association` assigns straight onto an already-associated
|
|
380
|
+
record, so a crafted back-reference inside `<assoc>_attributes[n]` moved that child onto a
|
|
381
|
+
different parent — one the submitter may have had no rights over. Dropping the key from the
|
|
382
|
+
allowlist closes it, because it never reaches the assignment.
|
|
383
|
+
|
|
384
|
+
`inverse_of: false` on the association is honoured, as in Rails. To keep the back-reference
|
|
385
|
+
visible — and permitted — for one field without touching the model, disable the parent field's
|
|
386
|
+
inverse. On a collection that re-opens the re-parenting described above, so scope it to the one
|
|
387
|
+
field that needs it:
|
|
388
|
+
|
|
389
|
+
```ruby
|
|
390
|
+
RailsAdminNext.config FieldTest do
|
|
391
|
+
edit do
|
|
392
|
+
field :comment do
|
|
393
|
+
inverse_of { false }
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Note the block: a bare `inverse_of nil` reads the option rather than setting it.
|
|
400
|
+
|
|
401
|
+
## The show action answers HTML by default
|
|
402
|
+
|
|
403
|
+
`show` used to declare its JSON format before its HTML one, so a request carrying no
|
|
404
|
+
`Accept` header — `curl`, a bare `fetch()`, most HTTP client libraries — arrived as
|
|
405
|
+
`*/*`, matched every registered type, and got JSON back. Every other action answered
|
|
406
|
+
HTML for the same request.
|
|
407
|
+
|
|
408
|
+
It now declares HTML first. A client that explicitly sends `Accept: application/json`,
|
|
409
|
+
or requests the `.json` extension, is unaffected.
|
|
410
|
+
|
|
411
|
+
If you have a script relying on the unheadered JSON response, send the header or use
|
|
412
|
+
`.json`:
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
curl -H 'Accept: application/json' https://example.com/admin/player/1
|
|
416
|
+
curl https://example.com/admin/player/1.json
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Note that the JSON branch renders the whole record, including columns the model config
|
|
420
|
+
hides from the show view.
|
|
421
|
+
## Removed: options deprecated before RailsAdmin 3.0
|
|
422
|
+
|
|
423
|
+
These six were deprecated upstream between 2019-07 and 2022-01 and had no effect for
|
|
424
|
+
years. They are gone rather than carried into this gem's 1.0:
|
|
425
|
+
|
|
426
|
+
| Removed | What to do |
|
|
427
|
+
| --- | --- |
|
|
428
|
+
| `config.total_columns_width=` | Nothing. It had no effect. |
|
|
429
|
+
| `config.sidescroll=` | Nothing. Horizontal scroll with frozen columns is always on. |
|
|
430
|
+
| `list.sidescroll` | Nothing. Same as above. The field-level `sticky?` still controls which columns freeze. |
|
|
431
|
+
| `list.sort_reverse` | Nothing. It had no effect. The field-level `sort_reverse?` is unrelated and unaffected. |
|
|
432
|
+
| `momentjs_format` | Nothing. Date/time fields are native HTML5 inputs and render per the browser locale. |
|
|
433
|
+
| `eager_load?` | Rename to `eager_load`. |
|
|
434
|
+
| `delete_key` (`:multiple_file_upload`) | Rename to `delete_value`. |
|
|
435
|
+
|
|
436
|
+
`register_deprecated_instance_option` itself stays — extensions and host apps use it for
|
|
437
|
+
their own options.
|
|
438
|
+
|
|
439
|
+
## Dropped support
|
|
440
|
+
|
|
441
|
+
These are intentionally gone — migrate off them before upgrading:
|
|
442
|
+
|
|
443
|
+
- **Mongoid** — RailsAdminNext is ActiveRecord only.
|
|
444
|
+
- **Sprockets / Webpacker / Webpack / Vite** — importmap + Propshaft only.
|
|
445
|
+
- **Standalone WYSIWYG/code editor field types** (`bootstrap-wysihtml5`, `froala`, and the
|
|
446
|
+
cdnjs-backed `:ck_editor` / `:code_mirror` / `:simple_mde`) — they loaded JS/CSS from cdnjs.
|
|
447
|
+
Migrate those fields to `:action_text` (self-hosted Trix rich text) or a plain `:text` textarea
|
|
448
|
+
instead. The engine now has **zero external CDN** usage, default and opt-in.
|
|
449
|
+
- **Non-ActiveStorage attachment libraries** (CarrierWave, Paperclip, Shrine, Dragonfly,
|
|
450
|
+
Refile) — RailsAdminNext detects **ActiveStorage** (and ActionText/Trix) attachments only.
|
|
451
|
+
Migrate file/image columns to ActiveStorage before upgrading. Image variants are processed
|
|
452
|
+
with **libvips** via `image_processing` + `ruby-vips` (set
|
|
453
|
+
`config.active_storage.variant_processor = :vips`); install the libvips system library
|
|
454
|
+
(`brew install vips` / `apt-get install libvips-dev`) — ImageMagick is no longer required.
|
|
455
|
+
- **Configurable app branding (`main_app_name`)** — the navbar brand and page `<title>` are
|
|
456
|
+
hardcoded to "Rails AdminNext"; the config option is gone. Fork
|
|
457
|
+
`app/views/layouts/rails_admin_next/_navigation.html.erb` and `content.html.erb` in your
|
|
458
|
+
host app if you need custom branding.
|
|
459
|
+
|
|
460
|
+
## History / audit table
|
|
461
|
+
|
|
462
|
+
The gem owns **no** history table. Auditing remains host/PaperTrail territory, so there is
|
|
463
|
+
no gem migration to run — your existing `rails_admin_histories` table (if any) is unaffected.
|
data/docs/security.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Security posture
|
|
2
|
+
|
|
3
|
+
The admin is a privileged surface. This engine ships the request-forgery, framing, and
|
|
4
|
+
destructive-action controls itself; a few things are left for the host to opt into.
|
|
5
|
+
|
|
6
|
+
## Always on
|
|
7
|
+
|
|
8
|
+
- **CSRF.** `protect_from_forgery(with: :exception)` is hardcoded. A non-GET request without a
|
|
9
|
+
valid token is rejected (`ActionController::InvalidAuthenticityToken` → HTTP 422). There is no
|
|
10
|
+
host-configurable forgery option to weaken it.
|
|
11
|
+
- **Clickjacking.** Every admin response carries `X-Frame-Options: SAMEORIGIN`.
|
|
12
|
+
- **Export.** The CSV/JSON/XML data stream requires a non-GET, CSRF-protected request; `GET` only
|
|
13
|
+
renders the export form, so the table can't be exfiltrated via a crafted link.
|
|
14
|
+
- **Destructive actions.** `delete` and `bulk_delete` render a confirmation page and additionally
|
|
15
|
+
carry `data-turbo-confirm` on the destructive submit (Turbo's confirmation, not `@rails/ujs`).
|
|
16
|
+
- **Mass assignment.** Submitted attributes are sliced to the model config's `visible_fields`
|
|
17
|
+
before `permit!`, at the top level and at every nested depth, regardless of the submitted
|
|
18
|
+
nested-attributes cardinality (hash or array). At nested depth the slice also drops the child's
|
|
19
|
+
link back to the record being edited — the field the subform does not draw — so a crafted
|
|
20
|
+
back-reference cannot move a nested record onto a different parent.
|
|
21
|
+
|
|
22
|
+
## Content Security Policy (opt-in)
|
|
23
|
+
|
|
24
|
+
The engine enforces **no** CSP by default. When you opt in, it applies the policy per-request to
|
|
25
|
+
admin responses only (never to the host app) and threads a per-request nonce onto the tags it
|
|
26
|
+
emits — the importmap JSON, the import entry point and its module preloads, the index
|
|
27
|
+
column-width `<style>`, and the engine's stylesheet and style-preload links — so a `:self` +
|
|
28
|
+
nonce policy does not block the admin's own assets.
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
# config/initializers/rails_admin_next.rb
|
|
32
|
+
RailsAdminNext.config do |config|
|
|
33
|
+
config.content_security_policy do |policy|
|
|
34
|
+
policy.default_src :self
|
|
35
|
+
policy.script_src :self
|
|
36
|
+
policy.style_src :self, :unsafe_inline # see note below
|
|
37
|
+
policy.img_src :self, :data
|
|
38
|
+
policy.font_src :self
|
|
39
|
+
policy.connect_src :self
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Tune it without breaking pages first by reporting only:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
RailsAdminNext.config do |config|
|
|
48
|
+
config.content_security_policy(report_only: true) do |policy|
|
|
49
|
+
policy.default_src :self
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
> **Note on `style-src`.** The admin still renders five inline `style="…"` attributes, which a CSP
|
|
55
|
+
> nonce cannot cover (nonces apply to `<script>`/`<style>` elements, not style attributes), so a
|
|
56
|
+
> `style-src` directive needs `:unsafe_inline` for now. The engine's own `<style>` element (the
|
|
57
|
+
> index column-width block) is nonced. The remaining attributes:
|
|
58
|
+
>
|
|
59
|
+
> - the `#loading` badge's positioning in the layout, and the delete-notice `<li>` spacing —
|
|
60
|
+
> static values, removable by moving them into the stylesheet;
|
|
61
|
+
> - the file-upload delete toggle's initial `display:none` — conditional initial state, removable
|
|
62
|
+
> by switching to the `hidden` attribute;
|
|
63
|
+
> - the dashboard's `.progress` margin (static) and each progress bar's `width: N%` (a per-row
|
|
64
|
+
> dynamic value — the one that genuinely resists removal; it would need the nonced-`<style>`
|
|
65
|
+
> route or a native `<progress>` element).
|
|
66
|
+
>
|
|
67
|
+
> Once those five are reauthored, `:unsafe_inline` can be dropped.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Upstream parity
|
|
2
|
+
|
|
3
|
+
Assessed through upstream `897ffc13` (2026-09-13). Fork point: `d8e0809e` (2025-09-14).
|
|
4
|
+
|
|
5
|
+
Divergence from the fork point: 787 files, +20,899 / −73,565 lines.
|
|
6
|
+
|
|
7
|
+
## How this fork tracks upstream
|
|
8
|
+
|
|
9
|
+
RailsAdminNext is a hard fork of [railsadminteam/rails_admin](https://github.com/railsadminteam/rails_admin). It is **never merged or rebased from upstream**, and it cannot be: the fork deleted whole subsystems upstream still ships — the Mongoid adapter, the npm/webpack build, jQuery and `@rails/ujs`, Bootstrap's JavaScript, Appraisal, CarrierWave. Replaying this fork's history onto a moving upstream would conflict with every commit touching any of them, by construction.
|
|
10
|
+
|
|
11
|
+
Instead, upstream work is absorbed by **reimplementation**: read the upstream commit, judge whether the underlying problem exists here, and write the fix in this codebase's terms. A port therefore need not — and often should not — match upstream's diff. Deliberate divergences are recorded below so they are not "corrected" back later.
|
|
12
|
+
|
|
13
|
+
This means `git rev-list --count upstream/master...master` is not a progress measure. It counts commits never merged, so it does not fall when work is absorbed and it grows every time upstream moves. **This ledger is the state.** The watermark above records what has been assessed; the table records the verdict for each commit, including the ones deliberately skipped.
|
|
14
|
+
|
|
15
|
+
## Verdicts
|
|
16
|
+
|
|
17
|
+
Commits are listed newest first, as `git log` reports them.
|
|
18
|
+
|
|
19
|
+
| upstream | subject | verdict | ours |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| `897ffc13` | Fix _discard leaking into SQL for multi-select enum filters | adapted — server half only; the client half was already stronger here (disabled options), so upstream's JS was deliberately not ported | [#36](https://github.com/6temes/rails_admin_next/pull/36) |
|
|
22
|
+
| `11f214f6` | Remove config options deprecated before RailsAdmin 3.0 | adapted — the deprecator half already shipped; this removed the six options | [#40](https://github.com/6temes/rails_admin_next/pull/40) |
|
|
23
|
+
| `cbea80b1` | Match GET in http_methods case-insensitively | adapted — `Actions::Base#linkable?` | [#38](https://github.com/6temes/rails_admin_next/pull/38) |
|
|
24
|
+
| `9336ec71` | Drop Proc bounds from a field's length validation | adapted — widened beyond upstream to Symbol bounds, which crash identically | [#35](https://github.com/6temes/rails_admin_next/pull/35) |
|
|
25
|
+
| `331841ea` | Remove duplicate global assignment of window.jQuery | n/a — jQuery removed | — |
|
|
26
|
+
| `71852cd8` | Fix malformed rubocop directive comment | n/a — measured: standardrb clean, and no `has_option?` directive here to malform | — |
|
|
27
|
+
| `cfc17e0d` | Pin json to < 3 for the test suite | n/a — dependency churn, Dependabot's job | — |
|
|
28
|
+
| `316c299d` | Change default response format of the show action to HTML | adapted | [#39](https://github.com/6temes/rails_admin_next/pull/39) |
|
|
29
|
+
| `0f4f765e` | Use the same widget which works for nullable booleans also for non nullable | declined — cosmetic consistency change; alters every boolean field in every host app and fixes no defect | — |
|
|
30
|
+
| `495cc864` | Filter ferrum 0.18's console stack-frame log lines | adapted — landed before the cuprite bump that would trigger it | [#37](https://github.com/6temes/rails_admin_next/pull/37) |
|
|
31
|
+
| `2eed28e1` | Fully specify all ESM imports with an extension | n/a — measured: every relative import in `src/` already carries `.js`; browser-native ESM requires it | — |
|
|
32
|
+
| `e8dec57a` | Upgrade @hotwired/turbo-rails to resolve security warning | n/a — turbo comes from the `turbo-rails` gem (2.0.23), which Dependabot reads | — |
|
|
33
|
+
| `07b2066a` | Replace `asset-url` with `url` in Sass files | n/a — no Sass; one hand-owned CSS file | — |
|
|
34
|
+
| `a0946615` | Drop support for Rails 6.x and Ruby 2.5/2.6 | n/a — this fork is Rails ~> 8.1 / Ruby >= 4.0.5 only | — |
|
|
35
|
+
| `0543cb3f` | Revert npm package.json version ahead of actual publish | n/a — no npm package | — |
|
|
36
|
+
| `cf7b112e` | Run Prettier on migrated docs | n/a — upstream's vendored wiki | — |
|
|
37
|
+
| `1c2b92a3` | Rewrite wiki cross-links to relative docs/ links | n/a — upstream's vendored wiki | — |
|
|
38
|
+
| `70ca765e` | Merge wiki history into docs/ | n/a — upstream vendored its GitHub wiki; this fork ships its own docs | — |
|
|
39
|
+
| `0690a5e6` | Start development for 4.0.0 | n/a — upstream release chore | — |
|
|
40
|
+
| `50fbbcd9` | Remove @babel/runtime as a direct dependency | n/a — no npm build here | — |
|
|
41
|
+
| `e4ad87b7` | Replace deprecated jQuery functions with native ones | n/a — jQuery removed | — |
|
|
42
|
+
| `77e5d0bc` | Improve intermittent autocomplete test failures due to stale element | n/a — measured: the widget stamps and aborts its own requests, and each query renders once | [#18](https://github.com/6temes/rails_admin_next/issues/18) |
|
|
43
|
+
| `26bb8763` | Declare JavaScript package is a module | n/a — no npm package | — |
|
|
44
|
+
| `336845f4` | Remove string mutation | adapted | [#16](https://github.com/6temes/rails_admin_next/pull/16) |
|
|
45
|
+
| `b72badeb` | Add a "sass" entry point to package.json | n/a — no npm/webpack build | — |
|
|
46
|
+
| `9b21e3ba` | Update rails/ujs NPM package | n/a — UJS removed, Turbo replaces it | — |
|
|
47
|
+
| `ca2d0954` | Use a non-beta trix package in dummy_app | n/a — trix is self-hosted from `action_text-trix`, not an npm pin | — |
|
|
48
|
+
| `1f681b48` | Add nonce to css/js tags | adapted — `csp_meta_tag` deliberately left out, see below | [#22](https://github.com/6temes/rails_admin_next/pull/22) |
|
|
49
|
+
| `54a23401` | Add belongs_to optional/required support to `required?` | adapted | [#13](https://github.com/6temes/rails_admin_next/pull/13) |
|
|
50
|
+
| `c18592a1` | Draw routes before the suite goes multi-threaded | adapted — premise corrected, see PR | [#17](https://github.com/6temes/rails_admin_next/pull/17) |
|
|
51
|
+
| `91e9cca8` | Resolve the inverse of a polymorphic association | adapted — 3 divergences, see below | [#14](https://github.com/6temes/rails_admin_next/pull/14) |
|
|
52
|
+
| `8bffeb61` | Pin vite-plugin-ruby below 5.2 | n/a — no vite | — |
|
|
53
|
+
| `9f03412d` | Fix RuboCop offense in main_controller_spec | n/a — linting is Standard Ruby | — |
|
|
54
|
+
| `17b64251` | Avoid carrierwave versions that pull in mimemagic | n/a — CarrierWave removed | — |
|
|
55
|
+
| `703e4894` | Add Rails 8.1 support | n/a — this fork is Rails ~> 8.1 only | — |
|
|
56
|
+
| `9881e254` | Add test for editing a nested one widget item | adapted — driven through this fork's markup | [#15](https://github.com/6temes/rails_admin_next/pull/15) |
|
|
57
|
+
| `47d913fe` | Fix flaky locale spec for Datetimepicker widget | n/a — native HTML5 date/time inputs replaced the widget | — |
|
|
58
|
+
| `ec685453` | Don't auto-require the appraisal gem | n/a — Appraisal removed | — |
|
|
59
|
+
|
|
60
|
+
## Standing divergences
|
|
61
|
+
|
|
62
|
+
Where a port deliberately differs from upstream's own patch. Each is correct *for this fork*; changing one to match upstream would reintroduce a bug.
|
|
63
|
+
|
|
64
|
+
**`Association#inverse_of`** (from `91e9cca8`) resolves the declared `inverse_of:`, then the inverse ActiveRecord resolves for the reflection, then a polymorphic `as:` — and honours `inverse_of: false`. Upstream reads only the declared option and `as`.
|
|
65
|
+
|
|
66
|
+
- The middle step exists because this fork is ActiveRecord-only. Upstream's adapter also serves Mongoid, which resolves no inverse, so `as` is the only source available to both. Without the middle step, a conventional pair that declares nothing on either side reports no inverse and keeps rendering its back-reference.
|
|
67
|
+
- Honouring `inverse_of: false` matters because `false.try(:to_sym)` is `nil`, so upstream's `|| as` silently reinstates an inverse the host explicitly declined.
|
|
68
|
+
- The order is load-bearing. Reading the declared option first is what keeps a polymorphic `belongs_to` that declares an inverse from raising inside Rails' reflection, where the class cannot be computed.
|
|
69
|
+
|
|
70
|
+
**`FormBuilder#nested_field_association?`** (from the same commit) limits its hoisted name-match branch to association fields. Upstream's hoist is unconstrained. A polymorphic `as:` is never validated against the child class, so without the limit a plain column sharing that name can be suppressed from a subform.
|
|
71
|
+
|
|
72
|
+
**`sanitize_params_for!`** drops the parent's inverse from the nested allowlist, which upstream does not do at all — its permitted keys are derived purely from the visible fields. Hiding the back-reference without narrowing the allowlist left a collection subform re-parentable through a crafted `<assoc>_attributes[n]`, since `assign_nested_attributes_for_collection_association` assigns straight onto an already-associated record ([#24](https://github.com/6temes/rails_admin_next/pull/24)).
|
|
73
|
+
|
|
74
|
+
**`Field::Base#valid_length` drops Symbol bounds as well as Proc ones** (from `9336ec71`), and drops them only from the three keys that get compared (`LENGTH_BOUNDS`). Upstream drops every Proc-valued entry and no Symbols at all.
|
|
75
|
+
|
|
76
|
+
- ActiveModel sanctions four bound types and resolves Proc and Symbol against the record at validation time, so neither can be compared against the column limit. Measured here: `minimum: :some_method` raised the same `ArgumentError` upstream's fix addresses for Procs, and a surviving `maximum: :some_method` rendered "Length up to max_len" as help text.
|
|
77
|
+
- The narrowing to three keys exists because `valid_length` is a `register_instance_option` that host apps read. Rejecting every Proc, as upstream does, would also strip `if:`/`unless:` conditions from the hash.
|
|
78
|
+
- `:in` and `:within` need no entry: ActiveModel collapses them to `:minimum`/`:maximum` at validator construction, and a Proc passed as `:in` raises at declaration time.
|
|
79
|
+
|
|
80
|
+
**`StatementBuilder#to_statement` excludes `between` from the `_discard` strip** (from `897ffc13`), and strips only when the array actually contained the sentinel. Upstream strips unconditionally.
|
|
81
|
+
|
|
82
|
+
- `between` reads its value array by index, so removing an element shifts the range ends. Measured: `["ignored", "_discard", "20"]` became a `>= 20` filter where it had been `<= 20`.
|
|
83
|
+
- An array that arrived empty is not an array the strip emptied. A date filter legitimately submits `{v: [], o: "today"}`, carrying its meaning in the operator alone; returning early on any empty array breaks it.
|
|
84
|
+
- Upstream's client-side half is deliberately not ported. `filter_box_controller` here sets `disabled` on the sentinel options in multi-select mode, not merely `hidden`, and a disabled `<option>` is excluded from form submission — a stronger guarantee than upstream's deselect.
|
|
85
|
+
|
|
86
|
+
**The engine's stylesheet and style-preload tags carry the CSP nonce** (from `1f681b48`), but `csp_meta_tag` does not ship. Turbo reads that meta to re-nonce scripts it re-activates, and this engine renders no body script for it to re-activate; the nonce generator here is also per-request random, which a cached Turbo snapshot would carry stale. Adding the meta wants that generator decision alongside it.
|
|
87
|
+
|
|
88
|
+
## Assessing the next batch
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git fetch upstream
|
|
92
|
+
git log --oneline --first-parent 897ffc13..upstream/master
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`--first-parent` is load-bearing. Upstream folds whole histories in from time to time — `70ca765e` merged its GitHub wiki into `docs/`, and a plain `git log` across it turns an 18-commit batch into 1301, the extra 1283 being someone else's markdown. Check anything that is a merge (`git log -1 --format='%P %s' <sha>`) for what it actually brought in: vendored docs are one row, a merged feature branch needs its commits triaged individually.
|
|
96
|
+
|
|
97
|
+
Triage each commit by the **underlying problem**, not by the API, selector, or library its diff happens to touch. State the problem without naming any of them; if that sentence describes something possible here, the commit is relevant even when its diff is entirely in code this fork deleted.
|
|
98
|
+
|
|
99
|
+
`77e5d0bc` above is the cautionary case, and it cuts both ways. It was first dismissed as jQuery-UI-only, which was the wrong reason — the race it describes is stated in terms of a selector, and dismissing it on that vocabulary is exactly the mistake the rule above exists to prevent. But the CI failure that seemed to confirm the re-triage turned out to be an unrelated bug: an example reading the database before the server had answered ([#23](https://github.com/6temes/rails_admin_next/pull/23)). The widget itself stamps and aborts its requests, and instrumenting it showed one render per query, so the upstream race has no counterpart here.
|
|
100
|
+
|
|
101
|
+
**A coincident failure is not confirmation.** Look past a commit's vocabulary to decide whether it *could* apply, then measure this fork's own code before concluding that it does.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-admin-next
|
|
3
|
+
description: Configure or extend the rails_admin_next admin in a host Rails app (models, fields, custom actions and field types, CanCanCan/Pundit authorization, PaperTrail history, theming, CSP). Use instead of upstream RailsAdmin knowledge, which is wrong for this fork.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rails_admin_next
|
|
7
|
+
|
|
8
|
+
rails_admin_next is a hard fork of RailsAdmin: ActiveRecord only, Rails 8.1, importmap + Propshaft, Turbo + Stimulus, no build step. Most of the per-model DSL is unchanged from upstream, but the namespace, the asset stack and the frontend are not. Code written from upstream RailsAdmin memory is usually wrong here.
|
|
9
|
+
|
|
10
|
+
## Read the installed gem first
|
|
11
|
+
|
|
12
|
+
Every `<gem>/` path below is relative to the directory this prints:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bundle info --path rails_admin_next
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Read the version the app actually bundles before answering:
|
|
19
|
+
|
|
20
|
+
- `<gem>/docs/UPGRADING.md` lists every difference from upstream RailsAdmin.
|
|
21
|
+
- `<gem>/docs/security.md` covers what the engine enforces and what the host opts into.
|
|
22
|
+
- `<gem>/docs/upstream-parity.md` has a "Standing divergences" section on behavior that deliberately differs from upstream patches.
|
|
23
|
+
- `<gem>/lib/rails_admin_next/config/` is the DSL itself. When this skill and the source disagree, the source wins.
|
|
24
|
+
|
|
25
|
+
The upstream RailsAdmin wiki is still accurate for model, group and field options that the fork leaves unchanged. Check it against the source above before relying on it.
|
|
26
|
+
|
|
27
|
+
## Naming: what was renamed, what was not
|
|
28
|
+
|
|
29
|
+
Renamed (write the new form):
|
|
30
|
+
|
|
31
|
+
- Ruby namespace `RailsAdminNext`, configured with `RailsAdminNext.config do |config| ... end` in config/initializers/rails_admin_next.rb.
|
|
32
|
+
- Mount: `mount RailsAdminNext::Engine => "/admin", as: "rails_admin_next"`. Keep the `rails_admin_next` alias, because the engine's views call that route proxy.
|
|
33
|
+
- Per-model macro `rails_admin_next do ... end`.
|
|
34
|
+
- Route helpers `rails_admin_next.dashboard_path`, `rails_admin_next_url`, and `bindings[:view].rails_admin_next` inside field blocks.
|
|
35
|
+
|
|
36
|
+
Not renamed (leave as is):
|
|
37
|
+
|
|
38
|
+
- The authorization subject is still `:rails_admin`. CanCanCan abilities grant `can :access, :rails_admin`. Never rewrite it to `:rails_admin_next`, which fails authorization.
|
|
39
|
+
- Action authorization keys (`:dashboard`, `:index`, `:show`, `:new`, `:edit`, `:export`, `:destroy`, `:history`, `:show_in_app`).
|
|
40
|
+
- The `admin.*` I18n namespace.
|
|
41
|
+
|
|
42
|
+
`bin/rails g rails_admin_next:install` writes the initializer and the mount.
|
|
43
|
+
|
|
44
|
+
## Models, sections and fields
|
|
45
|
+
|
|
46
|
+
Configure a model either inline with the `rails_admin_next do ... end` macro or in the initializer with `config.model "Team" do ... end`. Both take the upstream DSL: `list`, `show`, `edit`, `create`, `update`, `export`, `nested` and `modal` sections, plus `field`, `configure`, `exclude_fields`, `group` and `navigation_icon`. Global options live on `config`, for example `included_models`, `excluded_models`, `default_items_per_page` and `label_methods`.
|
|
47
|
+
|
|
48
|
+
Field types live in `<gem>/lib/rails_admin_next/config/fields/types/`. A custom type subclasses one of them and registers with `RailsAdminNext::Config::Fields::Types.register(MyType)`. Register it under a name of your own, or the registration replaces the built-in type.
|
|
49
|
+
|
|
50
|
+
Read the "Required `belongs_to` associations" and "Nested forms hide the link back to the parent" sections of `<gem>/docs/UPGRADING.md` before changing association fields. Both behave differently from upstream.
|
|
51
|
+
|
|
52
|
+
## Actions
|
|
53
|
+
|
|
54
|
+
Pick and order actions in the initializer:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
RailsAdminNext.config do |config|
|
|
58
|
+
config.actions do
|
|
59
|
+
dashboard
|
|
60
|
+
index
|
|
61
|
+
new
|
|
62
|
+
export
|
|
63
|
+
bulk_delete
|
|
64
|
+
show
|
|
65
|
+
edit
|
|
66
|
+
delete
|
|
67
|
+
show_in_app
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
A custom action subclasses `RailsAdminNext::Config::Actions::Base`, registers with `RailsAdminNext::Config::Actions.register(self)`, and then appears by name inside `config.actions`. Its options (`member`, `collection`, `http_methods`, `controller`, `link_icon`, `visible?`, `authorization_key`, `template_name` and the rest) are declared in `<gem>/lib/rails_admin_next/config/actions/base.rb`. `<gem>/lib/rails_admin_next/config/actions/show_in_app.rb` is a short example. The action renders the host view app/views/rails_admin_next/main/<template_name>.html.erb.
|
|
73
|
+
|
|
74
|
+
Icons take logical names, never Font Awesome classes: `link_icon :show_in_app` or `navigation_icon :list`. The list is in the "Icons" section of `<gem>/docs/UPGRADING.md`.
|
|
75
|
+
|
|
76
|
+
## Authorization and history
|
|
77
|
+
|
|
78
|
+
- `config.authorize_with :cancancan` uses the app's `Ability`. Keep the `:rails_admin` subject (see Naming).
|
|
79
|
+
- `config.authorize_with :pundit` calls the record's policy with `<action>?` methods (`index?`, `edit?`, `dashboard?`) and scopes lists with `policy_scope`.
|
|
80
|
+
- `config.authorize_with do ... end` runs a custom block as a before filter.
|
|
81
|
+
- `config.authenticate_with do ... end` and `config.current_user_method do ... end` wire in the app's authentication, such as Devise or Warden.
|
|
82
|
+
- `config.audit_with :paper_trail` turns on the history action. Read the "History / audit table" section of `<gem>/docs/UPGRADING.md` first.
|
|
83
|
+
|
|
84
|
+
## Frontend
|
|
85
|
+
|
|
86
|
+
- The host app must use Propshaft and importmap-rails. There is no Sprockets, Webpacker, Webpack, Vite or build step, and there is no `config.asset_source` option.
|
|
87
|
+
- The engine runs its own Stimulus application, exposed as `window.RailsAdminNext.application`. Register custom controllers on that instance, not on the host's Stimulus app.
|
|
88
|
+
- Add JavaScript to the admin through a host config/importmap.rails_admin.rb. The engine appends it after its own map, so it can add pins or override the entrypoint pin. The engine's pin names are in `<gem>/config/importmap.rails_admin.rb`.
|
|
89
|
+
- There is no jQuery, jQuery UI, Bootstrap JS or `@rails/ujs`. Confirmations use Turbo's `data-turbo-confirm`.
|
|
90
|
+
- Date and time fields are native HTML5 inputs, with no flatpickr. Rich text is ActionText/Trix. There are no CDN editors such as CKEditor, CodeMirror or froala.
|
|
91
|
+
- The admin supports only the latest Chrome and Safari, so modern CSS and DOM APIs are fair game.
|
|
92
|
+
|
|
93
|
+
## Theming
|
|
94
|
+
|
|
95
|
+
Engine CSS sits in the cascade layers `ra.tokens`, `ra.framework` and `ra.skin`, so any unlayered host rule wins without specificity tricks. Rebrand by overriding `--ra-*` tokens, for example `:root { --ra-primary: #663399; }`. Upstream `--bs-*` variables do nothing here. The supported tokens and the `--bs-*` to `--ra-*` mapping are in the "Styling" section of `<gem>/docs/UPGRADING.md`. Dark mode follows the OS. `config.color_scheme = :light` (or `:dark`, or `:auto`) pins it.
|
|
96
|
+
|
|
97
|
+
## Security
|
|
98
|
+
|
|
99
|
+
The engine always enforces these, and there is no option to weaken them: CSRF, `X-Frame-Options: SAMEORIGIN`, non-GET export, confirmation on delete, and visible-field slicing of submitted attributes. A Content Security Policy is opt-in through `config.content_security_policy do |policy| ... end`. Pass `report_only: true` while tuning it. Read `<gem>/docs/security.md` before writing the policy. It lists the inline styles that still need `:unsafe_inline` in `style-src`.
|
|
100
|
+
|
|
101
|
+
## Never suggest
|
|
102
|
+
|
|
103
|
+
Mongoid, `RailsAdmin.config`, `rails_admin do` blocks, Font Awesome icon classes, jQuery plugins, Sprockets or Webpacker setup, or a migration from upstream RailsAdmin. The migration is a one-time rename, and `<gem>/docs/UPGRADING.md` covers it.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_admin_next
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel López Prat
|
|
@@ -189,6 +189,9 @@ files:
|
|
|
189
189
|
- config/initializers/active_record_extensions.rb
|
|
190
190
|
- config/locales/rails_admin_next.en.yml
|
|
191
191
|
- config/routes.rb
|
|
192
|
+
- docs/UPGRADING.md
|
|
193
|
+
- docs/security.md
|
|
194
|
+
- docs/upstream-parity.md
|
|
192
195
|
- lib/generators/rails_admin_next/install_generator.rb
|
|
193
196
|
- lib/generators/rails_admin_next/templates/initializer.erb
|
|
194
197
|
- lib/generators/rails_admin_next/utils.rb
|
|
@@ -297,6 +300,7 @@ files:
|
|
|
297
300
|
- lib/rails_admin_next/support/paginated_collection.rb
|
|
298
301
|
- lib/rails_admin_next/version.rb
|
|
299
302
|
- lib/tasks/rails_admin.rake
|
|
303
|
+
- skills/rails-admin-next/SKILL.md
|
|
300
304
|
- src/rails_admin/application.js
|
|
301
305
|
- src/rails_admin/base.js
|
|
302
306
|
- src/rails_admin/controllers/alert_controller.js
|
|
@@ -324,6 +328,7 @@ licenses:
|
|
|
324
328
|
metadata:
|
|
325
329
|
bug_tracker_uri: https://github.com/6temes/rails_admin_next/issues
|
|
326
330
|
changelog_uri: https://github.com/6temes/rails_admin_next/releases
|
|
331
|
+
hyperdrive_targets: rails_admin_next
|
|
327
332
|
rubygems_mfa_required: 'true'
|
|
328
333
|
source_code_uri: https://github.com/6temes/rails_admin_next
|
|
329
334
|
rdoc_options: []
|