lutaml-model 0.3.0 → 0.3.2

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.
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)