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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83d3a5ce095d609bf7da53c08839eab3bc4924c6e6150f9675d79725c4c9f53f
4
- data.tar.gz: d2325213f4e461394adb107d3a3834f6ef668159ce6e90e7fa77aa09d09a8389
3
+ metadata.gz: 2b7756a87eb7ee1ef4267480a0e8370c1b63e50623a7a22cf0efe6a7a7a46644
4
+ data.tar.gz: 0e7e84177bc55634878165dd8ba0fe19ad2c9d635c586211477d48431354cb47
5
5
  SHA512:
6
- metadata.gz: 54d4c8b8075fc497c45caf991fe7f8e5ea04cacb30bf619a12682e137cf962072140b1ff69eaff9ad881658adb018a440bc344fd3bfb3859ec776bdd077ae87b
7
- data.tar.gz: 9d8a3e17c0cdb3e05f57381beda1c68b31b5b6924d7104717622c29d5658f6faa198ab33d1c576a81ea8e7adf4ccdee3b9be885c248421302229381536dc7445
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 — where the routes drew none — back to the
44
- # record it hangs off, which for a bare action is the page its button stood on.
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
- # The record the id names, under the name Rails would use. `find`, so an id naming
8
- # nothing answers 404.
9
- def find_resource = assign resource_class.find(params.expect(:id))
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 — `recourses :sweeps, only: :create` — labelled from the
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
- <p class='fg-2'><%= t 'recourse.none', models: Recourse.downcase(resources_name) %></p>
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
- <div class='row recourse-values'>
8
- <%= render 'values', resource_key => resource_record %>
9
- </div>
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: { turbo_confirm: destroy_warning(record) } }
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 `create` without an `index` is reached from nowhere, so
9
- # its button lives on the record it hangs off — beside the breadcrumbs. Which of
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 nesting with no page anywhere stands on the record's own page,
23
- # since every other page of the record is about something else — a place's memos
24
- # are not where a sweep of the place is offered.
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 `create` the routes drew on the collection, which needs no id: a member
37
- # action wants the row it acts on, and a row is what a table is for.
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
- return unless routed? nested, 'create'
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
- [bare_action_label(nested), bare_action_url(record, nested), :post]
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 'recourse.add', model: [lead.presence, model].compact.join(' ')
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: :create, "#{parent}_id": record.id
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 index is the whole
48
- # requirement: a `has_many` of that name decides how the tab reads, and a counter
49
- # cache whether it carries a number, but neither is what puts it there. A nesting
50
- # with no index is an action, and its button stands beside the breadcrumbs instead.
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
- next unless routed? nested, 'index'
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), nested_url(record, path, nested),
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, resources_name, nil] unless leaf
13
+ return crumbs << [here, breadcrumb_name, nil] unless leaf
14
14
 
15
- crumbs << [here, resources_name, index_url] << [nil, leaf, nil]
15
+ crumbs << [here, breadcrumb_name, index_url] << [nil, leaf, nil]
16
16
  end
17
17
 
18
- # Where this resource's index is, or nil where it has none, in which case the crumb
19
- # naming it is read out rather than linked.
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: :index,
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 index's tab reads as. A `has_many` of that name counts its rows
8
- # and lends its icon; a route the parent has no association for is named after the
9
- # path instead — a full index reached under a record. There is no model to ask
10
- # then, so such a tab carries neither an icon nor a count.
11
- def nested_tab_label(record, name, namespace)
12
- association = nested_association record, name
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 answers.
24
- def routed_tab_name(name, namespace)
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.recourse_counters.find { |_, one| one == association }&.first
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
@@ -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
@@ -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
@@ -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. Fifty states are a
14
- # list to pick from; three thousand counties are a page of HTML nobody reads.
15
- MENU_LIMIT = 100
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
@@ -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 button a bare action earns: `Add sweep` is one
42
- # sweep, and the same split that names the tab beside it.
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
@@ -1,4 +1,4 @@
1
1
  module Recourse
2
2
  # Version of the gem, read by the gemspec and by hosts checking compatibility.
3
- VERSION = '5.6.3'
3
+ VERSION = '5.8.0'
4
4
  end
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 — `recourses :sweeps, only: :create` — labelled from the path alone,
92
- # so whether a name has a model behind it is a question and not an accusation.
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.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recourse
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.3
4
+ version: 5.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob