Class: Pubid::Identifier
- Inherits:
-
Lutaml::Model::Serializable
- Object
- Lutaml::Model::Serializable
- Pubid::Identifier
- Defined in:
- lib/pubid/identifier.rb
Direct Known Subclasses
Amca::Identifier, Ansi::Identifier, Api::Identifier, Ashrae::Identifier, Asme::Identifier, Astm::Identifier, Bsi::Identifier, BundledIdentifier, Ccsds::Identifier, Ccsds::SingleIdentifier, CenCenelec::Identifier, Cie::Identifier, Csa::Identifier, Etsi::Identifier, Pubid::Iala::Identifier, Pubid::Idf::Identifier, Pubid::Iec::Identifier, Pubid::Ieee::Identifier, Pubid::Iho::Identifier, Pubid::Iso::Identifier, Pubid::Itu::Identifier, Jcgm::Identifier, Jis::Identifier, Nist::Identifier, Oiml::Identifier, Plateau::Identifier, Plateau::SupplementIdentifier, Sae::Identifier
Class Attribute Summary collapse
Class Method Summary collapse
-
.from_hash(data, options = {}) ⇒ Object
Polymorphic deserialization shared by every flavor base.
- .polymorphic_name ⇒ Object
-
.polymorphic_type_map ⇒ Object
Map of polymorphic_name ("pubid:iso:corrigendum") => concrete class for the flavor this base belongs to.
Instance Method Summary collapse
-
#base_document ⇒ Object
The underlying standard, with amendment / corrigendum / Expert-commentary / Flex-version wrappers peeled recursively.
-
#base_identifier ⇒ Object
base_identifier is declared by supplement subclasses with proper type.
-
#drop_supplements ⇒ Object
The standard this identifier supplements, dropping its own supplement layer (one level).
- #eql?(other) ⇒ Boolean
-
#exclude(*args) ⇒ Object
Excluded attributes are nilled; every other value is passed through #exclude_from_nested so the exclusion also propagates into nested identifiers — wrapper types (adopted standards, consolidated amendments, expert-commentary wrappers) delegate their date to an inner identifier rather than storing it in their own attribute.
- #hash ⇒ Object
-
#initialize(attrs = {}, options = {}) ⇒ Identifier
constructor
A new instance of Identifier.
-
#matches?(other, ignore: []) ⇒ Boolean
Fuzz-level equality: two identifiers match when they are equal after excluding the given aspects.
- #mr_number ⇒ Object
- #mr_number_with_part ⇒ Object
- #mr_part ⇒ Object
-
#mr_publisher ⇒ Object
MR string template methods — flavors override as needed.
- #mr_type ⇒ Object
- #mr_year ⇒ Object
- #new_edition_of?(other) ⇒ Boolean
-
#render(format: :human, **opts) ⇒ Object
Unified render — delegates to format registry.
- #resolve_urn_generator ⇒ Object
- #root ⇒ Object
-
#to_hash(*args) ⇒ Object
Canonicalize the serialized hash so it never carries a defaulted attribute still at its default (or empty) value.
- #to_mr_string ⇒ Object
- #to_s(**opts) ⇒ Object
-
#to_supplement_s(**opts) ⇒ Object
Supplement rendering hook — flavors override for supplement-specific rendering.
-
#to_urn ⇒ Object
Default URN generation — resolves flavor's UrnGenerator class.
- #urn_supplement_type ⇒ Object
-
#urn_type_code ⇒ Object
URN template methods — flavors override as needed.
-
#year ⇒ String?
Publication year from the date component.
Constructor Details
#initialize(attrs = {}, options = {}) ⇒ Identifier
Returns a new instance of Identifier.
213 214 215 216 217 |
# File 'lib/pubid/identifier.rb', line 213 def initialize(attrs = {}, = {}) attrs = attrs.dup attrs[:_type] ||= self.class.polymorphic_name super end |
Class Attribute Details
.format_registry ⇒ Object
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, = {}) type = data && (data["_type"] || data[:_type]) klass = polymorphic_type_map[type] return klass.from_hash(data, ) if klass && klass != self super end |
.polymorphic_name ⇒ Object
219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/pubid/identifier.rb', line 219 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_map ⇒ Object
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_document ⇒ Object
The underlying standard, with amendment / corrigendum / Expert-commentary / Flex-version wrappers peeled recursively. Wrapper subclasses override this; a plain identifier is its own base document.
126 127 128 |
# File 'lib/pubid/identifier.rb', line 126 def base_document self end |
#base_identifier ⇒ Object
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 |
#drop_supplements ⇒ Object
The standard this identifier supplements, dropping its own supplement layer (one level). Overridden by ConsolidatedIdentifier / Amendment / Corrigendum; a non-supplement identifier supplements nothing, so it is returned unchanged.
349 350 351 |
# File 'lib/pubid/identifier.rb', line 349 def drop_supplements self end |
#eql?(other) ⇒ Boolean
395 396 397 398 399 |
# File 'lib/pubid/identifier.rb', line 395 def eql?(other) return false unless other.is_a?(self.class) hash == other.hash && self == other end |
#exclude(*args) ⇒ Object
Excluded attributes are nilled; every other value is passed through #exclude_from_nested so the exclusion also propagates into nested identifiers — wrapper types (adopted standards, consolidated amendments, expert-commentary wrappers) delegate their date to an inner identifier rather than storing it in their own attribute.
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/pubid/identifier.rb', line 326 def exclude(*args) # :amendment / :supplement are structural, not attributes — they reduce # a supplemented identifier to the standard it wraps (#drop_supplements). supplement_keys = args & %i[amendment supplement] unless supplement_keys.empty? return drop_supplements.exclude(*(args - supplement_keys)) end 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| value = excluded_args.include?(name) ? nil : public_send(name) h[name] = exclude_from_nested(value, args) end self.class.new(attrs) end |
#hash ⇒ Object
391 392 393 |
# File 'lib/pubid/identifier.rb', line 391 def hash @hash ||= compute_hash end |
#matches?(other, ignore: []) ⇒ Boolean
Fuzz-level equality: two identifiers match when they are equal after
excluding the given aspects. ignore accepts the same symbols as #exclude
(e.g. :date, :edition, :amendment). This is the primitive relaton uses to
match a reference against catalogue hits at varying strictness.
357 358 359 360 361 |
# File 'lib/pubid/identifier.rb', line 357 def matches?(other, ignore: []) return false unless other.is_a?(::Pubid::Identifier) exclude(*ignore) == other.exclude(*ignore) end |
#mr_number ⇒ Object
283 284 285 |
# File 'lib/pubid/identifier.rb', line 283 def mr_number number&.to_s end |
#mr_number_with_part ⇒ Object
275 276 277 278 279 280 281 |
# File 'lib/pubid/identifier.rb', line 275 def mr_number_with_part num = mr_number return nil unless num prt = mr_part prt ? "#{num}-#{prt}" : num end |
#mr_part ⇒ Object
287 288 289 |
# File 'lib/pubid/identifier.rb', line 287 def mr_part part&.to_s end |
#mr_publisher ⇒ Object
MR string template methods — flavors override as needed
262 263 264 |
# File 'lib/pubid/identifier.rb', line 262 def mr_publisher publisher&.to_s end |
#mr_type ⇒ Object
266 267 268 269 270 271 272 273 |
# File 'lib/pubid/identifier.rb', line 266 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_year ⇒ Object
291 292 293 |
# File 'lib/pubid/identifier.rb', line 291 def mr_year date&.year&.to_s end |
#new_edition_of?(other) ⇒ Boolean
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 |
# File 'lib/pubid/identifier.rb', line 363 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
238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/pubid/identifier.rb', line 238 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_generator ⇒ Object
314 315 316 317 318 319 |
# File 'lib/pubid/identifier.rb', line 314 def resolve_urn_generator flavor = self.class.name.split("::")[1] Object.const_get("Pubid::#{flavor}::UrnGenerator") rescue NameError Pubid::UrnGenerator::Base end |
#root ⇒ Object
231 232 233 234 235 |
# File 'lib/pubid/identifier.rb', line 231 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.
148 149 150 151 152 |
# File 'lib/pubid/identifier.rb', line 148 def to_hash(*args) hash = super canonicalize_hash(self, hash) if hash.is_a?(::Hash) hash end |
#to_mr_string ⇒ Object
257 258 259 |
# File 'lib/pubid/identifier.rb', line 257 def to_mr_string render(format: :mr_string) end |
#to_s(**opts) ⇒ Object
253 254 255 |
# File 'lib/pubid/identifier.rb', line 253 def to_s(**opts) render(format: :human, **opts) end |
#to_supplement_s(**opts) ⇒ Object
Supplement rendering hook — flavors override for supplement-specific rendering
305 306 307 |
# File 'lib/pubid/identifier.rb', line 305 def to_supplement_s(**opts) to_s(**opts) end |
#to_urn ⇒ Object
Default URN generation — resolves flavor's UrnGenerator class
310 311 312 |
# File 'lib/pubid/identifier.rb', line 310 def to_urn resolve_urn_generator.new(self).generate end |
#urn_supplement_type ⇒ Object
300 301 302 |
# File 'lib/pubid/identifier.rb', line 300 def urn_supplement_type nil end |
#urn_type_code ⇒ Object
URN template methods — flavors override as needed
296 297 298 |
# File 'lib/pubid/identifier.rb', line 296 def urn_type_code nil end |
#year ⇒ String?
Returns publication year from the date component.
131 132 133 |
# File 'lib/pubid/identifier.rb', line 131 def year date&.year&.to_s end |