Class: Opencdd::Entity

Inherits:
Lutaml::Model::Serializable
  • Object
show all
Includes:
ParseHelpers
Defined in:
lib/opencdd/entity.rb,
lib/opencdd/entity/yaml.rb,
lib/opencdd/entity/field_reader.rb,
lib/opencdd/entity/field_registry.rb,
lib/opencdd/entity/version_history.rb

Direct Known Subclasses

Klass, Property, Relation, Unit, ValueList, ValueTerm, ViewControl

Defined Under Namespace

Modules: FieldReader, FieldRegistry Classes: Dates, VersionHistory, Yaml

Constant Summary collapse

META_CLASS_CODE =
nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ParseHelpers

brace_wrapped?, paren_wrapped?, parse_irdi_list, parse_pair_list, parse_string_list, parse_synonym_tuples, unwrap_delimiters, unwrap_parens

Constructor Details

#initialize(irdi: nil, properties: nil, schema: nil, meta_class_irdi: nil) ⇒ Entity

Returns a new instance of Entity.



97
98
99
100
101
102
103
104
# File 'lib/opencdd/entity.rb', line 97

def initialize(irdi: nil, properties: nil, schema: nil, meta_class_irdi: nil)
  super({})
  @irdi = irdi
  @properties = properties || {}
  @schema = schema
  @meta_class_irdi = meta_class_irdi
  @version_history = Opencdd::Entity::VersionHistory.new
end

Instance Attribute Details

#irdiObject (readonly)

Returns the value of attribute irdi.



67
68
69
# File 'lib/opencdd/entity.rb', line 67

def irdi
  @irdi
end

#meta_class_irdiObject (readonly)

Returns the value of attribute meta_class_irdi.



67
68
69
# File 'lib/opencdd/entity.rb', line 67

def meta_class_irdi
  @meta_class_irdi
end

#propertiesObject (readonly)

Returns the value of attribute properties.



67
68
69
# File 'lib/opencdd/entity.rb', line 67

def properties
  @properties
end

#schemaObject (readonly)

Returns the value of attribute schema.



67
68
69
# File 'lib/opencdd/entity.rb', line 67

def schema
  @schema
end

#source_locationObject (readonly)

Returns the value of attribute source_location.



67
68
69
# File 'lib/opencdd/entity.rb', line 67

def source_location
  @source_location
end

Class Method Details

.default_code_property_id(meta_class_irdi) ⇒ Object



93
94
95
# File 'lib/opencdd/entity.rb', line 93

def self.default_code_property_id(meta_class_irdi)
  Opencdd::MetaClasses.code_property_id_for(meta_class_irdi&.code)
end

.field(name, property_id = nil, value_kind = nil, multilingual: nil, synthetic: false, reader: nil, as: nil, &block) ⇒ Object

Declare a typed field on this entity class. Defaults for value_kind and multilingual are resolved from Opencdd::PropertyIds::REGISTRY when the property_id is known there — the REGISTRY is the SSOT for wire-format metadata, and re-declaring it here would violate DRY.

Pass an explicit value_kind to override (used when REGISTRY's kind doesn't match how the field is consumed — e.g. MDC_P023 is :identifier_ref in REGISTRY but Unit#structure uses it as a raw string).

synthetic: true for computed fields with no MDC_P### — requires a block (preferred) that returns the value. The block is evaluated via instance_exec on the entity, so it has access to private helpers without send dispatch.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/opencdd/entity.rb', line 35

def field(name, property_id = nil, value_kind = nil,
          multilingual: nil, synthetic: false, reader: nil,
          as: nil, &block)
  resolved_kind, resolved_ml = resolve_from_registry(property_id)
  Opencdd::Entity::FieldRegistry.register(
    entity_class: self,
    name: name,
    property_id: property_id,
    value_kind: value_kind || resolved_kind || :string,
    multilingual: multilingual.nil? ? resolved_ml : multilingual,
    synthetic: synthetic,
    reader: reader,
    block: block,
    json_key: as,
  )
  define_method(name) do |lang = nil|
    Opencdd::Entity::FieldReader.read(self, name, lang: lang)
  end
end

.from_row(row, schema:, meta_class_irdi:, code_property_id: nil) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/opencdd/entity.rb', line 69

def self.from_row(row, schema:, meta_class_irdi:, code_property_id: nil)
  code_property_id ||= default_code_property_id(meta_class_irdi)
  raw_code = code_property_id && row[code_property_id]
  # Fall back to other known code property IDs when the entity-
  # specific one is absent (happens with name-only header sheets
  # where "Code" synthesizes to MDC_P001_5 regardless of type).
  if raw_code.nil? || raw_code.to_s.strip.empty?
    Opencdd::MetaClasses::CODE_PROPERTY_IDS.each_value do |alt_id|
      val = row[alt_id]
      next if val.nil? || val.to_s.strip.empty?
      raw_code = val
      break
    end
  end
  irdi = raw_code && Opencdd::IRDI.parse(raw_code)

  props = row.each_with_object({}) do |(k, v), h|
    next if k == "__row_index__"
    h[k] = v if v
  end

  new(irdi: irdi, properties: props, schema: schema, meta_class_irdi: meta_class_irdi)
end

.from_yaml(yaml_str) ⇒ Object



285
286
287
# File 'lib/opencdd/entity.rb', line 285

def self.from_yaml(yaml_str)
  Opencdd::Entity::Yaml.from_yaml(yaml_str).to_entity
end

Instance Method Details

#[](key) ⇒ Object



177
178
179
# File 'lib/opencdd/entity.rb', line 177

def [](key)
  @properties[key.to_s]
end

#attach_source_location(loc) ⇒ Object

Attach source location (file:line) where this entity was declared. Set by Opencdd::Cddal::Builder from import/instantiation context. Nil for entities constructed from Parcel readers (which don't have a single source file).



261
262
263
264
# File 'lib/opencdd/entity.rb', line 261

def attach_source_location(loc)
  @source_location = loc
  self
end

#attach_version_history(version_history) ⇒ Object

Attach per-version provenance captured in _entity.json. Called by Opencdd::Parcel::ShardedDirReader after entity creation — the constructor doesn't take it because FlatDirReader creates entities from .xls rows without version context.



252
253
254
255
# File 'lib/opencdd/entity.rb', line 252

def attach_version_history(version_history)
  @version_history = version_history
  self
end

#codeObject Also known as: short



110
111
112
# File 'lib/opencdd/entity.rb', line 110

def code
  @irdi&.code
end

#each_propertyObject



189
190
191
192
# File 'lib/opencdd/entity.rb', line 189

def each_property
  return enum_for(:each_property) unless block_given?
  @properties.each { |k, v| yield k, v }
end

#eql?(other) ⇒ Boolean Also known as: ==

Returns:

  • (Boolean)


194
195
196
197
198
# File 'lib/opencdd/entity.rb', line 194

def eql?(other)
  other.is_a?(Opencdd::Entity) &&
    irdi == other.irdi &&
    type == other.type
end

#hashObject



200
201
202
# File 'lib/opencdd/entity.rb', line 200

def hash
  [irdi, type].hash
end

#inspectObject



206
207
208
# File 'lib/opencdd/entity.rb', line 206

def inspect
  "#<#{self.class.name} #{irdi}>"
end

#key?(key) ⇒ Boolean

Returns:

  • (Boolean)


181
182
183
# File 'lib/opencdd/entity.rb', line 181

def key?(key)
  @properties.key?(key.to_s)
end

#keysObject



185
186
187
# File 'lib/opencdd/entity.rb', line 185

def keys
  @properties.keys
end

#read_field(name, lang: nil) ⇒ Object

Read a declared field by name. Accepts an optional lang: for multilingual fields. Returns the typed value (parsed IRDI, Array, etc.) per the field's declared value_kind.



233
234
235
# File 'lib/opencdd/entity.rb', line 233

def read_field(name, lang: nil)
  Opencdd::Entity::FieldReader.read(self, name, lang: lang)
end

#replace_code_value!(code_property_id, new_code) ⇒ Object



215
216
217
218
219
# File 'lib/opencdd/entity.rb', line 215

def replace_code_value!(code_property_id, new_code)
  return self unless code_property_id
  @properties[code_property_id.to_s] = new_code.to_s
  self
end

#replace_irdi!(new_irdi) ⇒ Object



210
211
212
213
# File 'lib/opencdd/entity.rb', line 210

def replace_irdi!(new_irdi)
  @irdi = Opencdd::IRDI === new_irdi ? new_irdi : Opencdd::IRDI.parse(new_irdi.to_s)
  self
end

#to_yaml(*args) ⇒ Object

── YAML persistence via Entity::Yaml ────────────────────── Entity extends Lutaml::Model::Serializable but its own attribute set is empty — the typed YAML model lives in Entity::Yaml (the deepened adapter). This avoids name conflicts between the field DSL getters (which read from @properties and return coerced values like IRDI objects, source-language Strings, parsed Arrays) and lutaml-model serialization attrs (which must return flat types: String, Hash, Array).

lutaml-model serialization calls public_send(attr_name) during to_format, so any getter with the same name as a YAML attr is invoked. Keeping the YAML model in Entity::Yaml lets both worlds coexist: field DSL for domain access, Entity::Yaml for serialization.



281
282
283
# File 'lib/opencdd/entity.rb', line 281

def to_yaml(*args)
  Opencdd::Entity::Yaml.from_entity(self).to_yaml(*args)
end

#typeObject



106
107
108
# File 'lib/opencdd/entity.rb', line 106

def type
  Opencdd::Parcel::META_CLASS_TYPES[meta_class_irdi&.code]
end

#write_property!(id, value) ⇒ Object

Write a value to a property ID, routing through canonical-id alias resolution so writes are consistent with reads. Use this in preference to entity.properties[id] = value from non-infrastructure callers.



241
242
243
244
245
246
# File 'lib/opencdd/entity.rb', line 241

def write_property!(id, value)
  canonical = Opencdd::PropertyIds.canonical_id(id.to_s) || id.to_s
  base = canonical.to_s.split(".").first
  @properties[base] = value.to_s
  self
end