Module: Wayfinding

Defined in:
lib/wayfinding.rb,
lib/wayfinding/kind.rb,
lib/wayfinding/rspec.rb,
lib/wayfinding/errors.rb,
lib/wayfinding/version.rb,
lib/wayfinding/destination.rb,
lib/wayfinding/destination_list.rb,
lib/wayfinding/url_resolver_context.rb

Overview

Cross-application URL resolution.

Any component can link to any page without depending on the component that owns the route. Registered destinations are data, so this module's public surface never grows as destinations are added.

Wayfinding.register(
name: :home,
engine: -> { Projects::Engine },
helper: :home_path
)

Wayfinding.path_for(:home, home, current_tab: "Projects")

Defined Under Namespace

Modules: RSpecHelpers Classes: Destination, DestinationList, Kind, UrlResolverContext

Constant Summary collapse

Error =
Class.new(StandardError)
UnregisteredDestination =

Raised when looking up a destination that was never registered.

Class.new(Error)
InvalidDestination =

Raised at registration when a destination is missing fields its kind requires, or when its resolution strategy is ambiguous or incomplete.

Class.new(Error)
UnknownKind =

Raised when a destination declares a kind that was never defined.

Class.new(Error)
VERSION =
"0.0.1"

Class Method Summary collapse

Class Method Details

.define_kind(name, requires: []) ⇒ Object

Declare a named field contract. See Wayfinding::Kind.



26
27
28
29
# File 'lib/wayfinding.rb', line 26

def define_kind(name, requires: [])
  kind = Kind.new(name: name, requires: requires)
  kinds[kind.name] = kind
end

.fetch(name) ⇒ Object



52
53
54
55
56
57
# File 'lib/wayfinding.rb', line 52

def fetch(name)
  destinations.fetch(name.to_sym) do
    raise UnregisteredDestination,
          "No destination registered for #{name.inspect}. Registered: #{registered_names.join(', ')}"
  end
end

.kindsObject



31
# File 'lib/wayfinding.rb', line 31

def kinds = @kinds ||= {}

.of_kind(name) ⇒ Object



47
48
49
50
# File 'lib/wayfinding.rb', line 47

def of_kind(name)
  name = name.to_sym
  DestinationList.new(destinations.each_value.select { |destination| destination.kind == name })
end

.path_for(name, **params) ⇒ Object



43
# File 'lib/wayfinding.rb', line 43

def path_for(name, *, **params) = fetch(name).path(*, **params)

.preserve!Object

Snapshot the registry, yield, then restore it. Used by the RSpec helper so a destination registered inside an example does not leak.



75
76
77
78
79
80
81
82
# File 'lib/wayfinding.rb', line 75

def preserve!
  destinations_snapshot = destinations.dup
  kinds_snapshot = kinds.dup
  yield
ensure
  @destinations = destinations_snapshot
  @kinds = kinds_snapshot
end

.register(name:, engine: nil, kind: nil, helper: nil, **attributes, &resolver) ⇒ Object



33
34
35
36
37
38
39
40
41
# File 'lib/wayfinding.rb', line 33

def register(name:, engine: nil, kind: nil, helper: nil, **attributes, &resolver)
  validate_resolution!(name, engine, helper, resolver)
  validate_all_or_none!(name, attributes, %i[action subject])
  validate_kind!(name, kind, **attributes)

  destinations[name.to_sym] = Destination.new(
    name: name, engine: engine, kind: kind, helper: helper, resolver: resolver, **attributes
  )
end

.registered?(name) ⇒ Boolean

Returns:

  • (Boolean)


59
# File 'lib/wayfinding.rb', line 59

def registered?(name) = destinations.key?(name.to_sym)

.registered_namesObject



61
# File 'lib/wayfinding.rb', line 61

def registered_names = destinations.keys.sort

.reset!Object



84
85
86
87
# File 'lib/wayfinding.rb', line 84

def reset!
  @destinations = {}
  @kinds = {}
end

.url_for(name, **params) ⇒ Object



45
# File 'lib/wayfinding.rb', line 45

def url_for(name, *, **params) = fetch(name).url(*, **params)

.verify!(expected) ⇒ Object

Assert every expected destination is registered. Call at the end of an initializer so a dropped registration fails the boot rather than rendering a broken link.



66
67
68
69
70
71
# File 'lib/wayfinding.rb', line 66

def verify!(expected)
  missing = Array(expected).map(&:to_sym) - registered_names
  return true if missing.empty?

  raise UnregisteredDestination, "Missing destination registrations: #{missing.join(', ')}"
end