recourse 5.6.3 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -0
- data/app/controllers/concerns/recourse/landing.rb +5 -2
- data/app/controllers/concerns/recourse/resource_resolution.rb +29 -4
- data/app/views/recourses/_none.html.erb +5 -1
- data/app/views/recourses/show.html.erb +10 -3
- data/lib/recourse/helpers/actions.rb +6 -2
- data/lib/recourse/helpers/buttons.rb +37 -13
- data/lib/recourse/helpers/cards.rb +22 -7
- data/lib/recourse/helpers/counters.rb +1 -1
- data/lib/recourse/helpers/navigation.rb +17 -4
- data/lib/recourse/helpers/parents.rb +2 -2
- data/lib/recourse/helpers/resources.rb +4 -1
- data/lib/recourse/helpers/routing.rb +10 -0
- data/lib/recourse/helpers/tabs.rb +31 -11
- data/lib/recourse/routes.rb +17 -0
- data/lib/recourse/routing.rb +12 -0
- data/lib/recourse/searchable.rb +5 -3
- data/lib/recourse/titles.rb +5 -2
- data/lib/recourse/version.rb +1 -1
- data/lib/recourse.rb +3 -2
- 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: 2b7756a87eb7ee1ef4267480a0e8370c1b63e50623a7a22cf0efe6a7a7a46644
|
|
4
|
+
data.tar.gz: 0e7e84177bc55634878165dd8ba0fe19ad2c9d635c586211477d48431354cb47
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 468364cedb2192b3a73d7b4642689d0c6b984b156c0a20b7d6930c7a982e406d5c33132bc4fd8158b1c274eb47a925e299728d5f09530dc0421fdd72d303a243
|
|
7
|
+
data.tar.gz: d34ed9fd8cbe6f355a0bf9889d7cfa4e4ce9ebd02686ac2f1070fefb3230322173855a0662f720a3380f1d8e10224b0e7d5bf125af13287a41a9f81dbf457f01
|
data/README.md
CHANGED
|
@@ -52,6 +52,22 @@ edit page, or on each row of its table where no `edit` is routed:
|
|
|
52
52
|
|
|
53
53
|
[image]
|
|
54
54
|
|
|
55
|
+
`recourse` draws what Rails' `resource` draws, and the gem records it the same way: one
|
|
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
|
+
|
|
55
71
|
Every nested recourse gets namespaced after the parent:
|
|
56
72
|
|
|
57
73
|
<img width="3824" height="2274" alt="Image" src="https://github.com/user-attachments/assets/9b242335-a25b-4fa8-8023-422538d235b0" />
|
|
@@ -40,10 +40,13 @@ module Recourse
|
|
|
40
40
|
redirect_to written_url, status: :see_other
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
# And where a write goes: the index, or
|
|
44
|
-
#
|
|
43
|
+
# And where a write goes: the index, or the record's own page where the routes drew none
|
|
44
|
+
# — a singular resource is the collection of one. A nesting that routed neither has
|
|
45
|
+
# no page anywhere to land on, so the write goes back to the record it hangs off,
|
|
46
|
+
# which for a bare action is the page its button stood on.
|
|
45
47
|
def written_url
|
|
46
48
|
return url_for action: :index if Recourse.routed? controller_path, 'index'
|
|
49
|
+
return url_for action: :show if Recourse.idless_route? controller_path, 'show'
|
|
47
50
|
|
|
48
51
|
parent_url
|
|
49
52
|
end
|
|
@@ -4,9 +4,34 @@ module Recourse
|
|
|
4
4
|
module ResourceResolution
|
|
5
5
|
private
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
|
|
7
|
+
# A singular resource is reached with no id — `/places/5/memo` names the record by
|
|
8
|
+
# the path it hangs off rather than by a key of its own — so it is read off the
|
|
9
|
+
# parent instead, under the name the route already gives it. Where the parent has
|
|
10
|
+
# no association of that name the record is still the host's to find, in a
|
|
11
|
+
# controller of its own.
|
|
12
|
+
def find_resource
|
|
13
|
+
return assign resource_class.find(params.expect(:id)) if params.key? :id
|
|
14
|
+
return unless singular_reflection
|
|
15
|
+
|
|
16
|
+
record = @recourse_parent.association(singular_reflection.name).reader
|
|
17
|
+
|
|
18
|
+
record ? assign(record) : missing_record
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The parent's association of this resource's own name, whichever kind it is: a
|
|
22
|
+
# `has_one` where the parent keeps the record, a `belongs_to` where it points at
|
|
23
|
+
# one. `reflect_on_association` rather than a rescue, so a name the parent has
|
|
24
|
+
# never heard of reads as nothing to resolve rather than as an error.
|
|
25
|
+
def singular_reflection
|
|
26
|
+
@recourse_parent&.class&.reflect_on_association controller_name.singularize
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Nothing found, which for a resource routed `new` means the page that makes one:
|
|
30
|
+
# a `has_one` nobody has written yet is a form to fill in, not an absence to read.
|
|
31
|
+
# Where no `new` is drawn, nothing is assigned and the page says so instead.
|
|
32
|
+
def missing_record
|
|
33
|
+
redirect_to url_for(action: :new) if Recourse.routed? controller_path, 'new'
|
|
34
|
+
end
|
|
10
35
|
|
|
11
36
|
def assign(record)
|
|
12
37
|
@recourse = record
|
|
@@ -14,7 +39,7 @@ module Recourse
|
|
|
14
39
|
end
|
|
15
40
|
|
|
16
41
|
# Whether there is a model behind this page at all. A bare action has none: it is a
|
|
17
|
-
# verb the host answers itself — `
|
|
42
|
+
# verb the host answers itself — `recourse :sweep, only: :create` — labelled from the
|
|
18
43
|
# path alone, and still a `RecoursesController`, that being where a host keeps the
|
|
19
44
|
# filters guarding its admin. So what runs on every request asks this first, and the
|
|
20
45
|
# actions the gem serves reach for the model again and raise where it is missing.
|
|
@@ -1 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
<%# locals: (models: nil) -%>
|
|
2
|
+
<%# `models` for a page that stands for one record rather than a list: a singular
|
|
3
|
+
resource the host found nothing for says `No property.` where an empty index says
|
|
4
|
+
`No properties.` -%>
|
|
5
|
+
<p class='fg-2'><%= t 'recourse.none', models: models || Recourse.downcase(resources_name) %></p>
|
|
@@ -4,7 +4,14 @@
|
|
|
4
4
|
the card is the parent's, so the tabs on it are about the record the path names
|
|
5
5
|
above this one rather than about this one. -%>
|
|
6
6
|
<%= render layout: 'card', locals: { record: resource_parent || resource_record } do %>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
<%# Nothing to read out where the host found no record: a singular resource is
|
|
8
|
+
reached with no id, so the one it stands for is one it may not have yet, and the
|
|
9
|
+
page says so rather than reading attributes off nothing. -%>
|
|
10
|
+
<% if resource_record %>
|
|
11
|
+
<div class='row recourse-values'>
|
|
12
|
+
<%= render 'values', resource_key => resource_record %>
|
|
13
|
+
</div>
|
|
14
|
+
<% else %>
|
|
15
|
+
<%= render 'none', models: resource_name %>
|
|
16
|
+
<% end %>
|
|
10
17
|
<% end %>
|
|
@@ -74,14 +74,18 @@ module Recourse
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
# The icon alone, red for what it does, and the same warning the edit page's button
|
|
77
|
-
# carries in front of it, which the bundle draws as its dialog.
|
|
77
|
+
# carries in front of it, which the bundle draws as its dialog. Out of the frame
|
|
78
|
+
# the table is drawn in, like every other action in the row: what a delete lands on
|
|
79
|
+
# is a whole page with a message over it, and answering inside the frame would keep
|
|
80
|
+
# the table and throw the message away.
|
|
78
81
|
def destroy_button(record)
|
|
79
82
|
path = url_for controller: "/#{destroy_action_path}", action: :destroy, id: record
|
|
80
83
|
|
|
81
84
|
button_to icon_tag(ICONS[:destroy], class: 'fg-danger'), path,
|
|
82
85
|
method: :delete, class: 'btn btn-sm btn-link btn-icon p-0',
|
|
83
86
|
aria: { label: t('recourse.delete', model: resource_name) },
|
|
84
|
-
form: { data: {
|
|
87
|
+
form: { data: { turbo_frame: '_top',
|
|
88
|
+
turbo_confirm: destroy_warning(record), } }
|
|
85
89
|
end
|
|
86
90
|
end
|
|
87
91
|
end
|
|
@@ -3,10 +3,14 @@ module Recourse
|
|
|
3
3
|
# The buttons a record's pages carry: an action the routes drew under it that has
|
|
4
4
|
# no page of its own for a link to sit on.
|
|
5
5
|
module Buttons
|
|
6
|
+
# What a bare action may be, and the verb each one is asked with. `create`
|
|
7
|
+
# starts something and `destroy` undoes it; anything else wants a page.
|
|
8
|
+
BARE_ACTIONS = { create: :post, destroy: :delete }.freeze
|
|
9
|
+
|
|
6
10
|
private
|
|
7
11
|
|
|
8
|
-
# A nested resource routed
|
|
9
|
-
#
|
|
12
|
+
# A nested resource routed without an `index` is reached from nowhere, so its
|
|
13
|
+
# button lives on the record it hangs off — beside the breadcrumbs. Which of
|
|
10
14
|
# that record's pages is the host's business as much as the button is: the
|
|
11
15
|
# routes say where it goes as well as whether it exists.
|
|
12
16
|
def bare_action_buttons(record)
|
|
@@ -19,11 +23,14 @@ module Recourse
|
|
|
19
23
|
|
|
20
24
|
# Whether this is that page. An index is a page to reach the action from and a
|
|
21
25
|
# `new` is a form to fill in on the way: either one means there is more to this
|
|
22
|
-
# than a button. A
|
|
23
|
-
#
|
|
24
|
-
#
|
|
26
|
+
# than a button. A page of its own is where the button belongs and the only place
|
|
27
|
+
# it belongs, so a location's card does not offer to fetch one service's answer
|
|
28
|
+
# while another service's is open. And a nesting with no page anywhere stands on
|
|
29
|
+
# the record's own page, since every other page of the record is about something
|
|
30
|
+
# else — a place's ZIP is not where a sweep of the place is offered.
|
|
25
31
|
def bare_action_page?(nested)
|
|
26
32
|
return false if routed?(nested, 'index') || routed?(nested, 'new')
|
|
33
|
+
return nested == controller.controller_path if idless_route? nested, 'show'
|
|
27
34
|
|
|
28
35
|
record_page?
|
|
29
36
|
end
|
|
@@ -33,12 +40,28 @@ module Recourse
|
|
|
33
40
|
!resource_parent && controller.action_name == 'show'
|
|
34
41
|
end
|
|
35
42
|
|
|
36
|
-
# The
|
|
37
|
-
#
|
|
43
|
+
# The first of the two the routes drew that needs no id and the record leaves
|
|
44
|
+
# open: a `create` on the collection, or a singular resource's `destroy`. Both,
|
|
45
|
+
# where a singular routed each of them has the record to tell them apart. A
|
|
46
|
+
# member action wants the row it acts on, and a row is what a table is for.
|
|
38
47
|
def bare_action_button(record, nested)
|
|
39
|
-
|
|
48
|
+
action, method = BARE_ACTIONS.find do |one, _|
|
|
49
|
+
idless_route?(nested, one) && bare_action_open?(one)
|
|
50
|
+
end
|
|
51
|
+
return unless action
|
|
52
|
+
|
|
53
|
+
[bare_action_label(nested, action), bare_action_url(record, nested, action), method]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Whether the record leaves it to be done. On a singular resource's own page the
|
|
57
|
+
# record is what decides, and a `has_one` answers only one of the two: nothing to
|
|
58
|
+
# create where there is already one, nothing to delete where there is none.
|
|
59
|
+
# Elsewhere nothing here knows what the action would touch, so the routes stay
|
|
60
|
+
# the whole check.
|
|
61
|
+
def bare_action_open?(action)
|
|
62
|
+
return true unless idless_route? controller.controller_path, 'show'
|
|
40
63
|
|
|
41
|
-
|
|
64
|
+
action == :create ? resource_record.nil? : resource_record.present?
|
|
42
65
|
end
|
|
43
66
|
|
|
44
67
|
# The resource's own word, which a host renames in a locale like any other
|
|
@@ -47,18 +70,19 @@ module Recourse
|
|
|
47
70
|
# that is the only thing telling two routes to the same model apart — without it
|
|
48
71
|
# a `quick/memos` action and the `memos` beside it both read `Add memo`. The tab
|
|
49
72
|
# for a nested index is named from the same split, so the two agree.
|
|
50
|
-
def bare_action_label(nested)
|
|
73
|
+
def bare_action_label(nested, action)
|
|
51
74
|
name, namespace = nested_segments nested, card_path
|
|
52
75
|
model = Recourse.downcase Recourse.known_singular(name)
|
|
53
76
|
lead = namespace_words namespace
|
|
54
77
|
|
|
55
|
-
t
|
|
78
|
+
t "recourse.#{action == :create ? 'add' : 'delete'}",
|
|
79
|
+
model: [lead.presence, model].compact.join(' ')
|
|
56
80
|
end
|
|
57
81
|
|
|
58
|
-
def bare_action_url(record, nested)
|
|
82
|
+
def bare_action_url(record, nested, action)
|
|
59
83
|
parent = card_path.split('/').last.singularize
|
|
60
84
|
|
|
61
|
-
url_for controller: "/#{nested}", action
|
|
85
|
+
url_for controller: "/#{nested}", action:, "#{parent}_id": record.id
|
|
62
86
|
end
|
|
63
87
|
end
|
|
64
88
|
end
|
|
@@ -44,21 +44,36 @@ module Recourse
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# One tab per resource nested under the record, in the order routes.rb nested
|
|
47
|
-
# them — the order the sidebar already keeps. The nested
|
|
48
|
-
# requirement:
|
|
49
|
-
#
|
|
50
|
-
#
|
|
47
|
+
# them — the order the sidebar already keeps. The nested route is the whole
|
|
48
|
+
# requirement: an index earns a tab reading its rows, and a singular resource
|
|
49
|
+
# earns one reading its record. A `has_many` of that name decides how the first
|
|
50
|
+
# of those reads, and a counter cache whether it carries a number, but neither
|
|
51
|
+
# is what puts it there.
|
|
51
52
|
def nested_tabs(record, path)
|
|
52
53
|
Recourse.nested_under(path).filter_map do |nested|
|
|
53
|
-
|
|
54
|
+
action = nested_tab_action nested
|
|
55
|
+
next unless action
|
|
54
56
|
|
|
55
57
|
name, namespace = nested_segments nested, path
|
|
56
58
|
|
|
57
|
-
[nested_tab_label(record, name, namespace
|
|
58
|
-
nested == controller.controller_path,]
|
|
59
|
+
[nested_tab_label(record, name, namespace, action),
|
|
60
|
+
nested_url(record, path, nested, action), nested == controller.controller_path,]
|
|
59
61
|
end
|
|
60
62
|
end
|
|
61
63
|
|
|
64
|
+
# Which page of the nesting its tab opens, or nil where it has none to open: the
|
|
65
|
+
# index where one is routed, and otherwise the one record a singular resource
|
|
66
|
+
# keeps — a `show` the router needs no id for, which is what tells
|
|
67
|
+
# `recourse :memo` from `recourses :memos` here, since Rails routes both to the
|
|
68
|
+
# same plural controller. An index wins where a host drew both, being the page
|
|
69
|
+
# that reads every row rather than one of them. A nesting with neither page is an
|
|
70
|
+
# action, and its button stands beside the breadcrumbs instead.
|
|
71
|
+
def nested_tab_action(nested)
|
|
72
|
+
return :index if routed? nested, 'index'
|
|
73
|
+
|
|
74
|
+
:show if idless_route? nested, 'show'
|
|
75
|
+
end
|
|
76
|
+
|
|
62
77
|
def tab_label(action)
|
|
63
78
|
word = tag.span t("recourse.#{action}"), class: 'recourse-tab-word'
|
|
64
79
|
|
|
@@ -28,7 +28,7 @@ module Recourse
|
|
|
28
28
|
nested = nested_path_of path, association
|
|
29
29
|
return tag.span(counted, **named) unless nested && routed?(nested, 'index')
|
|
30
30
|
|
|
31
|
-
turbo_link_to counted, nested_url(resource, path, nested), **named
|
|
31
|
+
turbo_link_to counted, nested_url(resource, path, nested, :index), **named
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# The two forms of one count, of which a stylesheet ever shows one: the bare
|
|
@@ -10,13 +10,26 @@ module Recourse
|
|
|
10
10
|
crumbs = parent_breadcrumbs
|
|
11
11
|
leaf = breadcrumb_leaf
|
|
12
12
|
here = controller.controller_path
|
|
13
|
-
return crumbs << [here,
|
|
13
|
+
return crumbs << [here, breadcrumb_name, nil] unless leaf
|
|
14
14
|
|
|
15
|
-
crumbs << [here,
|
|
15
|
+
crumbs << [here, breadcrumb_name, index_url] << [nil, leaf, nil]
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
#
|
|
19
|
-
#
|
|
18
|
+
# What the crumb naming this resource reads: its plural, or its singular where
|
|
19
|
+
# the routes drew one record rather than a list. Rails routes a singular resource
|
|
20
|
+
# to a plural controller, so the path says `properties` for the one property a
|
|
21
|
+
# location keeps -- and the crumb over it would read `HouseCanaries` for a page
|
|
22
|
+
# there is only ever one of. The same word the tab leading here took, from the
|
|
23
|
+
# same place, since the two stand for one page.
|
|
24
|
+
def breadcrumb_name
|
|
25
|
+
return resources_name unless idless_route? controller.controller_path, 'show'
|
|
26
|
+
|
|
27
|
+
Recourse.known_singular controller.controller_name
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Where this resource's index is, or nil where it has none: a singular resource
|
|
31
|
+
# is one record reached with no id, so there is no list of it to go back to and
|
|
32
|
+
# the crumb naming it is read out rather than linked.
|
|
20
33
|
def index_url
|
|
21
34
|
url_for action: :index if routed_action? 'index'
|
|
22
35
|
end
|
|
@@ -22,8 +22,8 @@ module Recourse
|
|
|
22
22
|
# named after the path the children are nested under rather than after the page
|
|
23
23
|
# being served, since a page nested one level up is served by another controller
|
|
24
24
|
# entirely.
|
|
25
|
-
def nested_url(record, path, nested)
|
|
26
|
-
url_for controller: "/#{nested}", action
|
|
25
|
+
def nested_url(record, path, nested, action)
|
|
26
|
+
url_for controller: "/#{nested}", action:,
|
|
27
27
|
"#{path.split('/').last.singularize}_id": record.id
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -44,7 +44,10 @@ module Recourse
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# What the record on the page is called, by whatever its model labels it with —
|
|
47
|
-
# and nothing where there is no record to name.
|
|
47
|
+
# and nothing where there is no record to name. A singular resource is one the
|
|
48
|
+
# host finds, since the path carries no id, and a `has_one` it finds nothing for
|
|
49
|
+
# is still a page: the crumb above it goes unnamed rather than taking the chrome
|
|
50
|
+
# down with it, and what the body says about the absence is the host's to write.
|
|
48
51
|
def resource_record_label
|
|
49
52
|
return unless resource_record
|
|
50
53
|
|
|
@@ -23,6 +23,16 @@ module Recourse
|
|
|
23
23
|
|
|
24
24
|
@recourse_routed.include? "#{controller_path}##{action}"
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
# True where the route needs no id of its own — a collection action, or a
|
|
28
|
+
# singular resource's. What a button on the parent can reach, in other words:
|
|
29
|
+
# a member action wants a row, and a row is what a table is for. Asked of
|
|
30
|
+
# `Recourse` rather than answered here: a write asks the same question of the
|
|
31
|
+
# same routes, and one of them scanning while the other remembers is two answers
|
|
32
|
+
# waiting to disagree.
|
|
33
|
+
def idless_route?(controller_path, action)
|
|
34
|
+
Recourse.idless_route? controller_path, action
|
|
35
|
+
end
|
|
26
36
|
end
|
|
27
37
|
end
|
|
28
38
|
end
|
|
@@ -4,15 +4,17 @@ module Recourse
|
|
|
4
4
|
module Tabs
|
|
5
5
|
private
|
|
6
6
|
|
|
7
|
-
# What a nested
|
|
8
|
-
# and lends its icon; a route the parent has no association for
|
|
9
|
-
# path instead — a full index reached under a record. There is no model to
|
|
10
|
-
# then, so such a tab carries neither an icon nor a count.
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
# What a nested resource's tab reads as. For an index, a `has_many` of that name
|
|
8
|
+
# counts its rows and lends its icon; a route the parent has no association for
|
|
9
|
+
# is named after the path instead — a full index reached under a record. There is no model to
|
|
10
|
+
# ask then, so such a tab carries neither an icon nor a count. A singular
|
|
11
|
+
# resource is one record and never a list, so no association is asked for it at
|
|
12
|
+
# all: a `has_many` of that name would count rows this page is not.
|
|
13
|
+
def nested_tab_label(record, name, namespace, action)
|
|
14
|
+
association = nested_association record, name if action == :index
|
|
13
15
|
return association_tab_label record, association, namespace if association
|
|
14
16
|
|
|
15
|
-
routed_tab_name name, namespace
|
|
17
|
+
routed_tab_name name, namespace, action
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def nested_association(record, name)
|
|
@@ -20,10 +22,14 @@ module Recourse
|
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
# `Messages`, and `Booked messages` where a namespace leads — the same shape
|
|
23
|
-
# the counted tab keeps. Humanized off the path, since nothing else
|
|
24
|
-
|
|
25
|
+
# the counted tab keeps. Humanized off the path for an index, since nothing else
|
|
26
|
+
# answers; the model's own singular for one record, since there is a model to ask
|
|
27
|
+
# and only ever one row of it. That singular is the word the bare action's button
|
|
28
|
+
# takes, from the same split, so a tab and a button under one record cannot come
|
|
29
|
+
# to disagree about what the resource is called.
|
|
30
|
+
def routed_tab_name(name, namespace, action)
|
|
25
31
|
lead = namespace_words namespace
|
|
26
|
-
title = name.humanize
|
|
32
|
+
title = action == :show ? Recourse.known_singular(name) : name.humanize
|
|
27
33
|
title = Recourse.downcase title if lead.present?
|
|
28
34
|
|
|
29
35
|
[lead.presence&.upcase_first, title].compact.join ' '
|
|
@@ -80,8 +86,22 @@ module Recourse
|
|
|
80
86
|
namespace.map { |segment| Recourse.downcase segment.humanize }.join ' '
|
|
81
87
|
end
|
|
82
88
|
|
|
89
|
+
# The counter a tab reads. A `has_many through:` keeps none of its own, and the
|
|
90
|
+
# join it goes through usually does: a market's ZIPs are reached through its
|
|
91
|
+
# territories, and `territories_count` is the number of them it holds. The figure
|
|
92
|
+
# is of the join rows, which is what the record actually keeps a count of.
|
|
83
93
|
def counter_column_of(model, association)
|
|
84
|
-
model
|
|
94
|
+
counter_of(model, association) || counter_of(model, join_of(model, association))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def counter_of(model, association)
|
|
98
|
+
model.recourse_counters.find { |_, one| one == association }&.first if association
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def join_of(model, association)
|
|
102
|
+
name = association.options[:through]
|
|
103
|
+
|
|
104
|
+
model.reflect_on_association name if name
|
|
85
105
|
end
|
|
86
106
|
end
|
|
87
107
|
end
|
data/lib/recourse/routes.rb
CHANGED
|
@@ -35,6 +35,23 @@ module Recourse
|
|
|
35
35
|
resources(*names, **options) { draw_within keepable, positionable, block }
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
# What `resource` draws, recorded the same way: one record reached without an id,
|
|
39
|
+
# at `/providers/5/authentication`. Rails routes a singular resource to a plural
|
|
40
|
+
# controller, so that is the path declared here — and recording it is the whole
|
|
41
|
+
# point, since an action drawn with no index of its own earns its button on the
|
|
42
|
+
# parent, and only a recorded nesting is ever looked for there.
|
|
43
|
+
def recourse(*names, **)
|
|
44
|
+
refuse_unscoped_nesting names
|
|
45
|
+
|
|
46
|
+
names.each do |name|
|
|
47
|
+
path = [current_module, name.to_s.pluralize].compact.join '/'
|
|
48
|
+
record_declaration path
|
|
49
|
+
Controllers.define_missing path
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
resource(*names, **)
|
|
53
|
+
end
|
|
54
|
+
|
|
38
55
|
private
|
|
39
56
|
|
|
40
57
|
# The module is the namespace being drawn in, so a resource is declared and its
|
data/lib/recourse/routing.rb
CHANGED
|
@@ -11,6 +11,18 @@ module Recourse
|
|
|
11
11
|
route.defaults[:controller] == controller_path && route.defaults[:action] == action.to_s
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
|
+
|
|
15
|
+
# True where that route needs no id of its own — a collection action, or a singular
|
|
16
|
+
# resource's, which is what tells `recourse :memo` from `recourses :memos`: Rails
|
|
17
|
+
# routes both to the same plural controller, and only the route says which it is.
|
|
18
|
+
# Nil where no such route is drawn at all, which is a third answer and reads as one.
|
|
19
|
+
def idless_route?(controller_path, action)
|
|
20
|
+
route = Rails.application.routes.routes.find do |one|
|
|
21
|
+
one.defaults[:controller] == controller_path && one.defaults[:action] == action.to_s
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
route&.required_parts&.exclude? :id
|
|
25
|
+
end
|
|
14
26
|
end
|
|
15
27
|
|
|
16
28
|
extend Routing
|
data/lib/recourse/searchable.rb
CHANGED
|
@@ -10,9 +10,11 @@ module Recourse
|
|
|
10
10
|
module Searchable
|
|
11
11
|
include Columns, Filters, Terms
|
|
12
12
|
|
|
13
|
-
# How many rows a menu may hold before it stops being a menu.
|
|
14
|
-
# list to pick from
|
|
15
|
-
|
|
13
|
+
# How many rows a menu may hold before it stops being a menu. A couple of hundred
|
|
14
|
+
# specialties are a list to pick from -- the menu carries a search box, so its length
|
|
15
|
+
# costs scrolling rather than reading -- and three thousand counties are a page of
|
|
16
|
+
# HTML nobody reads.
|
|
17
|
+
MENU_LIMIT = 200
|
|
16
18
|
|
|
17
19
|
# Attributes Ransack may read: every column that is not encrypted, plus the
|
|
18
20
|
# encrypted ones a search can still match whole — `cont` reads ciphertext and
|
data/lib/recourse/titles.rb
CHANGED
|
@@ -38,8 +38,11 @@ module Recourse
|
|
|
38
38
|
model.respond_to?(:model_name) ? model_title(model) : segment.humanize
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
# And the singular of that, for the
|
|
42
|
-
#
|
|
41
|
+
# And the singular of that, for the two places a page stands for one record rather
|
|
42
|
+
# than a list: the tab a singular resource earns, and the button a bare action
|
|
43
|
+
# earns. Off `known_title` rather than `model_title(count: 1)`, since a name this
|
|
44
|
+
# app has no class for is humanized from a plural path segment and needs
|
|
45
|
+
# singularizing too.
|
|
43
46
|
def known_singular(name)
|
|
44
47
|
known_title(name).singularize
|
|
45
48
|
end
|
data/lib/recourse/version.rb
CHANGED
data/lib/recourse.rb
CHANGED
|
@@ -88,8 +88,9 @@ module Recourse
|
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
# The same, answering nil where there is no such model rather than raising. A bare
|
|
91
|
-
# action is a verb — `
|
|
92
|
-
# so whether a name has a model behind it
|
|
91
|
+
# action is a verb — `recourse :sweep, only: :create` — and the gem labels its button
|
|
92
|
+
# from the path alone, so whether a name has a model behind it has to be a question
|
|
93
|
+
# and not an accusation.
|
|
93
94
|
def self.model?(name) = model_name(name).safe_constantize
|
|
94
95
|
|
|
95
96
|
# A namespaced resource is `admin/sources`, and the model it lists is a Source.
|