Class: Pubid::Identifier

Inherits:
Lutaml::Model::Serializable
  • Object
show all
Defined in:
lib/pubid/identifier.rb

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attrs = {}, options = {}) ⇒ Identifier

Returns a new instance of Identifier.



206
207
208
209
210
# File 'lib/pubid/identifier.rb', line 206

def initialize(attrs = {}, options = {})
  attrs = attrs.dup
  attrs[:_type] ||= self.class.polymorphic_name
  super
end

Class Attribute Details

.format_registryObject



6
7
8
# File 'lib/pubid/identifier.rb', line 6

def format_registry
  @format_registry || superclass&.format_registry
end

Class Method Details

.from_hash(data, options = {}) ⇒ Object

Polymorphic deserialization shared by every flavor base. lutaml's key_value polymorphic_map reads _type only to VALIDATE; it does not re-instantiate the concrete subclass. So a base-class from_hash would return a bare base object and drop subtype-specific attributes (e.g. a supplement's base_identifier). Route by _type to the concrete class named in polymorphic_type_map, then let its inherited from_hash (this method again, where klass == self) fall through to lutaml's real work.

This generalizes the per-flavor *_TYPE_MAP dispatch that ISO, JIS, IEC, CCSDS and IHO each hand-rolled — one implementation, inherited by every flavor base.



23
24
25
26
27
28
29
# File 'lib/pubid/identifier.rb', line 23

def from_hash(data, options = {})
  type = data && (data["_type"] || data[:_type])
  klass = polymorphic_type_map[type]
  return klass.from_hash(data, options) if klass && klass != self

  super
end

.polymorphic_nameObject



212
213
214
215
216
217
218
219
220
221
222
# File 'lib/pubid/identifier.rb', line 212

def self.polymorphic_name
  return nil unless name

  parts = name.split("::")
  flavor = parts[1]&.downcase
  type_kebab = parts.last
    .gsub(/([A-Z]+)([A-Z][a-z])/, '\1-\2')
    .gsub(/([a-z\d])([A-Z])/, '\1-\2')
    .downcase
  "pubid:#{flavor}:#{type_kebab}"
end

.polymorphic_type_mapObject

Map of polymorphic_name ("pubid:iso:corrigendum") => concrete class for the flavor this base belongs to. Built once by scanning the flavor's Identifiers namespace for Pubid::Identifier descendants and unioning the flavor's identifier_types registry (which may register classes living outside that namespace, e.g. ISO's BundledIdentifier). Memoized per class.



37
38
39
40
41
42
43
# File 'lib/pubid/identifier.rb', line 37

def polymorphic_type_map
  @polymorphic_type_map ||=
    identifier_registry_classes.each_with_object({}) do |klass, map|
      poly = klass.polymorphic_name
      map[poly] ||= klass if poly
    end
end

Instance Method Details

#base_identifierObject

base_identifier is declared by supplement subclasses with proper type



119
120
121
# File 'lib/pubid/identifier.rb', line 119

def base_identifier
  nil
end

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


357
358
359
360
361
# File 'lib/pubid/identifier.rb', line 357

def eql?(other)
  return false unless other.is_a?(self.class)

  hash == other.hash && self == other
end

#exclude(*args) ⇒ Object



314
315
316
317
318
319
320
321
322
323
# File 'lib/pubid/identifier.rb', line 314

def exclude(*args)
  excluded_args = args.dup
  # Map :year to :date since identifiers store years inside date
  excluded_args << :date if excluded_args.delete(:year)

  attrs = self.class.attributes.each_with_object({}) do |(name, _), h|
    h[name] = excluded_args.include?(name) ? nil : public_send(name)
  end
  self.class.new(attrs)
end

#hashObject



353
354
355
# File 'lib/pubid/identifier.rb', line 353

def hash
  @hash ||= compute_hash
end

#mr_numberObject



276
277
278
# File 'lib/pubid/identifier.rb', line 276

def mr_number
  number&.to_s
end

#mr_number_with_partObject



268
269
270
271
272
273
274
# File 'lib/pubid/identifier.rb', line 268

def mr_number_with_part
  num = mr_number
  return nil unless num

  prt = mr_part
  prt ? "#{num}-#{prt}" : num
end

#mr_partObject



280
281
282
# File 'lib/pubid/identifier.rb', line 280

def mr_part
  part&.to_s
end

#mr_publisherObject

MR string template methods — flavors override as needed



255
256
257
# File 'lib/pubid/identifier.rb', line 255

def mr_publisher
  publisher&.to_s
end

#mr_typeObject



259
260
261
262
263
264
265
266
# File 'lib/pubid/identifier.rb', line 259

def mr_type
  return nil unless typed_stage

  code = typed_stage.type_code
  return nil if code.nil? || code.empty? || code == "is"

  code
end

#mr_yearObject



284
285
286
# File 'lib/pubid/identifier.rb', line 284

def mr_year
  date&.year&.to_s
end

#new_edition_of?(other) ⇒ Boolean

Returns:

  • (Boolean)


325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/pubid/identifier.rb', line 325

def new_edition_of?(other)
  unless publisher == other.publisher
    raise ArgumentError,
          "Cannot compare edition: different publisher"
  end
  unless number == other.number
    raise ArgumentError,
          "Cannot compare edition: different number"
  end
  unless part == other.part
    raise ArgumentError,
          "Cannot compare edition: different part"
  end

  unless date && other.date
    raise ArgumentError,
          "Cannot compare identifier without date/year"
  end

  return date.year > other.date.year if date.year != other.date.year

  if edition && other.edition
    return edition.number > other.edition.number
  end

  false
end

#render(format: :human, **opts) ⇒ Object

Unified render — delegates to format registry



231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/pubid/identifier.rb', line 231

def render(format: :human, **opts)
  registry = self.class.format_registry
  unless registry
    raise ArgumentError, "No format registry configured on #{self.class}"
  end

  renderer = registry.renderer_for(format)
  unless renderer
    raise ArgumentError, "No renderer registered for format: #{format}"
  end

  context = build_rendering_context(renderer, format:, **opts)
  renderer.new(self).render(context:, **opts.slice(:with_edition))
end

#resolve_urn_generatorObject



307
308
309
310
311
312
# File 'lib/pubid/identifier.rb', line 307

def resolve_urn_generator
  flavor = self.class.name.split("::")[1]
  Object.const_get("Pubid::#{flavor}::UrnGenerator")
rescue NameError
  Pubid::UrnGenerator::Base
end

#rootObject



224
225
226
227
228
# File 'lib/pubid/identifier.rb', line 224

def root
  return base_identifier.root if base_identifier

  self
end

#to_hash(*args) ⇒ Object

Canonicalize the serialized hash so it never carries a defaulted attribute still at its default (or empty) value. This makes to_hash a pure function of the identifier's values, independent of how the object was built.

Motivation: lutaml materializes each attribute's default as an explicit assignment during deserialization (flipping using_default? to false), so a naive from_hash(x).to_hash re-emits defaults that parse(x).to_hash omits — breaking the exact-equality round-trip relaton-index relies on (from_hash(raw).to_hash == raw). pubid never consults lutaml's unset tracking and defines no render_default: true, so a defaulted attribute at its default/empty value carries no meaning and does not belong in the canonical hash. Dropping it here (rather than repairing from_hash) fixes the round-trip through every construction path — parse, from_hash, manual.



141
142
143
144
145
# File 'lib/pubid/identifier.rb', line 141

def to_hash(*args)
  hash = super
  canonicalize_hash(self, hash) if hash.is_a?(::Hash)
  hash
end

#to_mr_stringObject



250
251
252
# File 'lib/pubid/identifier.rb', line 250

def to_mr_string
  render(format: :mr_string)
end

#to_s(**opts) ⇒ Object



246
247
248
# File 'lib/pubid/identifier.rb', line 246

def to_s(**opts)
  render(format: :human, **opts)
end

#to_supplement_s(**opts) ⇒ Object

Supplement rendering hook — flavors override for supplement-specific rendering



298
299
300
# File 'lib/pubid/identifier.rb', line 298

def to_supplement_s(**opts)
  to_s(**opts)
end

#to_urnObject

Default URN generation — resolves flavor's UrnGenerator class



303
304
305
# File 'lib/pubid/identifier.rb', line 303

def to_urn
  resolve_urn_generator.new(self).generate
end

#urn_supplement_typeObject



293
294
295
# File 'lib/pubid/identifier.rb', line 293

def urn_supplement_type
  nil
end

#urn_type_codeObject

URN template methods — flavors override as needed



289
290
291
# File 'lib/pubid/identifier.rb', line 289

def urn_type_code
  nil
end

#yearString?

Returns publication year from the date component.

Returns:

  • (String, nil)

    publication year from the date component



124
125
126
# File 'lib/pubid/identifier.rb', line 124

def year
  date&.year&.to_s
end