Module: TypedEAV::SchemaPortability

Defined in:
lib/typed_eav/schema_portability.rb

Overview

Export and import field + section definitions for an exact partition tuple. Value rows are intentionally out of scope.

Class Method Summary collapse

Class Method Details

.export_schema(entity_type:, scope: nil, parent_scope: nil) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/typed_eav/schema_portability.rb', line 8

def export_schema(entity_type:, scope: nil, parent_scope: nil)
  fields = TypedEAV::Field::Base
           .where(entity_type: entity_type, scope: scope, parent_scope: parent_scope)
           .includes(:field_options)
           .order(:sort_order)
           .map { |field| export_field_entry(field) }

  sections = TypedEAV::Section
             .where(entity_type: entity_type, scope: scope, parent_scope: parent_scope)
             .order(:sort_order)
             .map { |section| export_section_entry(section) }

  {
    "schema_version" => 1,
    "entity_type" => entity_type,
    "scope" => scope,
    "parent_scope" => parent_scope,
    "fields" => fields,
    "sections" => sections,
  }
end

.export_snapshot_schema(entity_type:, scope: nil, parent_scope: nil) ⇒ Hash

Lean, restore-oriented projection of the field schema for a partition tuple. Sibling to export_schema — same partition filter, narrower per-field surface, no sections, no partition-identity keys.

The envelope is:

{
  "snapshot_schema_version" => 1,
  "fields" => [ <snapshot_field_entry>, ... ]   # ordered by sort_order
}

The ‘snapshot_schema_version` integer will be bumped explicitly when the inner per-field shape evolves in a non-additive way — it is NOT frozen forever. Consumers should branch on the version to handle cross-version snapshots.

Each per-field entry is a strict subset of the full export_field_entry shape:

{
  "name" => field.name,
  "field_type_name" => field.field_type_name,
  "required" => field.required,
  "sort_order" => field.sort_order,
  "options" => field.options,
  "options_data" => [...]   # ONLY present when field.optionable?
}

Omitted vs the full schema export: ‘entity_type`, `scope`, `parent_scope`, `type` (the AR STI class name), `field_dependent`, and `default_value_meta`. Non-optionable fields omit `options_data` entirely (absent, not nil, not an empty array).

The ‘field_type_name` value is the documented field-type dispatch identifier — robust to namespace relocations of the field class because it strips the namespace via `demodulize` before `underscore`-ing. It is NOT robust to renames of the leaf class itself: `Field::Select` → `“select”`, but renaming the class to `Field::Status` would change the dispatch identifier to `“status”`.

Parameters:

  • entity_type (String)

    host AR model class name (e.g. “Contact”)

  • scope (String, nil) (defaults to: nil)

    first partition axis

  • parent_scope (String, nil) (defaults to: nil)

    second partition axis

Returns:

  • (Hash)

    versioned snapshot envelope



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/typed_eav/schema_portability.rb', line 74

def export_snapshot_schema(entity_type:, scope: nil, parent_scope: nil)
  fields = TypedEAV::Field::Base
           .where(entity_type: entity_type, scope: scope, parent_scope: parent_scope)
           .includes(:field_options)
           .order(:sort_order)
           .map { |field| export_snapshot_field_entry(field) }

  {
    "snapshot_schema_version" => 1,
    "fields" => fields,
  }
end

.import_schema(hash, on_conflict: :error) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/typed_eav/schema_portability.rb', line 87

def import_schema(hash, on_conflict: :error)
  validate_schema_version!(hash)
  validate_conflict_policy!(on_conflict)

  result = { "created" => 0, "updated" => 0, "skipped" => 0, "unchanged" => 0, "errors" => [] }

  TypedEAV::Field::Base.transaction do
    Array(hash["fields"]).each do |entry|
      import_field_entry(entry, on_conflict, result)
    end

    Array(hash["sections"]).each do |entry|
      import_section_entry(entry, on_conflict, result)
    end
  end

  result
end