crumbly 2.1.0 → 2.2.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: 79dd182434d2559579a926dc460606527bea5ab63bda1e1c00bcef7ecd3e221d
4
- data.tar.gz: 265c0a56aa5ad03ee08d9d825b31432af7569dab24cd8d80974ed1a1e2fd2bb9
3
+ metadata.gz: 06aa2669dc2486a8c255ca46215e1c65e7ca3eaf76cd834501a7c0f8a1734237
4
+ data.tar.gz: 37ab7df264cbb31555bc2f3e646c2ea10036057dac7e9f4f70e022506906f6cf
5
5
  SHA512:
6
- metadata.gz: b465f51629faefb7bfce68edeb9c8c01cb4a5b9099ecc942add8fa9ff753f3d2bccc972c6d1b19c5ef34435121b2fca127c723c5297e10343298ed389c269920
7
- data.tar.gz: 39682d63dc8464e7fc3881ec95d3834f34fa1c9a1975682fa53c016e6fe105611d92d2b4f3ce784fa2792500b46e6bf59f46453c4d2fd9a71584b34ca8b8a6b3
6
+ metadata.gz: 5d77cc663933f726c4923fb1a5655ecff481b0eaabb6bcc0fb852306bf342e7f7838d420a68b823234fe3a13d4ea40355a500bdce8e80dc86ecb0a24788b2dc9
7
+ data.tar.gz: 6d3a5af07dcd0d5e69b3a6d2f6669c03c4eae29d182a17d30fa69f8f24886afd67f27d2b89e5394561f166c4033d4166d792fb4f5acca46902290b7140e6bb27
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.2.0] — 2026-11-06
6
+
7
+ A trail class can turn off derived trails with `derive_trails false`, and a derived level labels a record without its own `to_s` "Invoice 12" instead of `#<Invoice:0x…>`.
8
+
9
+ **New**
10
+ - `derive_trails false` in a trail class, for apps that want only the trails they write, as with gretel: a view without a `breadcrumb` call gets an empty `breadcrumb_trail` and a nil `breadcrumb_json_ld`. Subclasses inherit it and `derive_trails true` turns it back on. In such a class, `Crumbly.verify!` reports a crumb that only a derived trail would read as unused, and `Crumbly::Oracle.redundant_calls` lists nothing.
11
+
12
+ **Fixed**
13
+ - A derived level whose record's model doesn't define `to_s` is labelled with the translated model name and id, "Invoice 12", instead of Ruby's `#<Invoice:0x000…>`. A model's own `to_s` still wins.
14
+
5
15
  ## [2.1.0] — 2026-11-06
6
16
 
7
17
  Crumbs refuse `javascript:`, `vbscript:` and `data:` URLs, and a nil record, odd bytes or `=begin` comments no longer break Crumbly.
@@ -76,7 +86,8 @@ tests. Replaces gretel: `crumbly-convert` turns its breadcrumbs files into Crumb
76
86
  `breadcrumb` calls a derived trail may now cover.
77
87
  - `Crumbly::Oracle.compare` draws trails with gretel and with Crumbly, from a test of an app migrating.
78
88
 
79
- [Unreleased]: https://github.com/levups/crumbly/compare/v2.1.0...HEAD
89
+ [Unreleased]: https://github.com/levups/crumbly/compare/v2.2.0...HEAD
90
+ [2.2.0]: https://github.com/levups/crumbly/compare/v2.1.0...v2.2.0
80
91
  [2.1.0]: https://github.com/levups/crumbly/compare/v2.0.0...v2.1.0
81
92
  [2.0.0]: https://github.com/levups/crumbly/compare/v1.1.0...v2.0.0
82
93
  [1.1.0]: https://github.com/levups/crumbly/compare/v1.0.0...v1.1.0
data/README.md CHANGED
@@ -169,13 +169,14 @@ When a view doesn't call `breadcrumb`, Crumbly builds the trail from the action:
169
169
  | Action | Trail |
170
170
  |---|---|
171
171
  | `index` | the collection: your `posts` crumb if you wrote one, or else the model's plural name linked to the `index` route |
172
- | `show` | the record: your `post` crumb, or else its `to_s` linked to the `show` route, under the collection |
172
+ | `show` | the record: your `post` crumb, or else its label linked to the `show` route, under the collection |
173
173
  | `new`, `create` | "New" under the collection |
174
174
  | `edit`, `update` | "Edit" under the record |
175
175
 
176
176
  The record is the one the controller has already loaded, `@post` in `PostsController`. Crumbly never runs a query
177
177
  to find it. "New" and "Edit" come from the `crumbly.new` and `crumbly.edit` translations, so you can translate
178
- them.
178
+ them. A record's label is its `to_s`. A model without a `to_s` of its own gets its translated model name and id
179
+ instead ("Post 5"), not `#<Post:0x000...>`.
179
180
 
180
181
  Nested routes work too. On `/admin/posts/5/comments`, the trail is Posts › Post 5 › Comments, and the post is
181
182
  the `@post` the controller loaded. If the controller didn't load it, Crumbly skips that level and keeps Posts.
@@ -183,6 +184,19 @@ the `@post` the controller loaded. If the controller didn't load it, Crumbly ski
183
184
  To change how these trails are built, override the private methods `collection_crumb`, `record_crumb`,
184
185
  `new_record_crumb` or `edit_record_crumb` in your trail class.
185
186
 
187
+ To turn them off, set `derive_trails false` in the trail class. A view that doesn't call `breadcrumb` then has no
188
+ trail, as with gretel: `breadcrumb_trail` returns `[]` and `breadcrumb_json_ld` returns `nil`.
189
+
190
+ ```ruby
191
+ module Admin
192
+ class Breadcrumbs < Crumbly::Trail
193
+ derive_trails false
194
+ end
195
+ end
196
+ ```
197
+
198
+ Subclasses inherit the setting, and `derive_trails true` turns it back on in one of them.
199
+
186
200
  ### Which crumb wins
187
201
 
188
202
  Crumbly takes the first of these that applies:
@@ -194,6 +208,8 @@ Crumbly takes the first of these that applies:
194
208
  3. The trail Crumbly works out. Its parent levels follow the same rule: your crumb for the parent's model if there
195
209
  is one, or else a level Crumbly builds.
196
210
 
211
+ With `derive_trails false`, only the first applies.
212
+
197
213
  ## Testing your trails
198
214
 
199
215
  Add one test:
@@ -218,7 +234,7 @@ fails when:
218
234
  - a view or another crumb calls a crumb with the wrong number of arguments (calls spread over several lines, or
219
235
  with a splat, are skipped);
220
236
  - a crumb is never used. A crumb is used when another crumb, a view or a controller calls it, when a trail
221
- Crumbly works out reads it, or when it is named after a model.
237
+ Crumbly works out reads it (unless its class sets `derive_trails false`), or when it is named after a model.
222
238
 
223
239
  A crumb defined twice in the same class is caught even earlier: the class raises `Crumbly::DuplicateCrumb` when it
224
240
  loads.
@@ -296,6 +312,7 @@ It prints each line it left out or couldn't convert exactly, and with `--views`,
296
312
  Crumbly may now make unnecessary. It needs Prism 1.2 or later: Ruby 3.4 comes with it, and on Ruby 3.3 you add
297
313
  `gem "prism"` to the development group. While gretel is still installed on the migration branch,
298
314
  `Crumbly::Oracle` draws each trail your tests render with both gems and lists where the labels or URLs differ.
315
+ To keep gretel's empty trail on pages whose views name no crumb, set `derive_trails false` in the trail class.
299
316
 
300
317
  The [migration guide](https://levups.github.io/crumbly/migration.html) has every step: what the converter maps to
301
318
  what, what it leaves to you, the comparison in your tests, and the same walk-through for loaf and
data/lib/crumbly/trail.rb CHANGED
@@ -37,6 +37,18 @@ module Crumbly
37
37
  raise DuplicateCrumb, "#{self}##{name} is defined twice: the second definition would replace the first"
38
38
  end
39
39
 
40
+ def derive_trails(derive)
41
+ raise ArgumentError, "derive_trails takes true or false, not #{derive.inspect}" unless derive in true | false
42
+
43
+ @derive_trails = derive
44
+ end
45
+
46
+ def derive_trails?
47
+ return @derive_trails unless @derive_trails.nil?
48
+
49
+ self == Trail || superclass.derive_trails?
50
+ end
51
+
40
52
  def crumb?(name)
41
53
  trail_class = self
42
54
  while trail_class < Trail
@@ -52,6 +64,8 @@ module Crumbly
52
64
  end
53
65
 
54
66
  def derive(helpers)
67
+ return [] unless derive_trails?
68
+
55
69
  new(helpers).send(:derived_levels)
56
70
  end
57
71
  end
@@ -196,7 +210,14 @@ module Crumbly
196
210
  def record_crumb(record)
197
211
  return crumb_for(record) if crumb_name_for(record)
198
212
 
199
- crumb(record.to_s, route_path(action: "show", id: record), parent: collection_crumb)
213
+ crumb(record_label(record), route_path(action: "show", id: record), parent: collection_crumb)
214
+ end
215
+
216
+ # Kernel#to_s would label the level "#<Invoice:0x000...>".
217
+ def record_label(record)
218
+ return record.to_s unless record.class.instance_method(:to_s).owner == Kernel && record.class.respond_to?(:model_name)
219
+
220
+ "#{record.model_name.human} #{record.to_param}"
200
221
  end
201
222
 
202
223
  def new_record_crumb
@@ -235,7 +256,7 @@ module Crumbly
235
256
  return crumb_for(parent.record) if crumb_name_for(parent.record)
236
257
 
237
258
  name = parent.name.pluralize
238
- crumb(parent.record.to_s, route_path(resources: name, action: "show", id: parent.id, **route_parent_ids(ancestors)),
259
+ crumb(record_label(parent.record), route_path(resources: name, action: "show", id: parent.id, **route_parent_ids(ancestors)),
239
260
  parent: nested_collection_crumb(name, ancestors))
240
261
  end
241
262
 
@@ -165,8 +165,9 @@ module Crumbly
165
165
  end
166
166
 
167
167
  def dead_crumbs
168
- reachable_names = called_names + derived_names
168
+ reachable_names_with_derivation = called_names + derived_names
169
169
  trail_classes.flat_map do |trail_class|
170
+ reachable_names = trail_class.derive_trails? ? reachable_names_with_derivation : called_names
170
171
  dead_names = crumb_names(trail_class).reject do |name|
171
172
  reachable_names.include?(name) || model_name?(name)
172
173
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crumbly
4
- VERSION = "2.1.0"
4
+ VERSION = "2.2.0"
5
5
  end
@@ -2,7 +2,8 @@
2
2
 
3
3
  module Crumbly
4
4
  module ViewHelpers
5
- # The view names its crumb; a view that says nothing gets the trail derived from its controller and action.
5
+ # The view names its crumb; a view that says nothing gets the trail derived from its controller and action,
6
+ # or none under `derive_trails false`.
6
7
  def breadcrumb(target, *arguments)
7
8
  @crumbly_breadcrumb = [target, arguments]
8
9
  @crumbly_trail = nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crumbly
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Level UP Solutions