Module: LcpRuby::Metadata::FieldPath

Defined in:
lib/lcp_ruby/metadata/field_path.rb

Overview

Walks a dot-path field reference through metadata to find the terminal field definition and its owning model name.

Used by view templates and helpers that need to humanize/route an enum value coming from a cross-model field path like ‘“contact.tier”` or `“order.customer.status”`. The terminal model_name is the i18n namespace under which `enum_label_for` should look up labels (`lcp_ruby.enums.<model>.<field>.*`).

Returns ‘[FieldDefinition, model_name]` or `[nil, nil]` when:

- model_def is nil or path is blank
- any intermediate segment is missing / non-LCP
- an intermediate segment is a collection (`has_many`) and
  `through_collections:` is false (the default)
- any segment cannot be loaded from the metadata loader

‘through_collections:` — when false (default), an intermediate `has_many` aborts the walk (`[nil, nil]`). This is what `LabelMethodBuilder` wants: a `to_label` walks a singular chain with `public_send`, so a collection mid-path can’t yield a scalar value. Display surfaces (index tables, cards, tree) pass ‘true`: a has_many terminal enum (e.g. `contacts.tier`) resolves to an Array of values that the field def still humanizes element-wise (issue #18). The terminal field def is identical either way — the flag only governs whether a collection mid-path is allowed.

Callers treat ‘[nil, nil]` as “no metadata available — pass through”.

Class Method Summary collapse

Class Method Details

.terminal(model_def, path, through_collections: false) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/lcp_ruby/metadata/field_path.rb', line 33

def terminal(model_def, path, through_collections: false)
  return [ nil, nil ] if model_def.nil?

  path_str = path.to_s
  return [ nil, nil ] if path_str.empty?

  parts = path_str.split(".")
  current_def = model_def

  parts[0..-2].each do |segment|
    assoc = current_def.associations.find { |a| a.name == segment }
    return [ nil, nil ] unless assoc&.lcp_model?
    return [ nil, nil ] unless through_collections || assoc.singular?

    current_def = LcpRuby.loader.model_definition(assoc.target_model)
    return [ nil, nil ] if current_def.nil?
  end

  [ current_def.field(parts.last), current_def.name ]
rescue MetadataError
  [ nil, nil ]
end