recourse 7.11.2 → 7.12.1

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: 96bb337b5a0517cc5f7715b898c0cffd01c33fc6b8e7a6750f939b7361879fd4
4
- data.tar.gz: e77f1f0f2f4a42955669e988302bff0df520aa98a8cb7ce0d171cda7e7bd85ea
3
+ metadata.gz: c3f3474232bc6f128f093102e6695f23e9579ad323bc60bba64fc5bfeb47c5c1
4
+ data.tar.gz: 0ae245a7fd65040853c0ce90112241b19c8a47d053a86d48a513a229e35191ec
5
5
  SHA512:
6
- metadata.gz: 9d203812049cdcd045323970939f23c4aeb825e5cd904b949e511b8700bd24781ae5cb36ef83c41b7e90547665a5c255c00c4158425a3c7a1c04b86d9182a223
7
- data.tar.gz: 17c113285f5c356e86fad57838069d9c42b8c272d19950649be5f992a674de2bf1b1e6a60e75be220ec77ea48e15462459127cb94b7ba6ffffe5a2c9372576da
6
+ metadata.gz: 1087e317accf95b332f2e97f7aee06e22275a0129a2d6d7e8d4b3e35a0cbd84a059ce69e9f265559f6ce2d0ade6bd79b344ecb6dfe1feb86c45710da62c6372d
7
+ data.tar.gz: 55486b544450eaf334cd99d00e81fd493139870780fe78c9d2df90299b44d9a9cd2896a3a8c3e4115a4fb8c3a3929481a7390f876dcd8ea4369f9ffcdf5227c3
data/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ For more information about changelogs, check [Keep a Changelog](http://keepachan
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## 7.12.1 - 2026-10-02
11
+
12
+ * [FIX] A model kept in a module is bookmarked and nested under by its route's name
13
+
14
+ The id a bookmark posts and a nested page reads its parent by is named after the route,
15
+ `user_id`, and no longer after the model's whole name, `admin_user_id`, which no route draws.
16
+ A model's default icon and the name its parent is assigned to (`@user`) drop the module too.
17
+ For a model at the top level, all of these read as they did.
18
+
19
+ ## 7.12.0 - 2026-10-02
20
+
21
+ * [FEATURE] A route drawn in a module lists the model kept in that module
22
+
23
+ `scope module: :admin { recourses :users }` lists an `Admin::User`, and so does a page
24
+ nested under a post, `admin/posts/users`. Only an Active Record model in the module counts;
25
+ where there is none, the route lists the top-level class of its last word, as before, so
26
+ `admin/comments` still lists a `Comment`. A host keeping models in a module will want
27
+ `def self.use_relative_model_naming? = true` on it, so their routes and params say `user`
28
+ rather than `admin_user`.
29
+
10
30
  ## 7.11.2 - 2026-09-26
11
31
 
12
32
  * [FIX] The count in a tab is delimited
@@ -18,7 +18,7 @@ module Recourse
18
18
  model = path && Recourse.model?(path)
19
19
  return unless model && @recourse_parent.is_a?(model)
20
20
 
21
- instance_variable_set "@#{model.model_name.singular}", @recourse_parent
21
+ instance_variable_set "@#{model.model_name.element}", @recourse_parent
22
22
  end
23
23
  end
24
24
  end
@@ -27,7 +27,7 @@ module Recourse
27
27
  model = parent && Recourse.model?(parent)
28
28
  return unless model
29
29
 
30
- id = request.path_parameters[:"#{model.model_name.singular}_id"]
30
+ id = request.path_parameters[:"#{model.model_name.singular_route_key}_id"]
31
31
  model.find_by id: id if id
32
32
  end
33
33
 
@@ -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
@@ -83,7 +83,7 @@ module Recourse
83
83
 
84
84
  def bookmark_url(record)
85
85
  url_for controller: "/#{resource_controller_path}/bookmarks", action: :create,
86
- "#{resource_model.model_name.singular}_id": record.id
86
+ "#{resource_model.model_name.singular_route_key}_id": record.id
87
87
  end
88
88
  end
89
89
  end
@@ -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
@@ -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
data/lib/recourse/maps.rb CHANGED
@@ -18,7 +18,7 @@ module Recourse
18
18
  def self.mappable?(model) = placed?(model) || (POINT_COLUMNS - model.column_names).empty?
19
19
 
20
20
  # The layer a model's places are drawn on, or nil for a model that is no geography.
21
- def self.boundary(model) = BOUNDARIES[model.model_name.singular.to_sym]
21
+ def self.boundary(model) = BOUNDARIES[model.model_name.element.to_sym]
22
22
 
23
23
  # Whether a model's rows keep a place ID, which an area or a pin at a place is drawn from.
24
24
  def self.placed?(model) = model.column_names.include? PLACE_COLUMN
@@ -10,7 +10,7 @@ module Recourse
10
10
  # The concept a resource is drawn with, which Unicon names in each icon set it
11
11
  # knows. A model's own name by default — `contact` draws a rolodex, `job` a hammer
12
12
  # — and Unicon answers with a circle for a name it has never heard of.
13
- def recourse_icon = model_name.singular.to_sym
13
+ def recourse_icon = model_name.element.to_sym
14
14
 
15
15
  # Columns the model keeps off its screens — the table, the show page, the
16
16
  # form and the search box — none by default. One name or a list:
@@ -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.2'
3
+ VERSION = '7.12.1'
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.2
4
+ version: 7.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob