recourse 7.11.1 → 7.12.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.

Potentially problematic release.


This version of recourse might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d5b8233bcbb5c24c4df425ab9c5a43f5db42baf5f5801b29ec118ce792871bc
4
- data.tar.gz: 7c5a0bd68a7d5c22d752b2a8fdb2667d0291b6d6bbfbc376218cb5186fa5a2ed
3
+ metadata.gz: 9e4b4229005fd88e1617e55a5f4760da0ebb2d7b897eabd730adeac07c7eb61d
4
+ data.tar.gz: b84e0db179300948a05e77e9bbdbdcbaad8ef1ee794632f42abeec752b3395ad
5
5
  SHA512:
6
- metadata.gz: 67e9c27e1c7489a189e9a001b1c3d191f68ef480555a06f701d68d561bbc8c97f66de6eb307939e6c1bb980fc7afd5ec1b9195f48289e28cc3450e0148de8e59
7
- data.tar.gz: b0415018f1b632c3da2d61e1a77e8b96aafced9fcdbac997a4cb316821f30311805f7ed2e91e6e93d2a270ccbffae119394835f93d072becf9395f18aa159147
6
+ metadata.gz: 2aa90f2ea92f2370ac39c64f0351518785bf3cb3233e7fab52e61cae83d8c90df80362a8c8267d451e8869b5e6da3cf22b9bb38b59c0510d7cc7958355da07e2
7
+ data.tar.gz: b0c47758910fe5c03c28084afa668adec4d6a07c399894f05ea0a7fbf57983c2988a84f72fed6a2ea84cb8a49c0730f9a894240e3ddfeb85f8a1f256b9081e2e
data/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ For more information about changelogs, check [Keep a Changelog](http://keepachan
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## 7.12.0 - 2026-10-02
11
+
12
+ * [FEATURE] A route drawn in a module lists the model kept in that module
13
+
14
+ `scope module: :admin { recourses :users }` lists an `Admin::User`, and so does a page
15
+ nested under a post, `admin/posts/users`. Only an Active Record model in the module counts;
16
+ where there is none, the route lists the top-level class of its last word, as before, so
17
+ `admin/comments` still lists a `Comment`. A host keeping models in a module will want
18
+ `def self.use_relative_model_naming? = true` on it, so their routes and params say `user`
19
+ rather than `admin_user`.
20
+
21
+ ## 7.11.2 - 2026-09-26
22
+
23
+ * [FIX] The count in a tab is delimited
24
+
25
+ `3,506 aircrafts`, not `3506 aircrafts`: the same figure the table's own count and the
26
+ footer already draw with delimiters.
27
+
10
28
  ## 7.11.1 - 2026-09-26
11
29
 
12
30
  * [FIX] A tooltip in the sidebar's foot closes when its icon is clicked
@@ -78,7 +78,7 @@ module Recourse
78
78
  # to match it. Private, the way `recourse_relation` beside it is: naming a model
79
79
  # adds no action.
80
80
  def recourse_model
81
- Recourse.model controller_name
81
+ Recourse.model controller_path
82
82
  end
83
83
 
84
84
  # What the page calls one of its rows: the model's own word, or the route's for a
@@ -15,7 +15,7 @@ module Recourse
15
15
  def resources_name
16
16
  return controller.controller_name.humanize if blob_resource?
17
17
 
18
- Recourse.known_title controller.controller_name
18
+ Recourse.known_title controller.controller_path
19
19
  end
20
20
 
21
21
  # Singular, lowercase name of the resource, e.g. 'contact' — and 'photo' on a page
@@ -72,7 +72,7 @@ module Recourse
72
72
  # Resolved by the controller, which is the one that knows whether the name is a
73
73
  # model of this app's, something a record has attached, or a model a host named.
74
74
  def resource_model
75
- controller_assign('recourse_model') || Recourse.model(controller.controller_name)
75
+ controller_assign('recourse_model') || Recourse.model(controller.controller_path)
76
76
  end
77
77
  end
78
78
  end
@@ -50,7 +50,7 @@ module Recourse
50
50
  # its children out with a gap, and two of them would stand a gap and a space apart.
51
51
  def tab_label_for(icon, words, count = nil)
52
52
  words = tag.span words, class: 'recourse-tab-word' if icon || count
53
- words = tag.span safe_join([count, words], ' ') if count
53
+ words = tag.span safe_join([number_with_delimiter(count), words], ' ') if count
54
54
 
55
55
  safe_join [icon && tag.i(class: "bi bi-#{icon}"), words].compact, ' '
56
56
  end
@@ -8,7 +8,7 @@ module Recourse
8
8
  # `Message` may be a host's own class for gathering rows rather than a table of them,
9
9
  # and asking it would raise rather than draw nothing.
10
10
  def self.known_icon(name)
11
- model = name.to_s.split('/').last.classify.safe_constantize
11
+ model = model? name
12
12
 
13
13
  known_model_icon model if model.respond_to? :recourse_icon
14
14
  end
@@ -25,7 +25,7 @@ module Recourse
25
25
  # `/placeholders/:placeholder_id/bookmark`, where nothing linked to it and
26
26
  # anything reaching it raised. Asked of the constant rather than the class:
27
27
  # loading a model while the routes draw is what Rails 8.2 warns about.
28
- def addressable_rows? = Object.const_defined? Recourse.model_name(parent_resource.name)
28
+ def addressable_rows? = Recourse.model_defined? declared_path(parent_resource.name)
29
29
 
30
30
  # The place a row of a positioned table holds, at `/teams/5/position`. Recorded
31
31
  # nowhere, for the reason the bookmark gives: a tab and a bare-action button both
@@ -33,7 +33,7 @@ module Recourse
33
33
  # than a table of them, and asking it would raise rather than read.
34
34
  def known_title(name)
35
35
  segment = name.to_s.split('/').last
36
- model = segment.classify.safe_constantize
36
+ model = model? name
37
37
 
38
38
  model.respond_to?(:model_name) ? model_title(model) : segment.humanize
39
39
  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 = '7.11.1'
3
+ VERSION = '7.12.0'
4
4
  end
data/lib/recourse.rb CHANGED
@@ -71,11 +71,58 @@ module Recourse
71
71
  # action is a verb — `recourse :sweep, only: :create` — and the gem labels its button
72
72
  # from the path alone, so whether a name has a model behind it has to be a question
73
73
  # and not an accusation.
74
- def self.model?(name) = model_name(name).safe_constantize
74
+ def self.model?(name) = namespaced_model(name) || model_name(name).safe_constantize
75
75
 
76
76
  # A namespaced resource is `admin/sources`, and the model it lists is a Source.
77
77
  def self.model_name(name) = name.to_s.split('/').last.classify
78
78
 
79
+ # Whether a model answers the name, asked of the constants rather than the classes:
80
+ # the routes ask while they draw, and loading a model then is what Rails 8.2 warns
81
+ # about.
82
+ def self.model_defined?(name)
83
+ namespaced_names(name).any? { |namespaced| constant namespaced, load: false } ||
84
+ Object.const_defined?(model_name(name))
85
+ end
86
+
87
+ # A model the app keeps in the route's own module, where there is one: `admin/users`
88
+ # lists an Admin::User, and so does `admin/posts/users`, nested under a post.
89
+ # Only an Active Record model counts, so a concern or a host's own class that shares
90
+ # the name never takes a page over; and where there is none, `admin/comments` lists a
91
+ # Comment as it always has.
92
+ private_class_method def self.namespaced_model(name)
93
+ namespaced_names(name).each do |namespaced|
94
+ model = constant namespaced
95
+ return model if model.is_a?(Class) && model < ActiveRecord::Base
96
+ end
97
+
98
+ nil
99
+ end
100
+
101
+ # The names a model in the route's modules goes by, innermost module first. The
102
+ # segment a nested route adds for its parent is a module no model lives in, so the
103
+ # module around it is asked next.
104
+ private_class_method def self.namespaced_names(name)
105
+ *modules, last = name.to_s.split '/'
106
+ modules.size.downto(1).map { |depth| [*modules[0, depth], last].join('/').classify }
107
+ end
108
+
109
+ # The constant a name gives, looked up one module at a time and never through Object:
110
+ # `safe_constantize` answers `Admin::Comment` with the top-level Comment, since a module
111
+ # looks its constants up there too. `load: false` stops short of the last one,
112
+ # answering only whether it is there.
113
+ private_class_method def self.constant(name, load: true)
114
+ *modules, last = name.split '::'
115
+ scope = modules.inject Object do |outer, part|
116
+ break unless outer.const_defined? part, false
117
+
118
+ outer.const_get part, false
119
+ end
120
+ return unless scope.is_a? Module
121
+ return unless scope.const_defined? last, false
122
+
123
+ load ? scope.const_get(last, false) : true
124
+ end
125
+
79
126
  # Raised for every failure the gem reports, so hosts can rescue one type.
80
127
  class Error < StandardError; end
81
128
  end
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: 7.11.1
4
+ version: 7.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob