lutaml-model 0.8.98 → 0.8.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9f7cb26a9f87fbfe0feb461b25dd3e8b50e32d64bbbd32b23f848e96355965b
4
- data.tar.gz: 1961e58a64424ca1d69181f3e873d107e8f1d8762f1d1155c1de953c883ceaa1
3
+ metadata.gz: 2bd54a574eb02eec487d980543835abb2bec61c12104d6eea6b898a516fb301f
4
+ data.tar.gz: ffc5d3ed5b0c6df7140839064baec52624a034b2d1c5cfdc764dc4ee78c199ce
5
5
  SHA512:
6
- metadata.gz: 8624d39f5b4d556f6c72e7f76fa6ab262490a1583d5776756373dd4ea220bb1ba89e3f57f7774efa8f5f8c16f631f312ac149ed93ed651c15223068265bea629
7
- data.tar.gz: 1249cac19b6f80880f5eb0c29cb0b2df17ff5813021573f6c2b63c923f6b2d63d2f579b9d14ebf18b8dae774df208787cef61cbfc507848fd99afffbc28b1404
6
+ metadata.gz: 191c5ee654cc6863a6f8d865448f0fbb29c31f7d602e3ab2a356148fdca7117b254aa497bb1ebdd80da1c80f495cf46c5f1029897cf87e7423a7409747e4e97c
7
+ data.tar.gz: 4b55a05f437df7e0c85c7545d2042990572c70a4d64b42d9ae3ee44950ca6875cd640c2089cd9620951264180556e2e3776e7b32028b1b1d9962cbdaad739ecb
@@ -0,0 +1,85 @@
1
+ ---
2
+ title: Integrating lutaml-model in a Gem
3
+ nav_order: 21
4
+ ---
5
+
6
+ = Integrating lutaml-model in a gem
7
+
8
+ For gem authors, the integration contract is simple: **depend on
9
+ `lutaml-model` and nothing else format-specific.** Every XML, YAML,
10
+ JSON, and TOML engine is resolved at runtime, and the standard
11
+ adapters (Nokogiri through moxml, Psych, the `json` gem, toml-rb
12
+ compatible engines) cover a working default everywhere.
13
+
14
+ == Declaring the dependency
15
+
16
+ [source,ruby]
17
+ ----
18
+ spec.add_dependency "lutaml-model", "~> 0.8"
19
+ ----
20
+
21
+ Do not add Nokogiri, Ox, Oga, leptris, yeptris, or teptris to your
22
+ gemspec. All engines are optional at runtime:
23
+
24
+ * when an engine gem is in the end user's bundle, the matching
25
+ adapters prefer it (see the
26
+ link:../native-engines[native engines guide]);
27
+ * when it is not, adapters fall back to the standard engine for
28
+ that format — parsing and generation semantics are identical,
29
+ error classes are re-raised as their standard-library equivalents.
30
+
31
+ There is no code path to write, no require order to respect, and no
32
+ capability probe to perform in your gem.
33
+
34
+ == Testing across adapters
35
+
36
+ Your test suite should exercise the adapters your users may run.
37
+ Lutaml::Model's own suite parameterizes the XML adapter through an
38
+ environment variable; the same shape works downstream:
39
+
40
+ [source,ruby]
41
+ ----
42
+ ADAPTER = ENV.fetch("XML_ADAPTER", "nokogiri").to_sym
43
+
44
+ RSpec.configure do |config|
45
+ config.before(:suite) do
46
+ Lutaml::Model::Config.xml_adapter_type = ADAPTER
47
+ end
48
+ end
49
+ ----
50
+
51
+ Run the matrix in CI — one leg per adapter the standard runners can
52
+ install:
53
+
54
+ [source,sh]
55
+ ----
56
+ XML_ADAPTER=nokogiri bundle exec rspec
57
+ XML_ADAPTER=ox bundle exec rspec
58
+ XML_ADAPTER=leptris bundle exec rspec # requires the leptris gem in that leg's bundle
59
+ ----
60
+
61
+ Engine gems belong in the CI *Gemfile* (a `group :test` entry or a
62
+ separate CI gemfile), never in the gemspec. Start with the primary
63
+ adapter (Nokogiri) and enable further legs incrementally as they
64
+ pass — the same "adapter-first" strategy this library's own suite
65
+ uses.
66
+
67
+ == Semantics your users can rely on
68
+
69
+ * Adapter choice never changes results: the plan fast path and the
70
+ interpretive pipeline produce identical output; on engines below
71
+ a capability floor the affected models parse interpretively
72
+ rather than degrade (see the version-floor note in the
73
+ link:../native-engines[native engines guide]).
74
+ * Parse failures raise the standard error classes wrapped as
75
+ `Lutaml::Model::InvalidFormatError` on every engine — code that
76
+ rescues those classes works regardless of the bundle.
77
+
78
+ == Rules of thumb
79
+
80
+ * Gemspec: `lutaml-model` only. CI gemfiles: the engines you test.
81
+ * Expose format options you want to forward (pretty printing,
82
+ encoding) — never the adapter itself.
83
+ * If you ship models for multiple schema versions, see
84
+ link:../type-registers-contexts[type registers and contexts] for
85
+ per-version trees and substitution.
@@ -28,8 +28,13 @@ Task-oriented guides for accomplishing specific goals with Lutaml::Model.
28
28
  * link:../advanced-mapping[Advanced Attribute Mapping] - Same-name dispatch on attribute values (`when_attribute`), grouped declarations, custom methods, delegation
29
29
  * link:../grammar-backed-formats[Grammar-Backed String Formats] - Register a grammar engine as a serialization format (identifier strings, DSLs)
30
30
 
31
- == Performance
31
+ == Model architecture and integration
32
+
33
+ * link:../type-registers-contexts[Type Registers and Contexts] - Multi-version class trees, derived contexts, and model substitution
34
+ * link:../gem-integration[Integrating lutaml-model in a Gem] - Dependency contract, adapter deferral, cross-adapter testing
32
35
 
36
+ == Performance
37
+ /
33
38
  * link:../native-engines[Native engines (leptris / yeptris / teptris)] - Drop-in XML/YAML/JSON/TOML engine upgrades via bundle opt-in
34
39
 
35
40
  == Linked Data serialization
@@ -901,3 +901,28 @@ The generated schema will include validation constraints:
901
901
  </define>
902
902
  </grammar>
903
903
  ----
904
+
905
+ == Documentation metadata (desc)
906
+
907
+ Models and attributes carry documentation strings through `desc` —
908
+ set on the model class or on any `attribute` declaration, read back
909
+ by documentation and schema tooling. Serialization is never affected
910
+ by it.
911
+
912
+ [source,ruby]
913
+ ----
914
+ class Title < Lutaml::Model::Serializable
915
+ desc "This is a title."
916
+
917
+ attribute :type, :string, desc: "This attribute is used for..."
918
+ end
919
+
920
+ Title.desc # => "This is a title."
921
+ Title.attributes[:type].desc # => "This attribute is used for..."
922
+ ----
923
+
924
+ Subclasses inherit the nearest ancestor's class description; an
925
+ explicit `desc` overrides it. A consumer override-free `desc` value
926
+ does not change any generated schema today — wiring `desc` into the
927
+ generated `description` fields is deliberate product scope, not a
928
+ free rider on this option.
@@ -0,0 +1,126 @@
1
+ ---
2
+ title: Type Registers and Contexts
3
+ nav_order: 20
4
+ ---
5
+
6
+ = Type registers and contexts
7
+
8
+ Lutaml::Model resolves every attribute type — Symbol or class —
9
+ through a per-application *register*: a named table of types scoped
10
+ so that different class trees can share mapping definitions without
11
+ colliding. Registers are the mechanism behind multi-version model
12
+ suites and model substitution.
13
+
14
+ == Registers
15
+
16
+ A register maps type ids to classes. Classes declare the register
17
+ they belong to:
18
+
19
+ [source,ruby]
20
+ ----
21
+ module MyModels
22
+ class Mi < Lutaml::Model::Serializable
23
+ def self.lutaml_default_register
24
+ :my_models
25
+ end
26
+ end
27
+ end
28
+ ----
29
+
30
+ Symbol attribute types resolve in the declaring class's register,
31
+ so the same symbol can mean different classes per tree — the
32
+ multi-version pattern below builds on this.
33
+
34
+ == Contexts: derived registers with substitutions
35
+
36
+ A *context* is a derived type-resolution scope: it carries a
37
+ registry, fallback registers, and — the part that powers model
38
+ substitution — a list of type substitutions. Contexts are created
39
+ through the global registry:
40
+
41
+ [source,ruby]
42
+ ----
43
+ Lutaml::Model::GlobalContext.create_context(
44
+ id: :custom_models,
45
+ fallback_to: [:my_models],
46
+ substitutions: [
47
+ { from_type: MyModels::Mover, to_type: Overset },
48
+ ],
49
+ )
50
+
51
+ instance = MyModels::Math.from_xml(xml, context: :custom_models)
52
+ instance.mover_value.first # => #<Overset>
53
+ ----
54
+
55
+ Every attribute whose type resolves to `MyModels::Mover` — at any
56
+ depth of the tree — hydrates `Overset` in that context. Substitution
57
+ is type-based, so "swap this class everywhere it is referenced" and
58
+ "swap it only inside this one path" are the same operation scoped by
59
+ context: create two contexts with different substitution lists.
60
+
61
+ Contexts are cheap and disposable:
62
+
63
+ [source,ruby]
64
+ ----
65
+ Lutaml::Model::GlobalContext.unregister_context(:custom_models)
66
+ ----
67
+
68
+ === The custom-models convenience
69
+
70
+ A whole-table convenience reads exactly like a class-swap hash and
71
+ derives the context for you:
72
+
73
+ [source,ruby]
74
+ ----
75
+ models = { MyModels::Mover => Overset, MyModels::Mi => BoldMi }
76
+ Lutaml::Model::GlobalContext.create_context(
77
+ id: :custom, fallback_to: [:my_models],
78
+ substitutions: models.map { |from, to| { from_type: from, to_type: to } },
79
+ )
80
+ MyModels::Math.from_xml(xml, context: :custom)
81
+ ----
82
+
83
+ Substitute classes should inherit from the class they replace (or
84
+ provide its full attribute surface) so mappings and serialization
85
+ keep working.
86
+
87
+ === Substituting a symbol entry instead of a class
88
+
89
+ Registers accept direct class registrations under a type id — useful
90
+ when a tree declares `attribute :row, :row` and you want the symbol
91
+ to resolve to your own class within a derived register:
92
+
93
+ [source,ruby]
94
+ ----
95
+ derived = Lutaml::Model::Register.new(:derived, fallback: [:my_models])
96
+ derived.register_model(NewRow, id: :row)
97
+ MyModels::Doc.from_xml(xml, register: derived)
98
+ ----
99
+
100
+ (`register:` is the pre-context spelling; prefer `context:` with
101
+ substitutions in new code.)
102
+
103
+ == Multi-version class trees
104
+
105
+ The flagship pattern combines registers with per-version classes
106
+ and shared mapping modules:
107
+
108
+ * a `Base::Mrow` module holds the mapping DSL once;
109
+ * each version declares `class Mrow` mixing the module in and
110
+ pointing `lutaml_default_register` at its version's register;
111
+ * attributes use symbol types, so `attribute :mrow, :mrow` means
112
+ the right class per tree;
113
+ * a substitution context swaps individual classes for one call
114
+ without re-opening or redefining anything.
115
+
116
+ Nothing re-opens generated classes, and each version's tree stays
117
+ independent — no hidden delegation.
118
+
119
+ == Rules of thumb
120
+
121
+ * Prefer contexts over global configuration: substitutions scoped
122
+ to a context cannot leak into other resolutions.
123
+ * Substitute classes should inherit the original; substitution then
124
+ preserves every mapping declared on the base.
125
+ * Clean up exploratory contexts with `unregister_context` in test
126
+ teardowns — contexts are global state.
@@ -46,6 +46,7 @@ module Lutaml
46
46
  signed
47
47
  min_length
48
48
  max_length
49
+ desc
49
50
  ].freeze
50
51
 
51
52
  # Per-type-class memo of the custom from_xml/from_json probe used by #cast.
@@ -641,6 +642,12 @@ instance_object = nil)
641
642
  options[:pattern]
642
643
  end
643
644
 
645
+ # Documentation metadata (#356): the `:desc` attribute option,
646
+ # consumed by documentation and schema tooling.
647
+ def desc
648
+ options[:desc]
649
+ end
650
+
644
651
  # Performance: Frozen empty array to reduce allocations
645
652
  EMPTY_VALUES_ARRAY = [].freeze
646
653
 
@@ -505,6 +505,22 @@ module Lutaml
505
505
  end
506
506
  end
507
507
 
508
+ # Class-level documentation metadata (#356): `desc "text"` on
509
+ # the model class; a zero-argument call reads it back.
510
+ # Subclasses inherit the nearest ancestor's description — an
511
+ # explicit `desc` overrides. Consumed by documentation and
512
+ # schema tooling; the serialization paths never read it.
513
+ def desc(text = nil)
514
+ return @lutaml_desc = text if text
515
+
516
+ return @lutaml_desc if defined?(@lutaml_desc)
517
+
518
+ if superclass.is_a?(::Class) &&
519
+ superclass < ::Lutaml::Model::Serializable
520
+ superclass.desc
521
+ end
522
+ end
523
+
508
524
  # Define an attribute for the model
509
525
  #
510
526
  # @param name [Symbol] The attribute name
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.98"
5
+ VERSION = "0.8.100"
6
6
  end
7
7
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../spec_helper"
4
+
5
+ RSpec.describe "Documentation metadata (desc)" do
6
+ let(:widget_class) do
7
+ Class.new(Lutaml::Model::Serializable) do
8
+ desc "A widget."
9
+
10
+ attribute :name, :string, desc: "The widget name."
11
+ attribute :size, :integer
12
+ end
13
+ end
14
+
15
+ it "stores and reads the class-level description" do
16
+ expect(widget_class.desc).to eq("A widget.")
17
+ end
18
+
19
+ it "returns nil when no description is set" do
20
+ plain = Class.new(Lutaml::Model::Serializable)
21
+ expect(plain.desc).to be_nil
22
+ end
23
+
24
+ it "inherits the description on subclasses" do
25
+ child = Class.new(widget_class)
26
+ expect(child.desc).to eq("A widget.")
27
+ end
28
+
29
+ it "lets a subclass override the description" do
30
+ child = Class.new(widget_class) do
31
+ desc "A special widget."
32
+ end
33
+ expect(child.desc).to eq("A special widget.")
34
+ end
35
+
36
+ it "does not leak an override back to the superclass" do
37
+ Class.new(widget_class) { desc "Child only." }
38
+ expect(widget_class.desc).to eq("A widget.")
39
+ end
40
+
41
+ it "stores the attribute-level description" do
42
+ expect(widget_class.attributes[:name].desc).to eq("The widget name.")
43
+ expect(widget_class.attributes[:size].desc).to be_nil
44
+ end
45
+
46
+ it "keeps serialization untouched" do
47
+ widget = widget_class.new(name: "w", size: 3)
48
+ expect(widget.to_hash).to eq("name" => "w", "size" => 3)
49
+ end
50
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.98
4
+ version: 0.8.100
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -207,6 +207,7 @@ files:
207
207
  - docs/_guides/creating-xsd.adoc
208
208
  - docs/_guides/custom-methods.adoc
209
209
  - docs/_guides/document-validation.adoc
210
+ - docs/_guides/gem-integration.adoc
210
211
  - docs/_guides/grammar-backed-formats.adoc
211
212
  - docs/_guides/index.adoc
212
213
  - docs/_guides/jsonld-serialization.adoc
@@ -220,6 +221,7 @@ files:
220
221
  - docs/_guides/schema-generation.adoc
221
222
  - docs/_guides/schema-import.adoc
222
223
  - docs/_guides/turtle-serialization.adoc
224
+ - docs/_guides/type-registers-contexts.adoc
223
225
  - docs/_guides/value-transformations.adoc
224
226
  - docs/_guides/xml-mapping.adoc
225
227
  - docs/_guides/xml-mappings-guide.adoc
@@ -1740,6 +1742,7 @@ files:
1740
1742
  - spec/lutaml/model/deferred_type_no_materialize_spec.rb
1741
1743
  - spec/lutaml/model/delegation_spec.rb
1742
1744
  - spec/lutaml/model/derived_attribute_serialization_spec.rb
1745
+ - spec/lutaml/model/desc_spec.rb
1743
1746
  - spec/lutaml/model/dynamic_attribute_spec.rb
1744
1747
  - spec/lutaml/model/encoding_spec.rb
1745
1748
  - spec/lutaml/model/enum_spec.rb