lutaml-model 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -5
  3. data/.rubocop_todo.yml +20 -101
  4. data/Gemfile +3 -18
  5. data/README.adoc +109 -6
  6. data/lib/lutaml/model/attribute.rb +15 -2
  7. data/lib/lutaml/model/config.rb +0 -1
  8. data/lib/lutaml/model/json_adapter/json_document.rb +20 -0
  9. data/lib/lutaml/model/json_adapter/json_object.rb +28 -0
  10. data/lib/lutaml/model/json_adapter/{multi_json.rb → multi_json_adapter.rb} +2 -3
  11. data/lib/lutaml/model/json_adapter/{standard.rb → standard_json_adapter.rb} +2 -3
  12. data/lib/lutaml/model/json_adapter.rb +1 -31
  13. data/lib/lutaml/model/key_value_mapping.rb +0 -1
  14. data/lib/lutaml/model/key_value_mapping_rule.rb +0 -1
  15. data/lib/lutaml/model/mapping_hash.rb +0 -2
  16. data/lib/lutaml/model/mapping_rule.rb +0 -1
  17. data/lib/lutaml/model/schema/json_schema.rb +0 -1
  18. data/lib/lutaml/model/schema/relaxng_schema.rb +0 -1
  19. data/lib/lutaml/model/schema/xsd_schema.rb +0 -1
  20. data/lib/lutaml/model/schema/yaml_schema.rb +0 -1
  21. data/lib/lutaml/model/schema.rb +0 -1
  22. data/lib/lutaml/model/serializable.rb +0 -1
  23. data/lib/lutaml/model/serialize.rb +22 -4
  24. data/lib/lutaml/model/toml_adapter/toml_document.rb +20 -0
  25. data/lib/lutaml/model/toml_adapter/toml_object.rb +28 -0
  26. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +2 -3
  27. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +2 -3
  28. data/lib/lutaml/model/toml_adapter.rb +0 -31
  29. data/lib/lutaml/model/type/date_time.rb +20 -0
  30. data/lib/lutaml/model/type/json.rb +34 -0
  31. data/lib/lutaml/model/type/time_without_date.rb +4 -3
  32. data/lib/lutaml/model/type.rb +61 -124
  33. data/lib/lutaml/model/version.rb +1 -1
  34. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +5 -6
  35. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +4 -5
  36. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +8 -9
  37. data/lib/lutaml/model/xml_adapter/xml_attribute.rb +27 -0
  38. data/lib/lutaml/model/xml_adapter/xml_document.rb +184 -0
  39. data/lib/lutaml/model/xml_adapter/xml_element.rb +94 -0
  40. data/lib/lutaml/model/xml_adapter/xml_namespace.rb +49 -0
  41. data/lib/lutaml/model/xml_adapter.rb +0 -285
  42. data/lib/lutaml/model/xml_mapping.rb +1 -1
  43. data/lib/lutaml/model/xml_mapping_rule.rb +3 -4
  44. data/lib/lutaml/model/yaml_adapter/standard_yaml_adapter.rb +34 -0
  45. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +20 -0
  46. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +28 -0
  47. data/lib/lutaml/model/yaml_adapter.rb +1 -27
  48. data/lib/lutaml/model.rb +0 -5
  49. metadata +17 -5
  50. data/lib/lutaml/model/xml_namespace.rb +0 -47
@@ -0,0 +1,184 @@
1
+ require_relative "../mapping_hash"
2
+ require_relative "xml_element"
3
+ require_relative "xml_attribute"
4
+ require_relative "xml_namespace"
5
+
6
+ module Lutaml
7
+ module Model
8
+ module XmlAdapter
9
+ class XmlDocument
10
+ attr_reader :root
11
+
12
+ def initialize(root)
13
+ @root = root
14
+ end
15
+
16
+ def self.parse(xml)
17
+ raise NotImplementedError, "Subclasses must implement `parse`."
18
+ end
19
+
20
+ def children
21
+ @root.children
22
+ end
23
+
24
+ def declaration(options)
25
+ version = "1.0"
26
+ version = options[:declaration] if options[:declaration].is_a?(String)
27
+
28
+ encoding = options[:encoding] ? "UTF-8" : nil
29
+ encoding = options[:encoding] if options[:encoding].is_a?(String)
30
+
31
+ declaration = "<?xml version=\"#{version}\""
32
+ declaration += " encoding=\"#{encoding}\"" if encoding
33
+ declaration += "?>\n"
34
+ declaration
35
+ end
36
+
37
+ def to_h
38
+ parse_element(@root)
39
+ end
40
+
41
+ def order
42
+ @root.order
43
+ end
44
+
45
+ def handle_nested_elements(builder, value, rule: nil, attribute: nil)
46
+ options = build_options_for_nested_elements(attribute, rule)
47
+
48
+ case value
49
+ when Array
50
+ value.each { |val| build_element(builder, val, options) }
51
+ else
52
+ build_element(builder, value, options)
53
+ end
54
+ end
55
+
56
+ def build_options_for_nested_elements(attribute, rule)
57
+ return {} unless rule
58
+
59
+ options = {}
60
+
61
+ options[:namespace_prefix] = rule.prefix if rule&.namespace_set?
62
+ options[:mixed_content] = rule.mixed_content
63
+
64
+ options[:mapper_class] = attribute&.type if attribute
65
+
66
+ options
67
+ end
68
+
69
+ def parse_element(element)
70
+ result = Lutaml::Model::MappingHash.new
71
+ result.item_order = element.order
72
+
73
+ element.children.each_with_object(result) do |child, hash|
74
+ value = child.text? ? child.text : parse_element(child)
75
+
76
+ if hash[child.unprefixed_name]
77
+ hash[child.unprefixed_name] =
78
+ [hash[child.unprefixed_name], value].flatten
79
+ else
80
+ hash[child.unprefixed_name] = value
81
+ end
82
+ end
83
+
84
+ element.attributes.each_value do |attr|
85
+ result[attr.unprefixed_name] = attr.value
86
+ end
87
+
88
+ result
89
+ end
90
+
91
+ def build_element(xml, element, options = {})
92
+ if ordered?(element, options)
93
+ build_ordered_element(xml, element, options)
94
+ else
95
+ build_unordered_element(xml, element, options)
96
+ end
97
+ end
98
+
99
+ def ordered?(element, options = {})
100
+ return false unless element.respond_to?(:element_order)
101
+ return element.ordered? if element.respond_to?(:ordered?)
102
+ return options[:mixed_content] if options.key?(:mixed_content)
103
+
104
+ mapper_class = options[:mapper_class]
105
+ mapper_class ? mapper_class.mappings_for(:xml).mixed_content? : false
106
+ end
107
+
108
+ def build_namespace_attributes(klass, processed = {})
109
+ xml_mappings = klass.mappings_for(:xml)
110
+ attributes = klass.attributes
111
+
112
+ attrs = {}
113
+
114
+ if xml_mappings.namespace_prefix
115
+ attrs["xmlns:#{xml_mappings.namespace_prefix}"] =
116
+ xml_mappings.namespace_uri
117
+ end
118
+
119
+ xml_mappings.mappings.each do |mapping_rule|
120
+ processed[klass] ||= {}
121
+
122
+ next if processed[klass][mapping_rule.name]
123
+
124
+ processed[klass][mapping_rule.name] = true
125
+
126
+ type = if mapping_rule.delegate
127
+ attributes[mapping_rule.delegate].type.attributes[mapping_rule.to].type
128
+ else
129
+ attributes[mapping_rule.to].type
130
+ end
131
+
132
+ if type <= Lutaml::Model::Serialize
133
+ attrs = attrs.merge(build_namespace_attributes(type, processed))
134
+ end
135
+
136
+ if mapping_rule.namespace
137
+ attrs["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
138
+ end
139
+ end
140
+
141
+ attrs
142
+ end
143
+
144
+ def build_attributes(element, xml_mapping)
145
+ attrs = namespace_attributes(xml_mapping)
146
+
147
+ xml_mapping.attributes.each_with_object(attrs) do |mapping_rule, hash|
148
+ if mapping_rule.namespace
149
+ hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
150
+ end
151
+
152
+ hash[mapping_rule.prefixed_name] = element.send(mapping_rule.to)
153
+ end
154
+
155
+ xml_mapping.elements.each_with_object(attrs) do |mapping_rule, hash|
156
+ if mapping_rule.namespace
157
+ hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
158
+ end
159
+ end
160
+ end
161
+
162
+ def attribute_definition_for(element, rule, mapper_class: nil)
163
+ klass = mapper_class || element.class
164
+ return klass.attributes[rule.to] unless rule.delegate
165
+
166
+ element.send(rule.delegate).class.attributes[rule.to]
167
+ end
168
+
169
+ def attribute_value_for(element, rule)
170
+ return element.send(rule.to) unless rule.delegate
171
+
172
+ element.send(rule.delegate).send(rule.to)
173
+ end
174
+
175
+ def namespace_attributes(xml_mapping)
176
+ return {} unless xml_mapping.namespace_uri
177
+
178
+ key = ["xmlns", xml_mapping.namespace_prefix].compact.join(":")
179
+ { key => xml_mapping.namespace_uri }
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,94 @@
1
+ require_relative "xml_attribute"
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ class XmlElement
7
+ attr_reader :attributes,
8
+ :children,
9
+ :text,
10
+ :namespace_prefix,
11
+ :parent_document
12
+
13
+ def initialize(
14
+ name,
15
+ attributes = {},
16
+ children = [],
17
+ text = nil,
18
+ parent_document: nil,
19
+ namespace_prefix: nil
20
+ )
21
+ @name = extract_name(name)
22
+ @namespace_prefix = namespace_prefix || extract_namespace_prefix(name)
23
+ @attributes = attributes # .map { |k, v| XmlAttribute.new(k, v) }
24
+ @children = children
25
+ @text = text
26
+ @parent_document = parent_document
27
+ end
28
+
29
+ def name
30
+ if namespace_prefix
31
+ "#{namespace_prefix}:#{@name}"
32
+ else
33
+ @name
34
+ end
35
+ end
36
+
37
+ def unprefixed_name
38
+ @name
39
+ end
40
+
41
+ def document
42
+ XmlDocument.new(self)
43
+ end
44
+
45
+ def namespaces
46
+ @namespaces || @parent_document&.namespaces || {}
47
+ end
48
+
49
+ def own_namespaces
50
+ @namespaces || {}
51
+ end
52
+
53
+ def namespace
54
+ return default_namespace unless namespace_prefix
55
+
56
+ namespaces[namespace_prefix]
57
+ end
58
+
59
+ def attribute_is_namespace?(name)
60
+ name.to_s.start_with?("xmlns")
61
+ end
62
+
63
+ def add_namespace(namespace)
64
+ @namespaces ||= {}
65
+ @namespaces[namespace.prefix] = namespace
66
+ end
67
+
68
+ def default_namespace
69
+ namespaces[nil] || @parent_document&.namespaces&.dig(nil)
70
+ end
71
+
72
+ def extract_name(name)
73
+ n = name.to_s.split(":")
74
+ return name if n.length <= 1
75
+
76
+ n[1..].join(":")
77
+ end
78
+
79
+ def extract_namespace_prefix(name)
80
+ n = name.to_s.split(":")
81
+ return if n.length <= 1
82
+
83
+ n.first
84
+ end
85
+
86
+ def order
87
+ children.each_with_object([]) do |child, arr|
88
+ arr << child.unprefixed_name
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_striing_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ class XmlNamespace
7
+ # Return name
8
+ #
9
+ # @return [String]
10
+ #
11
+ # @api private
12
+ attr_accessor :uri
13
+
14
+ # Return prefix
15
+ #
16
+ # @return [String]
17
+ #
18
+ # @api private
19
+ attr_accessor :prefix
20
+
21
+ # Initialize instance
22
+ #
23
+ # @param [String, nil] name
24
+ # @param [String, nil] prefix
25
+ #
26
+ # @api private
27
+ def initialize(uri = nil, prefix = nil)
28
+ @uri = uri
29
+ @prefix = normalize_prefix(prefix)
30
+ end
31
+
32
+ def normalize_prefix(prefix)
33
+ normalized_prefix = prefix.to_s.gsub(/xmlns:?/, "")
34
+ return if normalized_prefix.empty?
35
+
36
+ normalized_prefix
37
+ end
38
+
39
+ def attr_name
40
+ if prefix && !prefix.empty?
41
+ "xmlns:#{prefix}"
42
+ else
43
+ "xmlns"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,292 +1,7 @@
1
- # lib/lutaml/model/xml_adapter.rb
2
-
3
- require_relative "xml_namespace"
4
- require_relative "mapping_hash"
5
-
6
1
  module Lutaml
7
2
  module Model
8
3
  module XmlAdapter
9
4
  XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace".freeze
10
-
11
- class Document
12
- attr_reader :root
13
-
14
- def initialize(root)
15
- @root = root
16
- end
17
-
18
- def self.parse(xml)
19
- raise NotImplementedError, "Subclasses must implement `parse`."
20
- end
21
-
22
- def children
23
- @root.children
24
- end
25
-
26
- def declaration(options)
27
- version = "1.0"
28
- version = options[:declaration] if options[:declaration].is_a?(String)
29
-
30
- encoding = options[:encoding] ? "UTF-8" : nil
31
- encoding = options[:encoding] if options[:encoding].is_a?(String)
32
-
33
- declaration = "<?xml version=\"#{version}\""
34
- declaration += " encoding=\"#{encoding}\"" if encoding
35
- declaration += "?>\n"
36
- declaration
37
- end
38
-
39
- def to_h
40
- parse_element(@root)
41
- end
42
-
43
- def order
44
- @root.order
45
- end
46
-
47
- def handle_nested_elements(builder, value, rule: nil, attribute: nil)
48
- options = build_options_for_nested_elements(attribute, rule)
49
-
50
- case value
51
- when Array
52
- value.each { |val| build_element(builder, val, options) }
53
- else
54
- build_element(builder, value, options)
55
- end
56
- end
57
-
58
- def build_options_for_nested_elements(attribute, rule)
59
- return {} unless rule
60
-
61
- options = {}
62
-
63
- options[:namespace_prefix] = rule.prefix if rule&.namespace_set?
64
- options[:mixed_content] = rule.mixed_content
65
-
66
- options[:mapper_class] = attribute&.type if attribute
67
-
68
- options
69
- end
70
-
71
- def parse_element(element)
72
- result = Lutaml::Model::MappingHash.new
73
- result.item_order = element.order
74
-
75
- element.children.each_with_object(result) do |child, hash|
76
- value = child.text? ? child.text : parse_element(child)
77
-
78
- if hash[child.unprefixed_name]
79
- hash[child.unprefixed_name] =
80
- [hash[child.unprefixed_name], value].flatten
81
- else
82
- hash[child.unprefixed_name] = value
83
- end
84
- end
85
-
86
- element.attributes.each_value do |attr|
87
- result[attr.unprefixed_name] = attr.value
88
- end
89
-
90
- result
91
- end
92
-
93
- def build_element(xml, element, options = {})
94
- if ordered?(element, options)
95
- build_ordered_element(xml, element, options)
96
- else
97
- build_unordered_element(xml, element, options)
98
- end
99
- end
100
-
101
- def ordered?(element, options = {})
102
- return false unless element.respond_to?(:element_order)
103
- return element.ordered? if element.respond_to?(:ordered?)
104
- return options[:mixed_content] if options.key?(:mixed_content)
105
-
106
- mapper_class = options[:mapper_class]
107
- mapper_class ? mapper_class.mappings_for(:xml).mixed_content? : false
108
- end
109
-
110
- def build_namespace_attributes(klass, processed = {})
111
- xml_mappings = klass.mappings_for(:xml)
112
- attributes = klass.attributes
113
-
114
- attrs = {}
115
-
116
- if xml_mappings.namespace_prefix
117
- attrs["xmlns:#{xml_mappings.namespace_prefix}"] =
118
- xml_mappings.namespace_uri
119
- end
120
-
121
- xml_mappings.mappings.each do |mapping_rule|
122
- processed[klass] ||= {}
123
-
124
- next if processed[klass][mapping_rule.name]
125
-
126
- processed[klass][mapping_rule.name] = true
127
-
128
- type = if mapping_rule.delegate
129
- attributes[mapping_rule.delegate].type.attributes[mapping_rule.to].type
130
- else
131
- attributes[mapping_rule.to].type
132
- end
133
-
134
- if type <= Lutaml::Model::Serialize
135
- attrs = attrs.merge(build_namespace_attributes(type, processed))
136
- end
137
-
138
- if mapping_rule.namespace
139
- attrs["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
140
- end
141
- end
142
-
143
- attrs
144
- end
145
-
146
- def build_attributes(element, xml_mapping)
147
- attrs = namespace_attributes(xml_mapping)
148
-
149
- xml_mapping.attributes.each_with_object(attrs) do |mapping_rule, hash|
150
- if mapping_rule.namespace
151
- hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
152
- end
153
-
154
- hash[mapping_rule.prefixed_name] = element.send(mapping_rule.to)
155
- end
156
-
157
- xml_mapping.elements.each_with_object(attrs) do |mapping_rule, hash|
158
- if mapping_rule.namespace
159
- hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
160
- end
161
- end
162
- end
163
-
164
- def attribute_definition_for(element, rule, mapper_class: nil)
165
- klass = mapper_class || element.class
166
- return klass.attributes[rule.to] unless rule.delegate
167
-
168
- element.send(rule.delegate).class.attributes[rule.to]
169
- end
170
-
171
- def attribute_value_for(element, rule)
172
- return element.send(rule.to) unless rule.delegate
173
-
174
- element.send(rule.delegate).send(rule.to)
175
- end
176
-
177
- def namespace_attributes(xml_mapping)
178
- return {} unless xml_mapping.namespace_uri
179
-
180
- key = ["xmlns", xml_mapping.namespace_prefix].compact.join(":")
181
- { key => xml_mapping.namespace_uri }
182
- end
183
- end
184
-
185
- class Element
186
- attr_reader :attributes,
187
- :children,
188
- :text,
189
- :namespace_prefix,
190
- :parent_document
191
-
192
- def initialize(
193
- name,
194
- attributes = {},
195
- children = [],
196
- text = nil,
197
- parent_document: nil,
198
- namespace_prefix: nil
199
- )
200
- @name = extract_name(name)
201
- @namespace_prefix = namespace_prefix || extract_namespace_prefix(name)
202
- @attributes = attributes # .map { |k, v| Attribute.new(k, v) }
203
- @children = children
204
- @text = text
205
- @parent_document = parent_document
206
- end
207
-
208
- def name
209
- if namespace_prefix
210
- "#{namespace_prefix}:#{@name}"
211
- else
212
- @name
213
- end
214
- end
215
-
216
- def unprefixed_name
217
- @name
218
- end
219
-
220
- def document
221
- Document.new(self)
222
- end
223
-
224
- def namespaces
225
- @namespaces || @parent_document&.namespaces || {}
226
- end
227
-
228
- def own_namespaces
229
- @namespaces || {}
230
- end
231
-
232
- def namespace
233
- return default_namespace unless namespace_prefix
234
-
235
- namespaces[namespace_prefix]
236
- end
237
-
238
- def attribute_is_namespace?(name)
239
- name.to_s.start_with?("xmlns")
240
- end
241
-
242
- def add_namespace(namespace)
243
- @namespaces ||= {}
244
- @namespaces[namespace.prefix] = namespace
245
- end
246
-
247
- def default_namespace
248
- namespaces[nil] || @parent_document&.namespaces&.dig(nil)
249
- end
250
-
251
- def extract_name(name)
252
- n = name.to_s.split(":")
253
- return name if n.length <= 1
254
-
255
- n[1..].join(":")
256
- end
257
-
258
- def extract_namespace_prefix(name)
259
- n = name.to_s.split(":")
260
- return if n.length <= 1
261
-
262
- n.first
263
- end
264
-
265
- def order
266
- children.each_with_object([]) do |child, arr|
267
- arr << child.unprefixed_name
268
- end
269
- end
270
- end
271
-
272
- class Attribute
273
- attr_reader :name, :value, :namespace, :namespace_prefix
274
-
275
- def initialize(name, value, namespace: nil, namespace_prefix: nil)
276
- @name = name
277
- @value = value
278
- @namespace = namespace
279
- @namespace_prefix = namespace_prefix
280
- end
281
-
282
- def unprefixed_name
283
- if namespace_prefix
284
- name.split(":").last
285
- else
286
- name
287
- end
288
- end
289
- end
290
5
  end
291
6
  end
292
7
  end
@@ -1,4 +1,3 @@
1
- # lib/lutaml/model/xml_mapping.rb
2
1
  require_relative "xml_mapping_rule"
3
2
 
4
3
  module Lutaml
@@ -82,6 +81,7 @@ module Lutaml
82
81
  namespace_set: namespace_set != false,
83
82
  )
84
83
  end
84
+
85
85
  # rubocop:enable Metrics/ParameterLists
86
86
 
87
87
  def map_content(
@@ -1,4 +1,3 @@
1
- # lib/lutaml/model/xml_mapping_rule.rb
2
1
  require_relative "mapping_rule"
3
2
 
4
3
  module Lutaml
@@ -24,12 +23,12 @@ module Lutaml
24
23
  with: with,
25
24
  delegate: delegate,
26
25
  mixed_content: mixed_content,
27
- namespace_set: namespace_set
26
+ namespace_set: namespace_set,
28
27
  )
29
28
 
30
29
  @namespace = if namespace.to_s == "inherit"
31
- # we are using inherit_namespace in xml builder by
32
- # default so no need to do anything here.
30
+ # we are using inherit_namespace in xml builder by
31
+ # default so no need to do anything here.
33
32
  else
34
33
  namespace
35
34
  end
@@ -0,0 +1,34 @@
1
+ require "yaml"
2
+ require_relative "yaml_document"
3
+
4
+ module Lutaml
5
+ module Model
6
+ module YamlAdapter
7
+ class StandardYamlAdapter < YamlDocument
8
+ def self.parse(yaml)
9
+ YAML.safe_load(
10
+ yaml,
11
+ permitted_classes: [Date, Time, DateTime, Symbol,
12
+ BigDecimal, Hash, Array],
13
+ )
14
+ end
15
+
16
+ def to_yaml(options = {})
17
+ YAML.dump(@attributes, options)
18
+ end
19
+
20
+ # TODO: Is this really needed?
21
+ def self.to_yaml(attributes, *args)
22
+ new(attributes).to_yaml(*args)
23
+ end
24
+
25
+ # TODO: Is this really needed?
26
+ def self.from_yaml(yaml, klass)
27
+ data = parse(yaml)
28
+ mapped_attrs = klass.send(:apply_mappings, data, :yaml)
29
+ klass.new(mapped_attrs)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "yaml_object"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module YamlAdapter
8
+ # Base class for YAML documents
9
+ class YamlDocument < YamlObject
10
+ def self.parse(yaml)
11
+ raise NotImplementedError, "Subclasses must implement `parse`."
12
+ end
13
+
14
+ def to_yaml(*args)
15
+ raise NotImplementedError, "Subclasses must implement `to_yaml`."
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end