petit_poucet 1.0.0 → 1.1.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: 31a0dd9277ecdaa4109bc2baaf3d8a7bc7c7e63ddc25d04986b384476d2b2129
4
- data.tar.gz: 11148aa356349a3d75ff866b972ba628f6d5d389df322aaee6316b06ba87792d
3
+ metadata.gz: 9af4ab73179b6f7ec11b872254231821a51f3f58ca6af6900dc3ecbddb128994
4
+ data.tar.gz: 7840558d52da64060b0a39d45f6288647726f845fc071f9269d53eabe8cae890
5
5
  SHA512:
6
- metadata.gz: 41d0d30be7ca055999c3901aa0704323e15329d87d709eed97e12a840f5b9ee439a53cffc279f1a5a9184354ed588d9d8b279bfa34e47e78a60e0527e6a895a7
7
- data.tar.gz: de911c4ee152f4ea74804b4b9c5f930d306189fde6cd1c021294a73a1381d708a09aaa88fcbaceb4f507f0397bb0b6cba765ba5074a959fd2b52f9acbdeb2412
6
+ metadata.gz: 62469923081b601fe3f218312507ab549f841827c1349a4ba960ad4af63a9811c2857a4eaecd6d21b2cd92afddbcdbb1394c6b1ab0c181e2ce977633d2ba6f41
7
+ data.tar.gz: b1e22b547b0a80cdc6a44150f18a134cc915f0b4b98d94284487fef1923b40e1684e989d80cddc5a079587944134698c10eb533c014cb30682bc92627647584d
data/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.0] - 2025-12-10
11
+
12
+ ### Added
13
+
14
+ - `breadcrumb_group` to apply shared `only:`/`except:` filters to multiple breadcrumbs
15
+ - Conditional `clear_breadcrumbs` with `only:` and `except:` options
16
+ - Nested groups with intelligent option merging (intersection for `:only`, union for `:except`)
17
+
10
18
  ## [1.0.0] - 2025-12-03
11
19
 
12
20
  ### Added
data/README.md CHANGED
@@ -1,14 +1,25 @@
1
- # Petit Poucet
1
+ # Petit Poucet 🥖
2
2
 
3
- [![CI](https://github.com/Sbastien/petit_poucet/actions/workflows/ci.yml/badge.svg)](https://github.com/Sbastien/petit_poucet/actions/workflows/ci.yml)
4
- [![Gem Version](https://badge.fury.io/rb/petit_poucet.svg)](https://rubygems.org/gems/petit_poucet)
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
3
+ [![gem version](https://img.shields.io/gem/v/petit_poucet.svg)](https://rubygems.org/gems/petit_poucet)
4
+ [![gem downloads](https://img.shields.io/gem/dt/petit_poucet.svg)](https://rubygems.org/gems/petit_poucet)
5
+ [![ci](https://img.shields.io/github/actions/workflow/status/Sbastien/petit_poucet/ci.yml?branch=main&label=ci)](https://github.com/Sbastien/petit_poucet/actions/workflows/ci.yml)
6
+ [![license](https://img.shields.io/badge/license-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![ruby](https://img.shields.io/badge/ruby-%3E%3D%203.0-red.svg)](https://www.ruby-lang.org/)
8
+ [![rails](https://img.shields.io/badge/rails-%3E%3D%207.0-red.svg)](https://rubyonrails.org/)
6
9
 
7
- > *Le petit Pouçet les laissoit crier, sçachant bien par où il reviendroit à la maison ; car en marchant il avoit laissé tomber le long du chemin les petits cailloux blancs qu'il avoit dans ses poches.*
8
- >
9
- > — Charles Perrault, *Le Petit Poucet* (1697)
10
+ ***Breadcrumbs for Rails, the simple way.***
10
11
 
11
- A minimal breadcrumbs gem for Rails. Like the clever boy from the fairy tale who left pebbles to find his way home, this gem helps users navigate back through your application.
12
+ A lightweight, zero-dependency breadcrumbs gem for Ruby on Rails. Simple DSL, controller inheritance, and full view customization help your users find their way back, one pebble at a time.
13
+
14
+ ## Features
15
+
16
+ - 🪶 **Zero dependencies** — only Rails required
17
+ - 🎯 **Simple DSL** — declare breadcrumbs in one line
18
+ - 🔗 **Controller inheritance** — child controllers inherit parent breadcrumbs
19
+ - 🎨 **Flexible rendering** — use the built-in helper or full custom views
20
+ - ⚡ **Lazy evaluation** — lambdas for dynamic names and paths
21
+ - 🎛️ **Action filtering** — `only` and `except` options for fine control
22
+ - 📦 **Grouping** — apply filters to multiple breadcrumbs at once
12
23
 
13
24
  ## Installation
14
25
 
@@ -60,6 +71,8 @@ breadcrumb -> { @article.title }
60
71
 
61
72
  ### Runtime Breadcrumbs
62
73
 
74
+ You can also add breadcrumbs at runtime in actions or `before_action` callbacks:
75
+
63
76
  ```ruby
64
77
  def show
65
78
  @article = Article.find(params[:id])
@@ -68,6 +81,30 @@ def show
68
81
  end
69
82
  ```
70
83
 
84
+ #### Combining Declarative and Runtime
85
+
86
+ Use declarative breadcrumbs for general structure and runtime for action-specific additions:
87
+
88
+ ```ruby
89
+ class ArticlesController < ApplicationController
90
+ breadcrumb "Articles", :articles_path
91
+
92
+ def show
93
+ @article = Article.find(params[:id])
94
+ breadcrumb @article.title, article_path(@article)
95
+ breadcrumb @article.category.name, category_path(@article.category) if @article.category
96
+ end
97
+
98
+ def edit
99
+ @article = Article.find(params[:id])
100
+ breadcrumb @article.title, article_path(@article)
101
+ breadcrumb "Edit"
102
+ end
103
+ end
104
+ # show → Articles → My Article → Tech (if category exists)
105
+ # edit → Articles → My Article → Edit
106
+ ```
107
+
71
108
  ### View Rendering
72
109
 
73
110
  #### Simple (built-in helper)
@@ -115,6 +152,145 @@ class AdminController < ApplicationController
115
152
  end
116
153
  ```
117
154
 
155
+ #### Conditional Clearing
156
+
157
+ Clear inherited breadcrumbs only for specific actions:
158
+
159
+ ```ruby
160
+ class Admin::ArticlesController < AdminController
161
+ # Start fresh on :new and :create actions only
162
+ clear_breadcrumbs only: %i[new create]
163
+ breadcrumb "New Article", only: %i[new create]
164
+ end
165
+
166
+ class PublicController < ApplicationController
167
+ # Clear inherited breadcrumbs on all actions except :index
168
+ clear_breadcrumbs except: :index
169
+ breadcrumb "Public Section"
170
+ end
171
+ ```
172
+
173
+ ### Grouping Breadcrumbs
174
+
175
+ Use `breadcrumb_group` to apply the same `only`/`except` filters to multiple breadcrumbs:
176
+
177
+ ```ruby
178
+ class ArticlesController < ApplicationController
179
+ # These breadcrumbs only appear on :edit and :update
180
+ breadcrumb_group only: %i[edit update] do
181
+ breadcrumb "Articles", :articles_path
182
+ breadcrumb -> { @article.title }, -> { article_path(@article) }
183
+ breadcrumb "Edit"
184
+ end
185
+ end
186
+ ```
187
+
188
+ #### Nested Groups
189
+
190
+ Groups can be nested. Options are merged intelligently:
191
+
192
+ - `:only` uses **intersection** (more restrictive)
193
+ - `:except` uses **union** (cumulative exclusions)
194
+
195
+ ```ruby
196
+ class ArticlesController < ApplicationController
197
+ breadcrumb_group except: :index do
198
+ breadcrumb "Articles", :articles_path
199
+
200
+ breadcrumb_group only: %i[edit update] do
201
+ # Appears on :edit and :update, but NOT on :index
202
+ breadcrumb -> { @article.title }, -> { article_path(@article) }
203
+ end
204
+ end
205
+ end
206
+ ```
207
+
208
+ #### Overriding Group Options
209
+
210
+ Individual breadcrumbs can override group options:
211
+
212
+ ```ruby
213
+ breadcrumb_group only: %i[show edit update] do
214
+ breadcrumb "Details", :article_path # Appears on :show, :edit, :update
215
+ breadcrumb "Edit Form", only: :edit # Appears only on :edit (intersection)
216
+ end
217
+ ```
218
+
219
+ #### Combining Groups with Regular Breadcrumbs
220
+
221
+ ```ruby
222
+ class ArticlesController < ApplicationController
223
+ breadcrumb "Home", :root_path # Always
224
+
225
+ breadcrumb_group only: %i[edit update] do
226
+ breadcrumb "Edit Section", :edit_article_path # Only on :edit, :update
227
+ end
228
+
229
+ breadcrumb -> { @article.title }, except: :index # Except :index
230
+ end
231
+ ```
232
+
233
+ ### Complete Example
234
+
235
+ A typical CRUD controller setup:
236
+
237
+ ```ruby
238
+ class ArticlesController < ApplicationController
239
+ breadcrumb "Articles", :articles_path
240
+
241
+ # Show article title on :show, :edit, :update, :destroy
242
+ breadcrumb_group only: %i[show edit update destroy] do
243
+ breadcrumb -> { @article.title }, -> { article_path(@article) }
244
+ end
245
+
246
+ # Add "Edit" crumb on :edit and :update
247
+ breadcrumb "Edit", only: %i[edit update]
248
+
249
+ # Different breadcrumb for new articles
250
+ breadcrumb "New Article", only: %i[new create]
251
+
252
+ def show
253
+ @article = Article.find(params[:id])
254
+ end
255
+
256
+ # ...
257
+ end
258
+ ```
259
+
260
+ **Result:**
261
+
262
+ | Action | Breadcrumbs |
263
+ |----------|--------------------------------------|
264
+ | index | Articles |
265
+ | show | Articles / My Article |
266
+ | edit | Articles / My Article / Edit |
267
+ | new | Articles / New Article |
268
+
269
+ ## API Reference
270
+
271
+ ### Controller Class Methods
272
+
273
+ | Method | Description |
274
+ |--------|-------------|
275
+ | `breadcrumb(name, path = nil, **options)` | Declare a breadcrumb |
276
+ | `clear_breadcrumbs(**options)` | Clear inherited breadcrumbs |
277
+ | `breadcrumb_group(**options, &block)` | Group breadcrumbs with shared options |
278
+
279
+ ### Options
280
+
281
+ | Option | Description | Example |
282
+ |--------|-------------|---------|
283
+ | `:only` | Show only on these actions | `only: %i[edit update]` |
284
+ | `:except` | Show on all actions except these | `except: :index` |
285
+
286
+ ### Dynamic Values
287
+
288
+ | Type | Name | Path |
289
+ |------|------|------|
290
+ | String | `"Home"` | `"/path"` |
291
+ | Symbol | `:method_name` | `:path_helper` |
292
+ | Proc | `-> { @model.title }` | `-> { model_path(@model) }` |
293
+
118
294
  ## Requirements
119
295
 
120
296
  - Ruby >= 3.0
@@ -123,3 +299,13 @@ end
123
299
  ## License
124
300
 
125
301
  MIT
302
+
303
+ ---
304
+
305
+ ## About the Name
306
+
307
+ > *Le petit Pouçet les laissoit crier, sçachant bien par où il reviendroit à la maison ; car en marchant il avoit laissé tomber le long du chemin les petits cailloux blancs qu'il avoit dans ses poches.*
308
+ >
309
+ > — Charles Perrault, *Le Petit Poucet* (1697)
310
+
311
+ Named after the French fairy tale "Le Petit Poucet" (Hop-o'-My-Thumb), where a clever boy leaves a trail of pebbles to find his way home.
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PetitPoucet
4
+ class BreadcrumbGroupBuilder
5
+ def initialize(controller_class, options, parent_options = nil)
6
+ @controller_class = controller_class
7
+ @options = merge_action_options(parent_options, options)
8
+ end
9
+
10
+ def breadcrumb(name, path = nil, **options)
11
+ merged_options = merge_action_options(@options, options)
12
+ add_definition(Crumb.new(name, path, **merged_options))
13
+ end
14
+
15
+ def breadcrumb_group(**options, &block)
16
+ BreadcrumbGroupBuilder.new(@controller_class, options, @options).instance_eval(&block)
17
+ end
18
+
19
+ def clear_breadcrumbs(**options)
20
+ merged_options = options.empty? ? @options : merge_action_options(@options, options)
21
+ add_definition(Crumb.new(clear: true, **merged_options))
22
+ end
23
+
24
+ private
25
+
26
+ def add_definition(crumb)
27
+ @controller_class._breadcrumb_definitions = @controller_class._breadcrumb_definitions + [crumb]
28
+ end
29
+
30
+ def merge_action_options(base, override)
31
+ return override if base.nil? || base.empty?
32
+ return base if override.nil? || override.empty?
33
+
34
+ {}.tap do |result|
35
+ result[:only] = merge_only(base[:only], override[:only])
36
+ result[:except] = merge_except(base[:except], override[:except])
37
+ result.compact!
38
+ end
39
+ end
40
+
41
+ def merge_only(base_only, override_only)
42
+ return base_only unless override_only
43
+ return override_only unless base_only
44
+
45
+ # Intersection: more restrictive
46
+ base_arr = Array(base_only).map(&:to_s)
47
+ override_arr = Array(override_only).map(&:to_s)
48
+ (base_arr & override_arr).map(&:to_sym)
49
+ end
50
+
51
+ def merge_except(base_except, override_except)
52
+ return nil if base_except.nil? && override_except.nil?
53
+
54
+ # Union: cumulative exclusions
55
+ base_arr = Array(base_except).map(&:to_s)
56
+ override_arr = Array(override_except).map(&:to_s)
57
+ (base_arr | override_arr).map(&:to_sym)
58
+ end
59
+ end
60
+ end
@@ -17,12 +17,40 @@ module PetitPoucet
17
17
  # @param options [Hash] Options including :only and :except
18
18
  #
19
19
  def breadcrumb(name, path = nil, **options)
20
- self._breadcrumb_definitions = _breadcrumb_definitions + [Crumb.new(name, path, **options)]
20
+ _add_breadcrumb_definition(Crumb.new(name, path, **options))
21
21
  end
22
22
 
23
23
  # Clear all inherited breadcrumbs
24
- def clear_breadcrumbs
25
- self._breadcrumb_definitions = []
24
+ #
25
+ # @param options [Hash] Options including :only and :except to conditionally clear
26
+ #
27
+ def clear_breadcrumbs(**options)
28
+ if options.empty?
29
+ self._breadcrumb_definitions = []
30
+ else
31
+ _add_breadcrumb_definition(Crumb.new(clear: true, **options))
32
+ end
33
+ end
34
+
35
+ # Group multiple breadcrumbs with shared options
36
+ #
37
+ # @param options [Hash] Options including :only and :except applied to all breadcrumbs in block
38
+ # @yield Block containing breadcrumb declarations
39
+ #
40
+ # @example
41
+ # breadcrumb_group only: %i[index show edit] do
42
+ # breadcrumb 'Home', '/'
43
+ # breadcrumb 'Section', '/section'
44
+ # end
45
+ #
46
+ def breadcrumb_group(**options, &block)
47
+ BreadcrumbGroupBuilder.new(self, options).instance_eval(&block)
48
+ end
49
+
50
+ private
51
+
52
+ def _add_breadcrumb_definition(crumb)
53
+ self._breadcrumb_definitions = _breadcrumb_definitions + [crumb]
26
54
  end
27
55
  end
28
56
 
@@ -52,7 +80,12 @@ module PetitPoucet
52
80
  private
53
81
 
54
82
  def resolve_breadcrumbs
55
- crumbs = _breadcrumb_definitions.select { |c| c.applies_to_action?(action_name) }
83
+ crumbs = []
84
+ _breadcrumb_definitions.each do |crumb|
85
+ next unless crumb.applies_to_action?(action_name)
86
+
87
+ crumb.clear? ? crumbs.clear : crumbs << crumb
88
+ end
56
89
  crumbs += @runtime_breadcrumbs || []
57
90
  crumbs.map { |crumb| { name: crumb.resolve_name(self), path: crumb.resolve_path(self) } }
58
91
  end
@@ -4,8 +4,9 @@ module PetitPoucet
4
4
  class Crumb
5
5
  attr_reader :name, :path
6
6
 
7
- def initialize(name, path = nil, **options)
7
+ def initialize(name = nil, path = nil, clear: false, **options)
8
8
  @name = name
9
+ @clear = clear
9
10
 
10
11
  if path.is_a?(Hash)
11
12
  options = path
@@ -18,6 +19,10 @@ module PetitPoucet
18
19
  @except_actions = options[:except] && Array(options[:except]).map(&:to_s)
19
20
  end
20
21
 
22
+ def clear?
23
+ @clear
24
+ end
25
+
21
26
  def resolve_name(context)
22
27
  case name
23
28
  when Proc then context.instance_exec(&name)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PetitPoucet
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
data/lib/petit_poucet.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative 'petit_poucet/version'
4
4
  require_relative 'petit_poucet/crumb'
5
5
  require_relative 'petit_poucet/crumb_presenter'
6
+ require_relative 'petit_poucet/breadcrumb_group_builder'
6
7
  require_relative 'petit_poucet/controller_methods'
7
8
  require_relative 'petit_poucet/view_helpers'
8
9
  require_relative 'petit_poucet/railtie' if defined?(Rails::Railtie)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: petit_poucet
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sébastien Loyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-12-04 00:00:00.000000000 Z
11
+ date: 2025-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -38,7 +38,8 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '7.0'
41
- description: Minimal breadcrumbs gem with declarative DSL and action filtering
41
+ description: A zero-dependency breadcrumbs gem for Rails with simple DSL, controller
42
+ inheritance, and flexible view rendering.
42
43
  email:
43
44
  executables: []
44
45
  extensions: []
@@ -48,6 +49,7 @@ files:
48
49
  - LICENSE.txt
49
50
  - README.md
50
51
  - lib/petit_poucet.rb
52
+ - lib/petit_poucet/breadcrumb_group_builder.rb
51
53
  - lib/petit_poucet/controller_methods.rb
52
54
  - lib/petit_poucet/crumb.rb
53
55
  - lib/petit_poucet/crumb_presenter.rb
@@ -61,6 +63,8 @@ metadata:
61
63
  homepage_uri: https://github.com/Sbastien/petit_poucet
62
64
  source_code_uri: https://github.com/Sbastien/petit_poucet
63
65
  changelog_uri: https://github.com/Sbastien/petit_poucet/blob/main/CHANGELOG.md
66
+ documentation_uri: https://github.com/Sbastien/petit_poucet
67
+ bug_tracker_uri: https://github.com/Sbastien/petit_poucet/issues
64
68
  rubygems_mfa_required: 'true'
65
69
  post_install_message:
66
70
  rdoc_options: []
@@ -80,5 +84,5 @@ requirements: []
80
84
  rubygems_version: 3.5.22
81
85
  signing_key:
82
86
  specification_version: 4
83
- summary: Modern breadcrumbs DSL for Rails
87
+ summary: Lightweight breadcrumbs gem for Ruby on Rails
84
88
  test_files: []