Module: LcpRuby::Presenter::Enrichment
- Defined in:
- lib/lcp_ruby/presenter/enrichment.rb
Overview
Per-entry decoration of show fields and index columns. Each enricher is a pure function: (entry, location:, model_def:, loader:) -> entry. Must be a no-op when the enricher’s keys are absent from entry.
Constant Summary collapse
- ENTRY_ENRICHERS =
%i[link].freeze
Class Method Summary collapse
-
.auto_detect_dot_path(field, model_def:, loader:) ⇒ Object
Walk a dot-path and compute (chain, target_model).
-
.enrich(entry, location:, model_def:, loader:) ⇒ Hash
Decorate a single field/column hash with enricher-produced keys.
-
.enrich_link_options(entry, location:, model_def:, loader:) ⇒ Object
Silent on invalid input — the boot-time validator rejects it first.
- .link_opt_in?(entry, location) ⇒ Boolean
-
.resolve_chain(entry, location:, model_def:, loader:) ⇒ Object
Returns [chain_array, target_model_name] or [nil, nil] when enrichment should be skipped.
-
.resolve_presenter_slug(target_model_name, entry: nil) ⇒ Object
B19 — three-tier resolution for multi-presenter models: 1.
Class Method Details
.auto_detect_dot_path(field, model_def:, loader:) ⇒ Object
Walk a dot-path and compute (chain, target_model). Chain = every segment except a scalar terminal; if the terminal is itself a belongs_to, the whole path is the chain.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/lcp_ruby/presenter/enrichment.rb', line 85 def auto_detect_dot_path(field, model_def:, loader:) segments = field.split(".") current_model = model_def segments[0..-2].each do |seg| assoc = current_model.find_belongs_to(seg) return [ nil, nil ] unless assoc&.target_model next_def = loader.model_definitions[assoc.target_model] return [ nil, nil ] unless next_def current_model = next_def end last = segments.last terminal = current_model.find_belongs_to(last) if terminal return [ nil, nil ] if terminal.polymorphic || terminal.target_model.nil? [ segments, terminal.target_model ] else [ segments[0..-2], current_model.name ] end end |
.enrich(entry, location:, model_def:, loader:) ⇒ Hash
Decorate a single field/column hash with enricher-produced keys.
18 19 20 21 22 23 24 |
# File 'lib/lcp_ruby/presenter/enrichment.rb', line 18 def enrich(entry, location:, model_def:, loader:) return entry unless entry.is_a?(Hash) ENTRY_ENRICHERS.reduce(entry) do |e, name| send("enrich_#{name}_options", e, location: location, model_def: model_def, loader: loader) end end |
.enrich_link_options(entry, location:, model_def:, loader:) ⇒ Object
Silent on invalid input — the boot-time validator rejects it first.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/lcp_ruby/presenter/enrichment.rb', line 27 def (entry, location:, model_def:, loader:) return entry unless link_opt_in?(entry, location) chain, target_model = resolve_chain(entry, location: location, model_def: model_def, loader: loader) return entry if chain.nil? slug = resolve_presenter_slug(target_model, entry: entry) entry.merge( "link_chain" => chain, "link_target_model" => target_model, "link_target_presenter_slug" => slug ) end |
.link_opt_in?(entry, location) ⇒ Boolean
42 43 44 45 46 47 48 |
# File 'lib/lcp_ruby/presenter/enrichment.rb', line 42 def link_opt_in?(entry, location) return true if entry["link"] == true return true if entry["link_through"].present? return true if location == :column && entry["link_to"].to_s == "show" false end |
.resolve_chain(entry, location:, model_def:, loader:) ⇒ Object
Returns [chain_array, target_model_name] or [nil, nil] when enrichment should be skipped.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/lcp_ruby/presenter/enrichment.rb', line 51 def resolve_chain(entry, location:, model_def:, loader:) # Explicit link_through takes precedence over auto-detection. lt = entry["link_through"] if lt.present? assoc = model_def.find_belongs_to(lt.to_s) return [ nil, nil ] unless assoc&.target_model return [ [ lt.to_s ], assoc.target_model ] end field = entry["field"].to_s # Column: link_to: :show and link:true on scalar → empty chain (current row). if location == :column if entry["link_to"].to_s == "show" || (entry["link"] == true && !field.include?(".") && model_def.find_belongs_to(field).nil?) return [ [], model_def.name ] end end # From here on, only link: true remains (explicit link_through was handled above). return [ nil, nil ] unless entry["link"] == true if field.include?(".") auto_detect_dot_path(field, model_def: model_def, loader: loader) elsif (assoc = model_def.find_belongs_to(field)) [ [ field ], assoc.target_model ] else [ nil, nil ] # show-field link:true on scalar — validator V3a errors end end |
.resolve_presenter_slug(target_model_name, entry: nil) ⇒ Object
B19 — three-tier resolution for multi-presenter models:
1. Per-link override (`link_presenter:` on entry) wins when present.
2. Single presenter — unambiguous, backward-compatible (most models).
3. Multi-presenter — the one marked `default: true` is canonical.
Returns nil when multi-presenter has no resolution; the validator rejects that configuration at boot, so we don’t have to fall back to alphabetic order at render time (which was the original B19 bug).
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/lcp_ruby/presenter/enrichment.rb', line 119 def resolve_presenter_slug(target_model_name, entry: nil) return nil if target_model_name.blank? presenters = Resolver.presenters_for_model(target_model_name) if entry && (override = entry["link_presenter"]).present? return presenters.find { |p| p.slug == override.to_s }&.slug end return presenters.first&.slug if presenters.length <= 1 presenters.find(&:default?)&.slug rescue MetadataError nil end |