Module: RivetCms::Navigation

Defined in:
lib/rivet_cms/navigation.rb

Overview

Admin sidebar registry: the seam the Pro engine adds nav items through. Core registers its own items here too, so every item, core or extension, goes through the same can? filter and a denied user never sees a link that would 403.

RivetCms::Navigation.register :audit_log,
label: "Audit Log",
section: "Manage",
icon: :content,
requires: [:read, :content],
path: -> { audit_log_path },
position: 80

path is either a string or a lambda instance_exec'd in the controller, so engine route helpers work directly. requires is [action, resource] for can?, or nil for always visible. icon names one of the built-in sidebar icons; unknown names render a neutral dot. Items sort by position across sections; a section appears where its lowest-position item falls. Re-registering a key replaces the item, so registration is reload-safe.

Defined Under Namespace

Classes: Item

Constant Summary collapse

MUTEX =
Mutex.new

Class Method Summary collapse

Class Method Details

.itemsObject



51
52
53
# File 'lib/rivet_cms/navigation.rb', line 51

def items
  mutex.synchronize { registry.values.sort_by.with_index { |item, index| [ item.position, index ] } }
end

.register(key, label:, section:, path:, icon: nil, requires: nil, position: 100, exact: false, badge: nil, visible: nil) ⇒ Object

Raises:

  • (ArgumentError)


27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/rivet_cms/navigation.rb', line 27

def register(key, label:, section:, path:, icon: nil, requires: nil, position: 100, exact: false, badge: nil, visible: nil)
  raise ArgumentError, "label required" if label.to_s.strip.empty?
  raise ArgumentError, "path must be a String or callable" unless path.is_a?(String) || path.respond_to?(:call)
  # Class equality, not is_a?: ActiveSupport::Duration answers is_a?(Integer)
  raise ArgumentError, "position must be an Integer" unless position.class == Integer
  raise ArgumentError, "badge must be callable" unless badge.nil? || badge.respond_to?(:call)
  raise ArgumentError, "visible must be callable" unless visible.nil? || visible.respond_to?(:call)
  unless requires.nil? || (requires.is_a?(Array) && requires.size == 2 && requires.all? { |part| part.respond_to?(:to_sym) })
    raise ArgumentError, "requires must be nil or [action, resource]"
  end

  item = Item.new(
    key: key.to_sym, label: label.to_s, section: section.to_s, icon: icon&.to_sym,
    requires: requires&.map(&:to_sym), path: path, position: position, exact: exact == true,
    badge: badge, visible: visible
  )
  mutex.synchronize { registry[item.key] = item }
  item
end

.restore(snapshot) ⇒ Object



61
62
63
64
65
# File 'lib/rivet_cms/navigation.rb', line 61

def restore(snapshot)
  return if snapshot.nil? # never mistake a failed snapshot for "no items"

  mutex.synchronize { @registry = snapshot.dup }
end

.snapshotObject

Test-only: capture registrations so an example can add items and hand the registry back exactly as it found it.



57
58
59
# File 'lib/rivet_cms/navigation.rb', line 57

def snapshot
  mutex.synchronize { registry.dup }
end

.unregister(key) ⇒ Object



47
48
49
# File 'lib/rivet_cms/navigation.rb', line 47

def unregister(key)
  mutex.synchronize { registry.delete(key.to_sym) }
end