recourse 7.8.1 → 7.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +89 -182
- data/config/locales/recourse.en.yml +5 -2
- data/lib/recourse/helpers/actions.rb +7 -6
- data/lib/recourse/helpers/retrievals.rb +4 -7
- data/lib/recourse/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 94653dc38ca1c788fce69b9406faddc48c189cefcd9fbe7dbe11e4d87ddc3faf
|
|
4
|
+
data.tar.gz: c1282266e1e70de6bc5438661f86321e52948dee028baa7128efe4fac4317719
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dbed84f6d5071f7be79cdb87c499c6a9c4ae23ceb74c70390606cfe33652236f38e782ecbae67347c4873b270c23cba660395d2451034202517af60fe9a0a8d6
|
|
7
|
+
data.tar.gz: e04c3108e911aba9d08555b8d696994cc56df42ee21ee207f96cc0f1cf326491fcd62d867f38953e22c3fdb76e14297a50d22e4444db0bae1c28746a20b546db
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ For more information about changelogs, check [Keep a Changelog](http://keepachan
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## 7.8.3 - 2026-09-21
|
|
11
|
+
|
|
12
|
+
* [FIX] A delete button in a table row is no taller than the row
|
|
13
|
+
|
|
14
|
+
The button kept `.btn`'s minimum height of 2rem, half again the line the other cells
|
|
15
|
+
read at, so every row of a table with a delete column stood taller than the rows of a
|
|
16
|
+
table without one.
|
|
17
|
+
|
|
18
|
+
## 7.8.2 - 2026-09-20
|
|
19
|
+
|
|
20
|
+
* [CHANGE] The retrieval button says what it does
|
|
21
|
+
|
|
22
|
+
It drew an icon and said the rest to a screen reader alone. It reads `Retrieve` now,
|
|
23
|
+
and keeps the aria-label naming the rows it fetches.
|
|
24
|
+
|
|
10
25
|
## 7.8.1 - 2026-09-20
|
|
11
26
|
|
|
12
27
|
* [FIX] A retrieval route is named after its resource
|
data/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Swap two characters in your `config/routes.rb` and get awesome admin screens for your Rails app.
|
|
4
4
|
|
|
5
|
+
This README is the tour. The [guides](https://claudiob.github.io/recourse/guides/) are
|
|
6
|
+
where every screen is explained in full, one page each, and the
|
|
7
|
+
[reference](https://rubydoc.info/gems/recourse) is built from the source.
|
|
8
|
+
|
|
5
9
|
## How to install
|
|
6
10
|
|
|
7
11
|
```bash
|
|
@@ -10,10 +14,10 @@ gem install recourse
|
|
|
10
14
|
|
|
11
15
|
```ruby
|
|
12
16
|
# Gemfile
|
|
13
|
-
gem 'recourse', '~>
|
|
17
|
+
gem 'recourse', '~> 7.0'
|
|
14
18
|
```
|
|
15
19
|
|
|
16
|
-
`~>
|
|
20
|
+
`~> 7.0` follows Semantic Versioning: `bundle update` takes every 7.x and never a
|
|
17
21
|
breaking change. Rails 8.1 and Ruby 3.2 are the minimum; the pages need Turbo, which
|
|
18
22
|
`turbo-rails` brings, and nothing else in the host. Everything a page is styled and
|
|
19
23
|
scripted by — Bootstrap 6, its icons, Turbo, Stimulus and the controllers behind these
|
|
@@ -39,7 +43,8 @@ first letter, and a paginated, searchable, sortable, filterable index table:
|
|
|
39
43
|
<img width="3824" height="2274" alt="Image" src="https://github.com/user-attachments/assets/f952d3e5-c320-464b-b166-77c7ab8f9071" />
|
|
40
44
|
|
|
41
45
|
Every recourse with `show` gets a detail page where attributes are displayed with the appropriate
|
|
42
|
-
formatting
|
|
46
|
+
formatting — a boolean as `Yes` or `No`, a date by its name, a key as the label of what it
|
|
47
|
+
points at — or masked if sensitive:
|
|
43
48
|
|
|
44
49
|
<img width="3824" height="2274" alt="Image" src="https://github.com/user-attachments/assets/515f63c4-8a9b-422e-95c4-88d6647911d8" />
|
|
45
50
|
|
|
@@ -47,73 +52,48 @@ Every recourse with `new` or `edit` gets a form with appropriate browser formatt
|
|
|
47
52
|
|
|
48
53
|
<img width="3824" height="1220" alt="Image" src="https://github.com/user-attachments/assets/ebe5108b-bbd1-4cd1-a635-89eef0e6fc50" />
|
|
49
54
|
|
|
50
|
-
Every recourse with `destroy` gets a button with a detailed confirmation message — on
|
|
51
|
-
|
|
55
|
+
Every recourse with `destroy` gets a button with a detailed confirmation message — on the
|
|
56
|
+
record's own page, whichever of `show` and `edit` is open, and on each row of its table
|
|
57
|
+
where no `edit` page would carry it. A row a model will not give up stays, and the page says so
|
|
58
|
+
rather than raising. A model may word its own deletion — `Disconnect` rather than
|
|
59
|
+
`Delete` — by writing `recourse.models.<model>.delete` in its locale:
|
|
52
60
|
|
|
53
61
|
[image]
|
|
54
62
|
|
|
55
|
-
|
|
56
|
-
record reached with no id of its own, at `/locations/5/property`. Rails routes a singular
|
|
57
|
-
resource to a plural controller, so `Locations::PropertiesController` answers it, and the
|
|
58
|
-
gem reads the record off the parent under the name the route gives — `location.property`,
|
|
59
|
-
whether the parent keeps it with a `has_one` or points at one with a `belongs_to`. Where
|
|
60
|
-
the parent has no association of that name the record is still yours to find, with a
|
|
61
|
-
`find_resource` of your own.
|
|
62
|
-
|
|
63
|
-
What such a nesting earns on the parent's card depends only on what it routes. Routed
|
|
64
|
-
`show`, it earns a tab reading the model's own word in the singular, pointing at that one
|
|
65
|
-
page. Routed `create` or `destroy` and neither `index` nor `new`, it earns a button beside
|
|
66
|
-
the breadcrumb instead — `Add property`, `Delete property` — and which of the two it
|
|
67
|
-
offers is the record's to say: nothing to add where there is already one, nothing to
|
|
68
|
-
delete where there is none. A write has no index to return to, so it lands on the
|
|
69
|
-
record's own page: a singular resource is the collection of one.
|
|
70
|
-
|
|
71
|
-
Filters are drawn per enum, per boolean and per foreign key. A model adds one the schema
|
|
72
|
-
says nothing about — the type behind a `has_one`, a word reached through another table —
|
|
73
|
-
by naming the words itself, and naming the menu too, since there is no column to head it:
|
|
74
|
-
|
|
75
|
-
```ruby
|
|
76
|
-
def self.filter_fields
|
|
77
|
-
super.merge 'integration_type_in' => { label: 'CRM', values: Integration.selectable }
|
|
78
|
-
end
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Naming the words is not leave to query through a table, so `ransackable_associations`
|
|
82
|
-
still says how far Ransack may reach.
|
|
83
|
-
|
|
84
|
-
Every nested recourse gets namespaced after the parent:
|
|
63
|
+
Every nested recourse gets namespaced after the parent, and a tab on the parent's card:
|
|
85
64
|
|
|
86
65
|
<img width="3824" height="2274" alt="Image" src="https://github.com/user-attachments/assets/9b242335-a25b-4fa8-8023-422538d235b0" />
|
|
87
66
|
|
|
67
|
+
`recourse` draws what Rails' `resource` draws: one record reached with no id of its own,
|
|
68
|
+
at `/locations/5/property`, read off the parent under the name the route gives —
|
|
69
|
+
`location.property`, whether the parent keeps it with a `has_one` or points at one with
|
|
70
|
+
a `belongs_to`. What such a nesting earns on the parent's card depends only on what it
|
|
71
|
+
routes: a tab where `show` is, and a button beside the breadcrumb — `Add property`,
|
|
72
|
+
`Delete property` — where `create` or `destroy` is routed with no page of its own. A
|
|
73
|
+
singular resource holds at most one, so `new` sends a reader to the record where there
|
|
74
|
+
already is one, and `show` sends them to the form where there is none yet.
|
|
75
|
+
|
|
76
|
+
Two keywords say what a table may do beyond the seven actions. `positionable: true`
|
|
77
|
+
draws the route a dragged row reports its place to, and `retrievable: true` draws the
|
|
78
|
+
one a `Retrieve` button on the table posts to, for rows that came from somewhere else.
|
|
79
|
+
Both are refused on a resource with no `index`, since there is nowhere for either to
|
|
80
|
+
stand. The [routes guide](https://claudiob.github.io/recourse/guides/routes.html) has
|
|
81
|
+
the rest: namespaces, the defaults a nested resource takes, and the `exit` route that
|
|
82
|
+
earns the sidebar a log-out button.
|
|
88
83
|
|
|
89
84
|
## Step 2. Enhance your models
|
|
90
85
|
|
|
91
|
-
To change
|
|
86
|
+
To change what a model's screens show, override any of these class methods:
|
|
92
87
|
|
|
93
88
|
| Hook | Default | Decides |
|
|
94
89
|
| --- | --- | --- |
|
|
95
90
|
| [`recourse_label`](https://rubydoc.info/gems/recourse/Recourse/Recoursive#recourse_label-instance_method) | `:name` | the column a combobox shows and a foreign-key cell reads; typed rather than picked where it has a length validator |
|
|
96
91
|
| [`recourse_hidden`](https://rubydoc.info/gems/recourse/Recourse/Recoursive#recourse_hidden-instance_method) | `[]` | columns kept off the table, the page, the form and the search |
|
|
97
|
-
| [`recourse_displayed`](https://rubydoc.info/gems/recourse/Recourse/Recoursive#recourse_displayed-instance_method) | `[]` | columns a table draws that it would leave off: encrypted ones, the id, timestamps, JSON |
|
|
98
92
|
| [`recourse_order`](https://rubydoc.info/gems/recourse/Recourse/Recoursive#recourse_order-instance_method) | `:id`, or the positioned column | the index's order, a Symbol or a Hash; rows with nothing in the column come last |
|
|
99
93
|
| [`recourse_position`](https://rubydoc.info/gems/recourse/Recourse/Recoursive#recourse_position-instance_method) | `'position'` where the model keeps an integer one | the column a reader drags the rows into order by, or `nil` for a table nobody positions |
|
|
100
94
|
| [`recourse_icon`](https://rubydoc.info/gems/recourse/Recourse/Recoursive#recourse_icon-instance_method) | the model's name | the icon on the sidebar, the crumbs and the tabs |
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
it, when its column holds a counter cache or is named `<association>_count` for an
|
|
104
|
-
association the model has. So a count the app keeps itself — a `has_many through`, which
|
|
105
|
-
Rails will not cache — needs nothing declared:
|
|
106
|
-
|
|
107
|
-
```ruby
|
|
108
|
-
class Technician < ActiveRecord::Base
|
|
109
|
-
has_many :visits, through: :dispatches # and a visits_count column beside it
|
|
110
|
-
end
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Which means a `*_count` column whose prefix names an association must hold the count of
|
|
114
|
-
that association. One naming nothing — `word_count`, `retry_count` — is an ordinary
|
|
115
|
-
number, and one naming an association it does not count wants another name or a place in
|
|
116
|
-
`recourse_hidden`.
|
|
95
|
+
| [`recourse_includes`](https://rubydoc.info/gems/recourse/Recourse/Recoursive#recourse_includes-instance_method) | every `belongs_to` | what the index eager-loads, in any shape `includes` takes |
|
|
96
|
+
| [`recourse_broadcasts?`](https://rubydoc.info/gems/recourse/Recourse/Broadcasting#recourse_broadcasts%3F-instance_method) | `true` | whether saving a record redraws every open index listing it |
|
|
117
97
|
|
|
118
98
|
For instance, this would yield a more compact `index` than the default configuration:
|
|
119
99
|
|
|
@@ -121,14 +101,35 @@ For instance, this would yield a more compact `index` than the default configura
|
|
|
121
101
|
class Post < ActiveRecord::Base
|
|
122
102
|
def self.recourse_label = :title # Label a post with its title
|
|
123
103
|
def self.recourse_hidden = %i[ip_address] # Hide the IP address from the views
|
|
124
|
-
def self.recourse_displayed = :created_at # Display the created_at in the views
|
|
125
104
|
def self.recourse_order = { published_at: :desc } # Sort posts by last published first
|
|
126
105
|
def self.recourse_icon = :question # Represent Posts with a question icon
|
|
127
106
|
end
|
|
128
|
-
|
|
107
|
+
```
|
|
129
108
|
|
|
130
109
|
[image]
|
|
131
110
|
|
|
111
|
+
What the schema already says needs nothing declared. A number is drawn as a count,
|
|
112
|
+
headed with what it counts and linking to the rows behind it, when its column holds a
|
|
113
|
+
counter cache or is named `<association>_count` for an association the model has. An
|
|
114
|
+
enum is a badge on a page, a menu on a form and a filter beside the search box, and so
|
|
115
|
+
is a boolean and every foreign key whose table is short enough to list. An encrypted
|
|
116
|
+
column stays off every table, arrives masked on the record's page and is offered in the
|
|
117
|
+
clear on its form. A `has_one_attached` is a file field and a picture; a
|
|
118
|
+
`has_many_attached :photos` is a page of its own once `recourses :photos` is routed
|
|
119
|
+
under the record.
|
|
120
|
+
|
|
121
|
+
Filters are drawn per enum, per boolean and per foreign key. A model adds one the schema
|
|
122
|
+
says nothing about — a word reached through another table — by naming the predicate,
|
|
123
|
+
and the gem reads the words off the model behind it:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
def self.filter_fields = super + %i[team_name_in]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Naming a filter is not leave to query through a table, so `ransackable_associations`
|
|
130
|
+
still says how far Ransack may reach — and a filter Ransack will not answer is refused
|
|
131
|
+
while the page draws rather than left to narrow nothing.
|
|
132
|
+
|
|
132
133
|
## Step 3. Delete your views
|
|
133
134
|
|
|
134
135
|
If you are happy with the generated HTML files, then be happy to skip this step!
|
|
@@ -153,6 +154,12 @@ to define the columns you want:
|
|
|
153
154
|
|
|
154
155
|
[image]
|
|
155
156
|
|
|
157
|
+
A `_fields.html.erb` replaces the fields of a form the same way, and a `show`, `edit` or
|
|
158
|
+
nested `index` template of your own replaces the body of that page and nothing else:
|
|
159
|
+
the card, its tabs, the buttons beside the breadcrumb and the tab's title are the
|
|
160
|
+
layout's, so a page you write is its content alone. A page that wants the width to
|
|
161
|
+
itself assigns `@recourse_card = false`.
|
|
162
|
+
|
|
156
163
|
## Step 4. Secure your controllers
|
|
157
164
|
|
|
158
165
|
Every controller the gem defines inherits
|
|
@@ -165,14 +172,15 @@ class RecoursesController < Recourse::BaseController
|
|
|
165
172
|
end
|
|
166
173
|
```
|
|
167
174
|
|
|
168
|
-
A controller the app already defines is left alone
|
|
169
|
-
|
|
175
|
+
A controller the app already defines is left alone, and one line of it is usually
|
|
176
|
+
enough: `recourse_relation` puts a scope of your own behind a table the gem draws
|
|
177
|
+
whole, and `find_resource` finds a singular record the parent names no association for.
|
|
170
178
|
|
|
171
179
|
## Step 5. Enjoy the extras
|
|
172
180
|
|
|
173
|
-
The rest of what the gem offers
|
|
174
|
-
`config/initializers/recourse.rb`; the
|
|
175
|
-
already declares.
|
|
181
|
+
The rest of what the gem offers. The first two are a line each in
|
|
182
|
+
`config/initializers/recourse.rb`; the others need nothing beyond what the model
|
|
183
|
+
already declares, or one keyword in the routes.
|
|
176
184
|
|
|
177
185
|
### Color and theme
|
|
178
186
|
|
|
@@ -189,7 +197,8 @@ Recourse.theme = :nord
|
|
|
189
197
|
|
|
190
198
|
The theme is where a reader starts, not where they stay. The moon at the foot of the
|
|
191
199
|
sidebar rotates through every palette, light and dark, and the one they pick stays in
|
|
192
|
-
their browser.
|
|
200
|
+
their browser. Times are drawn in the reader's own zone the same way, reported by their
|
|
201
|
+
browser and never written to the host.
|
|
193
202
|
|
|
194
203
|
### Bookmarks
|
|
195
204
|
|
|
@@ -206,147 +215,45 @@ rows come first. A model that cannot hold a bookmark gets no column.
|
|
|
206
215
|
|
|
207
216
|
<img width="3824" height="1220" alt="Image" src="https://github.com/user-attachments/assets/d1db4adb-0b5c-4e41-8bb6-cf72a35288f0" />
|
|
208
217
|
|
|
209
|
-
###
|
|
210
|
-
|
|
211
|
-
What a model keeps as files needs nothing declared either. A `has_one_attached :logo` is
|
|
212
|
-
a file field on the form and, on the record's page, the picture Active Storage makes of
|
|
213
|
-
the file — 100 pixels tall, WebP where the browser takes it, opening the whole file in a
|
|
214
|
-
new tab — or the file's name where nothing can be drawn of it.
|
|
215
|
-
|
|
216
|
-
A `has_many_attached :photos` earns a page of its own, nested under the record:
|
|
217
|
-
|
|
218
|
-
```ruby
|
|
219
|
-
recourses :photos, only: %i[index destroy]
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
That page is a table of the files with their pictures, and a Delete on each row that
|
|
223
|
-
takes the file off the record. Files are added on the record's own form, where a chosen
|
|
224
|
-
file joins a shelf and replaces a single one, and a field nobody touched leaves
|
|
225
|
-
everything as it was. `recourse_hidden :photos` keeps a file off every screen the way it
|
|
226
|
-
keeps a column off.
|
|
227
|
-
|
|
228
|
-
Drawing a picture of an image needs `image_processing`; video and PDF also need the
|
|
229
|
-
host's `ffmpeg` and `poppler`.
|
|
230
|
-
|
|
231
|
-
### Maps
|
|
218
|
+
### Maps and calendars
|
|
232
219
|
|
|
233
220
|
A table whose model keeps a `google_place_id`, or a `latitude` and a `longitude`, can be
|
|
234
221
|
read as a Google map of the same page: the footer under it offers `Display as map`, and
|
|
235
222
|
`/counties.map` draws this page's rows in the frame and over the footer the table has, so
|
|
236
|
-
search, sort and pages work the same on either shape.
|
|
237
|
-
|
|
238
|
-
A point is a pin. A place ID is filled in as an area where the model is a geography
|
|
239
|
-
Google draws boundaries for — a `State`, a `County`, a `City` or a `ZIP`, by name — and
|
|
240
|
-
pinned at the place for any other model.
|
|
241
|
-
|
|
242
|
-
The key and the map ID are the host's own credentials:
|
|
243
|
-
|
|
244
|
-
```yaml
|
|
245
|
-
# config/credentials.yml.enc
|
|
246
|
-
google_maps:
|
|
247
|
-
api_key: AIza…
|
|
248
|
-
map_id: 4f2a…
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
The map's style has the matching **Feature layers** turned on in the Cloud console —
|
|
252
|
-
Postal code for ZIPs, Administrative area level 2 for counties. A host with a Content
|
|
253
|
-
Security Policy allows `maps.googleapis.com` for scripts and connections, and Google's
|
|
254
|
-
tile hosts for images.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
### Calendars
|
|
258
|
-
|
|
259
|
-
A table whose model keeps a `starts_at` and an `ends_at` can be read as a week of the same
|
|
260
|
-
rows: the footer offers `Display as calendar`, and `/shifts.cal` draws a column a day with
|
|
261
|
-
each row placed by the hours it runs between — rows that overlap in lanes of their own.
|
|
262
|
-
A row says what it is, whoever it points at, and the hours themselves, each led to its own
|
|
263
|
-
page where the routes drew one.
|
|
264
|
-
|
|
265
|
-
```ruby
|
|
266
|
-
recourses :shifts
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
A week rather than a page is what a calendar shows, so there is nothing to paginate:
|
|
270
|
-
`?week=2026-09-13` moves to the week holding that day, and `<<`, `<`, `>` and `>>` stand
|
|
271
|
-
where the pages stand under a table — a week either side and four weeks beyond each —
|
|
272
|
-
with the week named between them. The search and the
|
|
273
|
-
filters travel with it — and answer in the shape they were picked in — so a calendar
|
|
274
|
-
narrowed to one person stays a narrowed calendar as the weeks move.
|
|
275
|
-
|
|
276
|
-
The grid stands as tall as a table's own first page, so the weeks under a calendar sit
|
|
277
|
-
where the pages under a table do. The week opens on Sunday and the hours are the reader's
|
|
278
|
-
own — the same cookie every other
|
|
279
|
-
time on these pages is drawn against — and the scale runs only over the hours that week's
|
|
280
|
-
rows cover, or a working day where it holds none. A row running past midnight is drawn on
|
|
281
|
-
the day it opens, down to the foot of its column.
|
|
282
|
-
|
|
283
|
-
Both columns have to be datetimes, asked through `type_for_attribute`, so an
|
|
284
|
-
`attribute :starts_at, :datetime` override counts and a column of another kind named
|
|
285
|
-
`starts_at` earns nothing.
|
|
223
|
+
search, sort and pages work the same on either shape. The key and the map ID are the
|
|
224
|
+
host's own credentials, under `google_maps` as `api_key` and `map_id`.
|
|
286
225
|
|
|
226
|
+
A table whose model keeps a `starts_at` and an `ends_at` can be read as a week of the
|
|
227
|
+
same rows: the footer offers `Display as calendar`, and `/shifts.cal` draws a column a
|
|
228
|
+
day with each row placed by the hours it runs between. `?week=2026-09-13` moves to the
|
|
229
|
+
week holding that day, and the search and the filters travel with it.
|
|
287
230
|
|
|
288
231
|
### Positionable tables
|
|
289
232
|
|
|
290
233
|
A table whose model keeps an integer `position` is one a reader puts in order by hand: a
|
|
291
|
-
grip opens each row, dragging it moves the row, and the place it lands in is written to
|
|
292
|
-
route
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
recourses :steps, positionable: true
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
A table is put in order from its index and from nowhere else, so the keyword is refused on
|
|
300
|
-
a resource that draws none. The column decides the rest — the grips, the callbacks, and
|
|
301
|
-
`recourse_order`, since the order a table is read in and the order somebody put it in are
|
|
302
|
-
one fact.
|
|
234
|
+
grip opens each row, dragging it moves the row, and the place it lands in is written to
|
|
235
|
+
the route `positionable: true` drew under the index. The column decides the rest — the
|
|
236
|
+
grips, the callbacks that keep the numbers running 1, 2, 3, and `recourse_order`, since
|
|
237
|
+
the order a table is read in and the order somebody put it in are one fact. A model
|
|
238
|
+
pointing two ways says which of them its place is counted within:
|
|
303
239
|
|
|
304
240
|
```ruby
|
|
305
241
|
class Step < ActiveRecord::Base
|
|
306
242
|
belongs_to :team
|
|
307
243
|
belongs_to :person
|
|
308
244
|
|
|
309
|
-
# A step points two ways, so which of them its place is counted within is the model's
|
|
310
|
-
# to say. One `belongs_to` needs no answer, and a model pointing nowhere is positioned
|
|
311
|
-
# among the whole table.
|
|
312
245
|
def recourse_siblings = team.steps
|
|
313
246
|
end
|
|
314
247
|
```
|
|
315
248
|
|
|
316
|
-
|
|
317
|
-
and the gap closes behind one that goes, whether it was made behind a form or in a
|
|
318
|
-
console: what a drop reports is a row's place on the *page*, which is a position only
|
|
319
|
-
while the numbers run 1, 2, 3 with no holes in them. And a table being positioned is asked
|
|
320
|
-
per page — a position means something under the parent it is counted within, so a flat
|
|
321
|
-
model's own index is positioned and a listing of every row across every parent is not.
|
|
322
|
-
|
|
323
|
-
The search box and the sorted headings stand down while the grips are drawn, and a `q`
|
|
324
|
-
typed by hand is refused for the same reason: a filter shortens the page, and a drop on a
|
|
325
|
-
shortened page reports a place among the rows that are left.
|
|
326
|
-
|
|
327
|
-
A host that keeps the order itself writes `def recourse_position = nil`, which takes the
|
|
328
|
-
grips off the table and the two callbacks off the model together. A host with a *second*
|
|
329
|
-
listing of a positioned model — a plan's place among its service's plans, and another
|
|
330
|
-
among every plan of its department — names the second column on the controller instead,
|
|
331
|
-
and owns the write as well as the page:
|
|
332
|
-
|
|
333
|
-
```ruby
|
|
334
|
-
module Departmental
|
|
335
|
-
extend ActiveSupport::Concern
|
|
336
|
-
|
|
337
|
-
private
|
|
338
|
-
|
|
339
|
-
def recourse_position = 'ordering'
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
class Administered::Departments::Plans::PositionsController < Recourse::PositionsController
|
|
343
|
-
include Departmental
|
|
344
|
-
end
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
`Recourse::Positioning` is public for exactly that case: `new(relation, column).close` is
|
|
348
|
-
what such a host calls to keep its own column contiguous.
|
|
249
|
+
### Retrievable tables
|
|
349
250
|
|
|
251
|
+
A table whose rows come from somewhere else — a CRM, a feed — offers to fetch them
|
|
252
|
+
again: `recourses :visits, only: :index, retrievable: true` draws
|
|
253
|
+
`POST /providers/5/visits/retrieval` and the `Retrieve` button that posts to it. Where
|
|
254
|
+
the rows come from is the host's to know, so `Providers::Visits::RetrievalsController`
|
|
255
|
+
is the host's to write, and `recourse_retrievable?` on the listing controller is how a
|
|
256
|
+
page says there is nothing to fetch from.
|
|
350
257
|
|
|
351
258
|
## Development
|
|
352
259
|
|
|
@@ -71,8 +71,11 @@ en:
|
|
|
71
71
|
has no %{model} model.
|
|
72
72
|
nested: You nested `recourses %{names}` inside `resources :%{parent}`, which does
|
|
73
73
|
not namespace what it holds. Nest it under `recourses :%{parent}` instead.
|
|
74
|
-
#
|
|
75
|
-
|
|
74
|
+
# The button that fetches a table's rows again: the word on it, and what it is
|
|
75
|
+
# named to a screen reader, where the rows it fetches are said.
|
|
76
|
+
retrieval:
|
|
77
|
+
button: Retrieve
|
|
78
|
+
label: Retrieve %{models} again
|
|
76
79
|
unretrieved: "You asked for `recourses %{names}, retrievable: true`, which draws no
|
|
77
80
|
index to fetch them onto. The button that asks for them stands on the table that
|
|
78
81
|
lists them, so there is nowhere to put it."
|
|
@@ -73,17 +73,18 @@ module Recourse
|
|
|
73
73
|
url_for controller: "/#{resource_controller_path}", action: action, id: record
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
# The icon alone, red for what it does,
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
# the table and throw the
|
|
76
|
+
# The icon alone, red for what it does, no taller than the line the row reads at,
|
|
77
|
+
# and the same warning the edit page's button carries in front of it, which the
|
|
78
|
+
# bundle draws as its dialog. Out of the frame the table is drawn in, like every
|
|
79
|
+
# other action in the row: what a delete lands on is a whole page with a message
|
|
80
|
+
# over it, and answering inside the frame would keep the table and throw the
|
|
81
|
+
# message away.
|
|
81
82
|
def destroy_button(record)
|
|
82
83
|
path = url_for controller: "/#{destroy_action_path}", action: :destroy, id: record
|
|
83
84
|
|
|
84
85
|
confirm_button_to icon_tag(ICONS[:destroy], class: 'fg-danger'), path,
|
|
85
86
|
confirm: destroy_warning(record), method: :delete,
|
|
86
|
-
class: 'btn btn-sm btn-link btn-icon p-0',
|
|
87
|
+
class: 'btn btn-sm btn-link btn-icon p-0 min-h-0',
|
|
87
88
|
aria: { label: t('recourse.delete', model: resource_name) },
|
|
88
89
|
form: { data: { turbo_frame: '_top' } }
|
|
89
90
|
end
|
|
@@ -10,11 +10,9 @@ module Recourse
|
|
|
10
10
|
def retrieval_button
|
|
11
11
|
return unless Recourse.retrievable?(controller.controller_path) && recourse_retrievable?
|
|
12
12
|
|
|
13
|
-
button_to
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
icon_tag :sync
|
|
17
|
-
end
|
|
13
|
+
button_to t('recourse.retrieval.button'), retrieval_path,
|
|
14
|
+
class: 'btn btn-sm btn-solid theme-primary ms-3',
|
|
15
|
+
form_class: 'd-inline-block', aria: { label: retrieval_label }
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
# The one route a retrievable resource earns, under the resource itself.
|
|
@@ -23,8 +21,7 @@ module Recourse
|
|
|
23
21
|
end
|
|
24
22
|
|
|
25
23
|
def retrieval_label
|
|
26
|
-
t 'recourse.
|
|
27
|
-
lower: true)
|
|
24
|
+
t 'recourse.retrieval.label', models: Recourse.model_title(resource_model, lower: true)
|
|
28
25
|
end
|
|
29
26
|
end
|
|
30
27
|
end
|
data/lib/recourse/version.rb
CHANGED