Class: Ecoportal::API::GraphQL::Diff::Strategy

Inherits:
Object
  • Object
show all
Defined in:
lib/ecoportal/api/graphql/diff/strategy.rb

Overview

Composable configuration for a diff. There is NO single diff — different processes need different modes (see template_diff_pairing_domain.md §8). A Strategy is the value object that names the axes, consumed by the diff front-end (VersionDiff / CrossObjectDiff) and by Deploy.

Axes:

  • pairing — how counterpart objects are identified across the two snapshots: :id same object, retained Mongo ids (self-version) — NO equivalence needed; :genome pair by genomeSignature (cross-object; a strong-but-fallible signal); :type_label pair by __typename + label (cross-object; the TypedFieldsPairing precedent); :assisted the full multi-signal Pairing::Engine (+ Ledger), human-in-the-loop for ambiguous/unmatched — the general cross-object case. :id is the default and keeps the existing self-version behaviour untouched.

  • scope — which change kinds are emitted: :structural stages / sections / fields (add/remove/move/relabel/retype) + options + gauge; :config_only field configuration (byType), select options, gauge stops — NOT structure; :data_migration field<->field data pairing only (structure-agnostic): pairings + type/label changes on paired fields, no add/remove of scaffolding.

  • move_sensitive — when false, section/stage MOVES are suppressed (a section-agnostic diff that only cares about a field's data pairing, not where it now lives).

  • intent — the consuming process (documentation only; does not alter emission, but lets a caller/Deploy branch on it): :changelog what changed, for a ticket / review checklist; :deploy UAT->PROD replay delta (drives placeholderId threading ON in Deploy); :sync_readiness can this register/subset be synced to the active template.

Cross-object pairings (:genome/:type_label/:assisted) require a pairing map to translate the two id-spaces before change emission — see CrossObjectDiff. :id needs no map.

Constant Summary collapse

PAIRINGS =
%i[id genome type_label assisted].freeze
SCOPES =
%i[structural config_only data_migration].freeze
INTENTS =
%i[changelog deploy sync_readiness].freeze
STRUCTURAL_KINDS =

Change KINDS considered structure vs configuration — used to filter by scope.

%i[stage section field].freeze
CONFIG_KINDS =
%i[field_config option gauge_stop].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pairing: :id, scope: :structural, move_sensitive: true, intent: :changelog) ⇒ Strategy

Returns a new instance of Strategy.



48
49
50
51
52
53
# File 'lib/ecoportal/api/graphql/diff/strategy.rb', line 48

def initialize(pairing: :id, scope: :structural, move_sensitive: true, intent: :changelog)
  @pairing        = validate!(:pairing, pairing, PAIRINGS)
  @scope          = validate!(:scope, scope, SCOPES)
  @move_sensitive = move_sensitive ? true : false
  @intent         = validate!(:intent, intent, INTENTS)
end

Instance Attribute Details

#intentObject (readonly)

Returns the value of attribute intent.



46
47
48
# File 'lib/ecoportal/api/graphql/diff/strategy.rb', line 46

def intent
  @intent
end

#pairingObject (readonly)

Returns the value of attribute pairing.



46
47
48
# File 'lib/ecoportal/api/graphql/diff/strategy.rb', line 46

def pairing
  @pairing
end

#scopeObject (readonly)

Returns the value of attribute scope.



46
47
48
# File 'lib/ecoportal/api/graphql/diff/strategy.rb', line 46

def scope
  @scope
end

Class Method Details

.defaultObject

The default self-version strategy (id-paired, structural, move-aware, changelog). This is exactly the behaviour VersionDiff had before Phase 4 — used when no strategy is passed.



57
58
59
# File 'lib/ecoportal/api/graphql/diff/strategy.rb', line 57

def self.default
  new
end

Instance Method Details

#cross_object?Boolean

True when pairing does NOT rely on retained ids — the two snapshots live in different id-spaces and need a pairing map (CrossObjectDiff) before change emission.

Returns:

  • (Boolean)


67
68
69
# File 'lib/ecoportal/api/graphql/diff/strategy.rb', line 67

def cross_object?
  @pairing != :id
end

#emit_kind?(kind) ⇒ Boolean

Should a change of this KIND be emitted under this scope?

Returns:

  • (Boolean)


72
73
74
75
76
77
78
# File 'lib/ecoportal/api/graphql/diff/strategy.rb', line 72

def emit_kind?(kind)
  case @scope
  when :structural     then true
  when :config_only    then CONFIG_KINDS.include?(kind)
  when :data_migration then kind == :field # field-level data pairing only
  end
end

#emit_move?Boolean

Should a :moved change be emitted? Suppressed when move-insensitive, or under a scope that ignores scaffolding placement (config_only / data_migration never care about moves).

Returns:

  • (Boolean)


82
83
84
# File 'lib/ecoportal/api/graphql/diff/strategy.rb', line 82

def emit_move?
  move_sensitive? && @scope == :structural
end

#filter(changes) ⇒ Object

Filter a raw change-set to what this strategy emits (scope + move-sensitivity). The diff front-ends compute the full change-set once and let the strategy decide what survives.



88
89
90
91
92
93
94
95
# File 'lib/ecoportal/api/graphql/diff/strategy.rb', line 88

def filter(changes)
  Array(changes).select do |change|
    next false unless emit_kind?(change.kind)
    next false if change.op == :moved && !emit_move?

    true
  end
end

#move_sensitive?Boolean

Returns:

  • (Boolean)


61
62
63
# File 'lib/ecoportal/api/graphql/diff/strategy.rb', line 61

def move_sensitive?
  @move_sensitive
end

#to_hObject



97
98
99
# File 'lib/ecoportal/api/graphql/diff/strategy.rb', line 97

def to_h
  { pairing: @pairing, scope: @scope, move_sensitive: move_sensitive?, intent: @intent }
end