lutaml-model 0.3.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) 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 +1100 -140
  6. data/lib/lutaml/model/attribute.rb +15 -2
  7. data/lib/lutaml/model/config.rb +0 -1
  8. data/lib/lutaml/model/error/invalid_value_error.rb +18 -0
  9. data/lib/lutaml/model/error.rb +8 -0
  10. data/lib/lutaml/model/json_adapter/json_document.rb +20 -0
  11. data/lib/lutaml/model/json_adapter/json_object.rb +28 -0
  12. data/lib/lutaml/model/json_adapter/{multi_json.rb → multi_json_adapter.rb} +2 -3
  13. data/lib/lutaml/model/json_adapter/{standard.rb → standard_json_adapter.rb} +2 -3
  14. data/lib/lutaml/model/json_adapter.rb +1 -31
  15. data/lib/lutaml/model/key_value_mapping.rb +9 -2
  16. data/lib/lutaml/model/key_value_mapping_rule.rb +0 -1
  17. data/lib/lutaml/model/mapping_hash.rb +0 -2
  18. data/lib/lutaml/model/mapping_rule.rb +5 -3
  19. data/lib/lutaml/model/schema/json_schema.rb +0 -1
  20. data/lib/lutaml/model/schema/relaxng_schema.rb +0 -1
  21. data/lib/lutaml/model/schema/xsd_schema.rb +0 -1
  22. data/lib/lutaml/model/schema/yaml_schema.rb +0 -1
  23. data/lib/lutaml/model/schema.rb +0 -1
  24. data/lib/lutaml/model/serializable.rb +0 -1
  25. data/lib/lutaml/model/serialize.rb +241 -153
  26. data/lib/lutaml/model/toml_adapter/toml_document.rb +20 -0
  27. data/lib/lutaml/model/toml_adapter/toml_object.rb +28 -0
  28. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +4 -5
  29. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +2 -3
  30. data/lib/lutaml/model/toml_adapter.rb +0 -31
  31. data/lib/lutaml/model/type/date_time.rb +20 -0
  32. data/lib/lutaml/model/type/json.rb +34 -0
  33. data/lib/lutaml/model/type/time_without_date.rb +4 -3
  34. data/lib/lutaml/model/type.rb +61 -124
  35. data/lib/lutaml/model/version.rb +1 -1
  36. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +20 -13
  37. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +4 -5
  38. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +24 -17
  39. data/lib/lutaml/model/xml_adapter/xml_attribute.rb +27 -0
  40. data/lib/lutaml/model/xml_adapter/xml_document.rb +184 -0
  41. data/lib/lutaml/model/xml_adapter/xml_element.rb +94 -0
  42. data/lib/lutaml/model/xml_adapter/xml_namespace.rb +49 -0
  43. data/lib/lutaml/model/xml_adapter.rb +0 -266
  44. data/lib/lutaml/model/xml_mapping.rb +1 -1
  45. data/lib/lutaml/model/xml_mapping_rule.rb +3 -4
  46. data/lib/lutaml/model/yaml_adapter/standard_yaml_adapter.rb +34 -0
  47. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +20 -0
  48. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +28 -0
  49. data/lib/lutaml/model/yaml_adapter.rb +1 -19
  50. data/lib/lutaml/model.rb +7 -5
  51. metadata +19 -5
  52. data/lib/lutaml/model/xml_namespace.rb +0 -47
@@ -1,4 +1,3 @@
1
- # lib/lutaml/model/serialize.rb
2
1
  require_relative "yaml_adapter"
3
2
  require_relative "xml_adapter"
4
3
  require_relative "config"
@@ -25,11 +24,24 @@ module Lutaml
25
24
  def inherited(subclass)
26
25
  super
27
26
 
27
+ @mappings ||= {}
28
+ @attributes ||= {}
29
+
28
30
  subclass.instance_variable_set(:@attributes, @attributes.dup)
31
+ subclass.instance_variable_set(:@mappings, @mappings.dup)
32
+ subclass.instance_variable_set(:@model, subclass)
29
33
  end
30
34
 
35
+ def model(klass = nil)
36
+ if klass
37
+ @model = klass
38
+ else
39
+ @model
40
+ end
41
+ end
42
+
43
+ # Define an attribute for the model
31
44
  def attribute(name, type, options = {})
32
- self.attributes ||= {}
33
45
  attr = Attribute.new(name, type, options)
34
46
  attributes[name] = attr
35
47
 
@@ -38,19 +50,33 @@ module Lutaml
38
50
  end
39
51
 
40
52
  define_method(:"#{name}=") do |value|
53
+ unless self.class.attr_value_valid?(name, value)
54
+ raise Lutaml::Model::InvalidValueError.new(name, value, options[:values])
55
+ end
56
+
41
57
  instance_variable_set(:"@#{name}", value)
42
58
  end
43
59
  end
44
60
 
61
+ # Check if the value to be assigned is valid for the attribute
62
+ def attr_value_valid?(name, value)
63
+ attr = attributes[name]
64
+
65
+ return true unless attr.options[:values]
66
+
67
+ # If value validation failed but there is a default value, do not
68
+ # raise a validation error
69
+ attr.options[:values].include?(value || attr.default)
70
+ end
71
+
45
72
  FORMATS.each do |format|
46
73
  define_method(format) do |&block|
47
- self.mappings ||= {}
48
74
  klass = format == :xml ? XmlMapping : KeyValueMapping
49
- self.mappings[format] = klass.new
50
- self.mappings[format].instance_eval(&block)
75
+ mappings[format] = klass.new
76
+ mappings[format].instance_eval(&block)
51
77
 
52
- if format == :xml && !self.mappings[format].root_element
53
- self.mappings[format].root(to_s)
78
+ if format == :xml && !mappings[format].root_element
79
+ mappings[format].root(model.to_s)
54
80
  end
55
81
  end
56
82
 
@@ -59,24 +85,200 @@ module Lutaml
59
85
  doc = adapter.parse(data)
60
86
  mapped_attrs = apply_mappings(doc.to_h, format)
61
87
  # apply_content_mapping(doc, mapped_attrs) if format == :xml
62
- new(mapped_attrs)
88
+ generate_model_object(self, mapped_attrs)
89
+ end
90
+
91
+ define_method(:"to_#{format}") do |instance|
92
+ unless instance.is_a?(model)
93
+ msg = "argument is a '#{instance.class}' but should be a '#{model}'"
94
+ raise Lutaml::Model::IncorrectModelError, msg
95
+ end
96
+
97
+ adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
98
+
99
+ if format == :xml
100
+ xml_options = { mapper_class: self }
101
+
102
+ adapter.new(instance).public_send(:"to_#{format}", xml_options)
103
+ else
104
+ hash = hash_representation(instance, format)
105
+ adapter.new(hash).public_send(:"to_#{format}")
106
+ end
107
+ end
108
+ end
109
+
110
+ def hash_representation(instance, format, options = {})
111
+ only = options[:only]
112
+ except = options[:except]
113
+ mappings = mappings_for(format).mappings
114
+
115
+ mappings.each_with_object({}) do |rule, hash|
116
+ name = rule.to
117
+ next if except&.include?(name) || (only && !only.include?(name))
118
+
119
+ next handle_delegate(instance, rule, hash) if rule.delegate
120
+
121
+ value = if rule.custom_methods[:to]
122
+ instance.send(rule.custom_methods[:to], instance, instance.send(name))
123
+ else
124
+ instance.send(name)
125
+ end
126
+
127
+ next if value.nil? && !rule.render_nil
128
+
129
+ attribute = attributes[name]
130
+
131
+ hash[rule.from] = if rule.child_mappings
132
+ generate_hash_from_child_mappings(value, rule.child_mappings)
133
+ elsif value.is_a?(Array)
134
+ value.map do |v|
135
+ if attribute.type <= Serialize
136
+ attribute.type.hash_representation(v, format, options)
137
+ else
138
+ attribute.type.serialize(v)
139
+ end
140
+ end
141
+ elsif attribute.type <= Serialize
142
+ attribute.type.hash_representation(value, format, options)
143
+ else
144
+ attribute.type.serialize(value)
145
+ end
63
146
  end
64
147
  end
65
148
 
149
+ def handle_delegate(instance, rule, hash)
150
+ name = rule.to
151
+ value = instance.send(rule.delegate).send(name)
152
+ return if value.nil? && !rule.render_nil
153
+
154
+ attribute = instance.send(rule.delegate).class.attributes[name]
155
+ hash[rule.from] = case value
156
+ when Array
157
+ value.map do |v|
158
+ if v.is_a?(Serialize)
159
+ hash_representation(v, format, options)
160
+ else
161
+ attribute.type.serialize(v)
162
+ end
163
+ end
164
+ else
165
+ if value.is_a?(Serialize)
166
+ hash_representation(value, format, options)
167
+ else
168
+ attribute.type.serialize(value)
169
+ end
170
+ end
171
+ end
172
+
66
173
  def mappings_for(format)
67
- self.mappings[format] || default_mappings(format)
174
+ mappings[format] || default_mappings(format)
175
+ end
176
+
177
+ def generate_model_object(type, mapped_attrs)
178
+ return type.model.new(mapped_attrs) if self == model
179
+
180
+ instance = type.model.new
181
+
182
+ type.attributes.each do |name, attr|
183
+ value = attr_value(mapped_attrs, name, attr)
184
+
185
+ instance.send(:"#{name}=", ensure_utf8(value))
186
+ end
187
+
188
+ instance
189
+ end
190
+
191
+ def attr_value(attrs, name, attr_rule)
192
+ value = if attrs.key?(name)
193
+ attrs[name]
194
+ elsif attrs.key?(name.to_sym)
195
+ attrs[name.to_sym]
196
+ elsif attrs.key?(name.to_s)
197
+ attrs[name.to_s]
198
+ else
199
+ attr_rule.default
200
+ end
201
+
202
+ if attr_rule.collection? || value.is_a?(Array)
203
+ (value || []).map do |v|
204
+ if v.is_a?(Hash)
205
+ attr_rule.type.new(v)
206
+ else
207
+ # TODO: This code is problematic because Type.cast does not know
208
+ # about all the types.
209
+ Lutaml::Model::Type.cast(
210
+ v, attr_rule.type
211
+ )
212
+ end
213
+ end
214
+ elsif value.is_a?(Hash) && attr_rule.type != Lutaml::Model::Type::Hash
215
+ generate_model_object(attr_rule.type, value)
216
+ else
217
+ # TODO: This code is problematic because Type.cast does not know
218
+ # about all the types.
219
+ Lutaml::Model::Type.cast(value, attr_rule.type)
220
+ end
68
221
  end
69
222
 
70
223
  def default_mappings(format)
71
224
  klass = format == :xml ? XmlMapping : KeyValueMapping
72
225
  klass.new.tap do |mapping|
73
226
  attributes&.each do |name, attr|
74
- mapping.map_element(name.to_s, to: name,
75
- render_nil: attr.render_nil?)
227
+ mapping.map_element(
228
+ name.to_s,
229
+ to: name,
230
+ render_nil: attr.render_nil?,
231
+ )
76
232
  end
77
233
  end
78
234
  end
79
235
 
236
+ def apply_child_mappings(hash, child_mappings)
237
+ return hash unless child_mappings
238
+
239
+ hash.map do |key, value|
240
+ child_mappings.to_h do |attr_name, path|
241
+ attr_value = if path == :key
242
+ key
243
+ elsif path == :value
244
+ value
245
+ else
246
+ path = [path] unless path.is_a?(Array)
247
+ value.dig(*path.map(&:to_s))
248
+ end
249
+
250
+ [attr_name, attr_value]
251
+ end
252
+ end
253
+ end
254
+
255
+ def generate_hash_from_child_mappings(value, child_mappings)
256
+ return value unless child_mappings
257
+
258
+ hash = {}
259
+
260
+ value.each do |child_obj|
261
+ map_key = nil
262
+ map_value = {}
263
+ child_mappings.each do |attr_name, path|
264
+ if path == :key
265
+ map_key = child_obj.send(attr_name)
266
+ elsif path == :value
267
+ map_value = child_obj.send(attr_name)
268
+ else
269
+ path = [path] unless path.is_a?(Array)
270
+ path[0...-1].inject(map_value) do |acc, k|
271
+ acc[k.to_s] ||= {}
272
+ end.public_send(:[]=, path.last.to_s, child_obj.send(attr_name))
273
+ end
274
+ end
275
+ # hash[mapping.name] ||= {}
276
+ hash[map_key] = map_value
277
+ end
278
+
279
+ hash
280
+ end
281
+
80
282
  def apply_mappings(doc, format)
81
283
  return apply_xml_mapping(doc) if format == :xml
82
284
 
@@ -98,6 +300,8 @@ module Lutaml
98
300
  attr.default
99
301
  end
100
302
 
303
+ value = apply_child_mappings(value, rule.child_mappings)
304
+
101
305
  if attr.collection?
102
306
  value = (value || []).map do |v|
103
307
  attr.type <= Serialize ? attr.type.apply_mappings(v, format) : v
@@ -167,6 +371,21 @@ module Lutaml
167
371
  hash[rule.to] = value
168
372
  end
169
373
  end
374
+
375
+ def ensure_utf8(value)
376
+ case value
377
+ when String
378
+ value.encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
379
+ when Array
380
+ value.map { |v| ensure_utf8(v) }
381
+ when Hash
382
+ value.transform_keys do |k|
383
+ ensure_utf8(k)
384
+ end.transform_values { |v| ensure_utf8(v) }
385
+ else
386
+ value
387
+ end
388
+ end
170
389
  end
171
390
 
172
391
  attr_reader :element_order
@@ -180,37 +399,9 @@ module Lutaml
180
399
  end
181
400
 
182
401
  self.class.attributes.each do |name, attr|
183
- value = attr_value(attrs, name, attr)
184
-
185
- send(:"#{name}=", ensure_utf8(value))
186
- end
187
- end
402
+ value = self.class.attr_value(attrs, name, attr)
188
403
 
189
- def attr_value(attrs, name, attr_rule)
190
- value = if attrs.key?(name)
191
- attrs[name]
192
- elsif attrs.key?(name.to_sym)
193
- attrs[name.to_sym]
194
- elsif attrs.key?(name.to_s)
195
- attrs[name.to_s]
196
- else
197
- attr_rule.default
198
- end
199
-
200
- if attr_rule.collection? || value.is_a?(Array)
201
- (value || []).map do |v|
202
- if v.is_a?(Hash)
203
- attr_rule.type.new(v)
204
- else
205
- Lutaml::Model::Type.cast(
206
- v, attr_rule.type
207
- )
208
- end
209
- end
210
- elsif value.is_a?(Hash) && attr_rule.type != Lutaml::Model::Type::Hash
211
- attr_rule.type.new(value)
212
- else
213
- Lutaml::Model::Type.cast(value, attr_rule.type)
404
+ send(:"#{name}=", self.class.ensure_utf8(value))
214
405
  end
215
406
  end
216
407
 
@@ -226,119 +417,16 @@ module Lutaml
226
417
  hash[key] || hash[key.to_sym] || hash[key.to_s]
227
418
  end
228
419
 
229
- # TODO: Make this work
230
- # FORMATS.each do |format|
231
- # define_method("to_#{format}") do |options = {}|
232
- # adapter = Lutaml::Model::Config.send("#{format}_adapter")
233
- # representation = if format == :yaml
234
- # self
235
- # else
236
- # hash_representation(format, options)
237
- # end
238
- # adapter.new(representation).send("to_#{format}", options)
239
- # end
240
- # end
241
-
242
- def to_xml(options = {})
243
- adapter = Lutaml::Model::Config.xml_adapter
244
- adapter.new(self).to_xml(options)
245
- end
246
-
247
- def to_json(options = {})
248
- adapter = Lutaml::Model::Config.json_adapter
249
- adapter.new(hash_representation(:json, options)).to_json(options)
250
- end
251
-
252
- def to_yaml(options = {})
253
- adapter = Lutaml::Model::Config.yaml_adapter
254
- adapter.to_yaml(self, options)
255
- end
256
-
257
- def to_toml(options = {})
258
- adapter = Lutaml::Model::Config.toml_adapter
259
- adapter.new(hash_representation(:toml, options)).to_toml
260
- end
261
-
262
- # TODO: END Make this work
263
-
264
- def hash_representation(format, options = {})
265
- only = options[:only]
266
- except = options[:except]
267
- mappings = self.class.mappings_for(format).mappings
268
-
269
- mappings.each_with_object({}) do |rule, hash|
270
- name = rule.to
271
- next if except&.include?(name) || (only && !only.include?(name))
272
-
273
- next handle_delegate(self, rule, hash) if rule.delegate
274
-
275
- value = if rule.custom_methods[:to]
276
- send(rule.custom_methods[:to], self, send(name))
277
- else
278
- send(name)
279
- end
280
-
281
- next if value.nil? && !rule.render_nil
282
-
283
- attribute = self.class.attributes[name]
284
-
285
- hash[rule.from] = case value
286
- when Array
287
- value.map do |v|
288
- if v.is_a?(Serialize)
289
- v.hash_representation(format, options)
290
- else
291
- attribute.type.serialize(v)
292
- end
293
- end
294
- else
295
- if value.is_a?(Serialize)
296
- value.hash_representation(format, options)
297
- else
298
- attribute.type.serialize(value)
299
- end
300
- end
301
- end
302
- end
303
-
304
- private
305
-
306
- def handle_delegate(_obj, rule, hash)
307
- name = rule.to
308
- value = send(rule.delegate).send(name)
309
- return if value.nil? && !rule.render_nil
310
-
311
- attribute = send(rule.delegate).class.attributes[name]
312
- hash[rule.from] = case value
313
- when Array
314
- value.map do |v|
315
- if v.is_a?(Serialize)
316
- v.hash_representation(format, options)
317
- else
318
- attribute.type.serialize(v)
319
- end
320
- end
321
- else
322
- if value.is_a?(Serialize)
323
- value.hash_representation(format, options)
324
- else
325
- attribute.type.serialize(value)
326
- end
327
- end
328
- end
420
+ FORMATS.each do |format|
421
+ define_method(:"to_#{format}") do |options = {}|
422
+ adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
423
+ representation = if format == :xml
424
+ self
425
+ else
426
+ self.class.hash_representation(self, format, options)
427
+ end
329
428
 
330
- def ensure_utf8(value)
331
- case value
332
- when String
333
- value.encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
334
- when Array
335
- value.map { |v| ensure_utf8(v) }
336
- when Hash
337
- value.transform_keys do |k|
338
- ensure_utf8(k)
339
- end.transform_values { |v| ensure_utf8(v) }
340
- else
341
- value
429
+ adapter.new(representation).public_send(:"to_#{format}", options)
342
430
  end
343
431
  end
344
432
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "toml_object"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module TomlAdapter
8
+ # Base class for TOML documents
9
+ class TomlDocument < TomlObject
10
+ def self.parse(toml)
11
+ raise NotImplementedError, "Subclasses must implement `parse`."
12
+ end
13
+
14
+ def to_toml(*args)
15
+ raise NotImplementedError, "Subclasses must implement `to_toml`."
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module TomlAdapter
6
+ # Base class for TOML objects
7
+ class TomlObject
8
+ attr_reader :attributes
9
+
10
+ def initialize(attributes = {})
11
+ @attributes = attributes
12
+ end
13
+
14
+ def [](key)
15
+ @attributes[key]
16
+ end
17
+
18
+ def []=(key, value)
19
+ @attributes[key] = value
20
+ end
21
+
22
+ def to_h
23
+ @attributes
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,18 +1,17 @@
1
- # lib/lutaml/model/toml_adapter/toml_rb_adapter.rb
2
1
  require "toml-rb"
3
- require_relative "../toml_adapter"
2
+ require_relative "toml_document"
4
3
 
5
4
  module Lutaml
6
5
  module Model
7
6
  module TomlAdapter
8
- class TomlRbDocument < Document
7
+ class TomlRbAdapter < TomlDocument
9
8
  def self.parse(toml)
10
9
  data = TomlRB.parse(toml)
11
10
  new(data)
12
11
  end
13
12
 
14
- def to_toml(*args)
15
- TomlRB.dump(to_h, *args)
13
+ def to_toml(*)
14
+ TomlRB.dump(to_h)
16
15
  end
17
16
  end
18
17
  end
@@ -1,11 +1,10 @@
1
- # lib/lutaml/model/toml_adapter/tomlib_adapter.rb
2
1
  require "tomlib"
3
- require_relative "../toml_adapter"
2
+ require_relative "toml_document"
4
3
 
5
4
  module Lutaml
6
5
  module Model
7
6
  module TomlAdapter
8
- class TomlibDocument < Document
7
+ class TomlibAdapter < TomlDocument
9
8
  def self.parse(toml)
10
9
  data = Tomlib.load(toml)
11
10
  new(data)
@@ -1,37 +1,6 @@
1
- # lib/lutaml/model/toml_adapter.rb
2
-
3
1
  module Lutaml
4
2
  module Model
5
3
  module TomlAdapter
6
- class TomlObject
7
- attr_reader :attributes
8
-
9
- def initialize(attributes = {})
10
- @attributes = attributes
11
- end
12
-
13
- def [](key)
14
- @attributes[key]
15
- end
16
-
17
- def []=(key, value)
18
- @attributes[key] = value
19
- end
20
-
21
- def to_h
22
- @attributes
23
- end
24
- end
25
-
26
- class Document < TomlObject
27
- def self.parse(toml)
28
- raise NotImplementedError, "Subclasses must implement `parse`."
29
- end
30
-
31
- def to_toml(*args)
32
- raise NotImplementedError, "Subclasses must implement `to_toml`."
33
- end
34
- end
35
4
  end
36
5
  end
37
6
  end
@@ -0,0 +1,20 @@
1
+ require "date"
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Type
6
+ # Date and time representation
7
+ class DateTime
8
+ def self.cast(value)
9
+ return if value.nil?
10
+
11
+ ::DateTime.parse(value.to_s).new_offset(0)
12
+ end
13
+
14
+ def self.serialize(value)
15
+ value.iso8601
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ require "json"
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Type
6
+ # JSON representation
7
+ class Json
8
+ attr_reader :value
9
+
10
+ def initialize(value)
11
+ @value = value
12
+ end
13
+
14
+ def to_json(*_args)
15
+ @value.to_json
16
+ end
17
+
18
+ def ==(other)
19
+ @value == (other.is_a?(::Hash) ? other : other.value)
20
+ end
21
+
22
+ def self.cast(value)
23
+ return value if value.is_a?(self) || value.nil?
24
+
25
+ new(::JSON.parse(value))
26
+ end
27
+
28
+ def self.serialize(value)
29
+ value.to_json
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,11 +1,12 @@
1
- # lib/lutaml/model/type/time_without_date.rb
2
1
  module Lutaml
3
2
  module Model
4
3
  module Type
4
+ # Time representation without date
5
5
  class TimeWithoutDate
6
6
  def self.cast(value)
7
- parsed_time = ::Time.parse(value.to_s)
8
- parsed_time.strftime("%H:%M:%S")
7
+ return if value.nil?
8
+
9
+ ::Time.parse(value.to_s)
9
10
  end
10
11
 
11
12
  def self.serialize(value)