shale 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c96481848145b9736af4cc830c84c04970042fdc61f7d348aa64099065319ef
4
- data.tar.gz: 25739ce30a7f4e66040b5dbff394da06a5c578065af0cd0b03bd0a56c7d78cbf
3
+ metadata.gz: ab65ef1fa452cb7d3810b6a996597190541fcbc5e4d5832901f8608bb8ddbf29
4
+ data.tar.gz: 241f5a1b333205a53cb598e51c8cd639a82f6f7661437f44d20fc3a0674935df
5
5
  SHA512:
6
- metadata.gz: f205c1407868c6469922b7553f9aef3cdb0dc70502981d56eeef5b08703c793213f63c2a898568f372bc4e9519bcbfa771eefd6cfd663746e5b4180d1738c351
7
- data.tar.gz: 95d7e2c6f88d7d5099c1ff62345509e83faee23a3bdd16e77d8eea05fb061e0eeb76fabbba5dc50d7c15ea675d2d1430570a2ac4134416c3d03d905d4ef59528
6
+ metadata.gz: a6107d0c547481b76ad03573a880001043b030825eccccdb11e5627dfc817467bedce0c77f0c2ca52f267a2791855c5b37aa6a518347be908458bbeff42e1f50
7
+ data.tar.gz: c7e883f3a41049a72904ca94702eadcb2c568b1d8c7717ad2000340b21c53b5f9c90bd1a981e57866bb704e9ddcfb36d713b74061ed59eb3d065df9fbc5fbcba
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [0.2.0] - 2022-01-20
2
+
3
+ ### Added
4
+ - Support for using methods to extract/generate data from/to document
5
+
6
+ ### Changed
7
+ - deduplicate code
8
+ - Rename `Shale::Type::Complex` -> `Shale::Type::Composite`
9
+
1
10
  ## [0.1.0] - 2021-11-30
2
11
 
3
12
  First public release
data/README.md CHANGED
@@ -48,7 +48,7 @@ end
48
48
  ```
49
49
 
50
50
  - `default: -> { 'value' }` - add a default value to attribute (it must be a proc that returns value)
51
- - `collection: true` - indicated that a attribute is a collection
51
+ - `collection: true` - indicates that a attribute is a collection
52
52
 
53
53
  ### Creating objects
54
54
 
@@ -327,6 +327,100 @@ DATA
327
327
  - `map_attribute` - map element's attribute to attribute
328
328
  - `map_content` - map first text node to attribute
329
329
 
330
+ ### Using methods to extract and generate data
331
+
332
+ If you need full controll over extracting and generating data from/to document,
333
+ you can use methods to do so:
334
+
335
+ ```ruby
336
+ class Person < Shale::Mapper
337
+ attribute :hobbies, Shale::Type::String, collection: true
338
+ attribute :street, Shale::Type::String
339
+ attribute :city, Shale::Type::String
340
+
341
+ json do
342
+ map 'hobbies', using: { from: :hobbies_from_json, to: :hobbies_to_json }
343
+ map 'address', using: { from: :address_from_json, to: :address_to_json }
344
+ end
345
+
346
+ xml do
347
+ root 'Person'
348
+
349
+ map_attribute 'hobbies', using: { from: :hobbies_from_xml, to: :hobbies_to_xml }
350
+ map_element 'Address', using: { from: :address_from_xml, to: :address_to_xml }
351
+ end
352
+
353
+ def hobbies_from_json(value)
354
+ self.hobbies = value.split(',').map(&:strip)
355
+ end
356
+
357
+ def hobbies_to_json
358
+ hobbies.join(', ')
359
+ end
360
+
361
+ def address_from_json(value)
362
+ self.street = value['street']
363
+ self.city = value['city']
364
+ end
365
+
366
+ def address_to_json
367
+ { 'street' => street, 'city' => city }
368
+ end
369
+
370
+ def hobbies_from_xml(value)
371
+ self.hobbies = value.split(',').map(&:strip)
372
+ end
373
+
374
+ def hobbies_to_xml(element, doc)
375
+ doc.add_attribute(element, 'hobbies', hobbies.join(', '))
376
+ end
377
+
378
+ def address_from_xml(node)
379
+ self.street = node.children.find { |e| e.name == 'Street' }.text
380
+ self.city = node.children.find { |e| e.name == 'City' }.text
381
+ end
382
+
383
+ def address_to_xml(parent, doc)
384
+ street_element = doc.create_element('Street')
385
+ doc.add_text(street_element, street.to_s)
386
+
387
+ city_element = doc.create_element('City')
388
+ doc.add_text(city_element, city.to_s)
389
+
390
+ address_element = doc.create_element('Address')
391
+ doc.add_element(address_element, street_element)
392
+ doc.add_element(address_element, city_element)
393
+ doc.add_element(parent, address_element)
394
+ end
395
+ end
396
+
397
+ person = Person.from_json(<<~DATA)
398
+ {
399
+ "hobbies": "Singing, Dancing, Running",
400
+ "address": {
401
+ "street": "Oxford Street",
402
+ "city": "London"
403
+ }
404
+ }
405
+ DATA
406
+
407
+ person = Person.from_xml(<<~DATA)
408
+ <Person hobbies="Singing, Dancing, Running">
409
+ <Address>
410
+ <Street>Oxford Street</Street>
411
+ <City>London</City>
412
+ </Address>
413
+ </person>
414
+ DATA
415
+
416
+ # =>
417
+ #
418
+ # #<Person:0x00007f9bc3086d60
419
+ # @hobbies=["Singing", "Dancing", "Running"],
420
+ # @street="Oxford Street",
421
+ # @city="London">
422
+ ```
423
+
330
424
  ### Supported types
331
425
 
332
426
  Shale supports these types out of the box:
@@ -355,10 +449,10 @@ end
355
449
  ### Adapters
356
450
 
357
451
  Shale uses adapters for parsing and generating documents.
358
- By default Ruby's standard JSON parser is used for handing JSON documents, YAML for YAML and
452
+ By default Ruby's standard JSON parser is used for handling JSON documents, YAML for YAML and
359
453
  REXML for XML.
360
454
 
361
- You can change it by providing your own adapter. For JSON and YAML adapter must implement
455
+ You can change it by providing your own adapter. For JSON and YAML, adapter must implement
362
456
  `.load` and `.dump` class methods.
363
457
 
364
458
  ```ruby
@@ -369,15 +463,22 @@ Shale.json_adapter = MultiJson
369
463
  Shale.yaml_adapter = MyYamlAdapter
370
464
  ```
371
465
 
372
- For XML, other than REXML, Shale provides adapters for most popular Ruby XML parsers:
466
+ For XML, Shale provides adapters for most popular Ruby XML parsers:
373
467
 
374
468
  ```ruby
375
469
  require 'shale'
376
470
 
471
+ # REXML is used by default:
472
+
473
+ require 'shale/adapter/rexml'
474
+ Shale.xml_adapter = Shale::Adapter::REXML
475
+
476
+ # if you want to use Nokogiri:
477
+
377
478
  require 'shale/adapter/nokogiri'
378
479
  Shale.xml_adapter = Shale::Adapter::Nokogiri
379
480
 
380
- # or if you want to use Ox
481
+ # or if you want to use Ox:
381
482
 
382
483
  require 'shale/adapter/ox'
383
484
  Shale.xml_adapter = Shale::Adapter::Ox
data/lib/shale/error.rb CHANGED
@@ -30,4 +30,10 @@ module Shale
30
30
  super("'#{attribute}' default is not callable for #{record}.")
31
31
  end
32
32
  end
33
+
34
+ # Error for passing incorrect arguments to map functions
35
+ #
36
+ # @api private
37
+ class IncorrectMappingArgumentsError < StandardError
38
+ end
33
39
  end
data/lib/shale/mapper.rb CHANGED
@@ -5,7 +5,7 @@ require_relative 'error'
5
5
  require_relative 'utils'
6
6
  require_relative 'mapping/key_value'
7
7
  require_relative 'mapping/xml'
8
- require_relative 'type/complex'
8
+ require_relative 'type/composite'
9
9
 
10
10
  module Shale
11
11
  # Base class used for mapping
@@ -42,7 +42,7 @@ module Shale
42
42
  # person.to_json
43
43
  #
44
44
  # @api public
45
- class Mapper < Type::Complex
45
+ class Mapper < Type::Composite
46
46
  @attributes = {}
47
47
  @hash_mapping = Mapping::KeyValue.new
48
48
  @json_mapping = Mapping::KeyValue.new
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../error'
4
+
5
+ module Shale
6
+ module Mapping
7
+ class Base
8
+ private
9
+
10
+ # Validate correctness of argument passed to map functions
11
+ #
12
+ # @param [String] key
13
+ # @param [Symbol] to
14
+ # @param [Hash] using
15
+ #
16
+ # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
17
+ #
18
+ # @api private
19
+ def validate_arguments(key, to, using)
20
+ if to.nil? && using.nil?
21
+ msg = ":to or :using argument is required for mapping '#{key}'"
22
+ raise IncorrectMappingArgumentsError, msg
23
+ end
24
+
25
+ if !using.nil? && (using[:from].nil? || using[:to].nil?)
26
+ msg = ":using argument for mapping '#{key}' requires :to and :from keys"
27
+ raise IncorrectMappingArgumentsError, msg
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base'
4
+
3
5
  module Shale
4
6
  module Mapping
5
7
  # Mapping for key/value serialization formats (Hash/JSON/YAML)
6
8
  #
7
9
  # @api private
8
- class KeyValue
10
+ class KeyValue < Base
9
11
  # Return keys mapping hash
10
12
  #
11
13
  # @return [Hash]
@@ -17,6 +19,7 @@ module Shale
17
19
  #
18
20
  # @api private
19
21
  def initialize
22
+ super
20
23
  @keys = {}
21
24
  end
22
25
 
@@ -25,9 +28,12 @@ module Shale
25
28
  # @param [String] key Document's key
26
29
  # @param [Symbol] to Object's attribute
27
30
  #
31
+ # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
32
+ #
28
33
  # @api private
29
- def map(key, to:)
30
- @keys[key] = to
34
+ def map(key, to: nil, using: nil)
35
+ validate_arguments(key, to, using)
36
+ @keys[key] = to || using
31
37
  end
32
38
 
33
39
  # @api private
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base'
4
+
3
5
  module Shale
4
6
  module Mapping
5
- class Xml
7
+ class Xml < Base
6
8
  # Return elements mapping hash
7
9
  #
8
10
  # @return [Hash]
@@ -28,6 +30,7 @@ module Shale
28
30
  #
29
31
  # @api private
30
32
  def initialize
33
+ super
31
34
  @elements = {}
32
35
  @attributes = {}
33
36
  @content = nil
@@ -39,9 +42,12 @@ module Shale
39
42
  # @param [String] element Document's element
40
43
  # @param [Symbol] to Object's attribute
41
44
  #
45
+ # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
46
+ #
42
47
  # @api private
43
- def map_element(element, to:)
44
- @elements[element] = to
48
+ def map_element(element, to: nil, using: nil)
49
+ validate_arguments(element, to, using)
50
+ @elements[element] = to || using
45
51
  end
46
52
 
47
53
  # Map document's attribute to object's attribute
@@ -49,9 +55,12 @@ module Shale
49
55
  # @param [String] attribute Document's attribute
50
56
  # @param [Symbol] to Object's attribute
51
57
  #
58
+ # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
59
+ #
52
60
  # @api private
53
- def map_attribute(attribute, to:)
54
- @attributes[attribute] = to
61
+ def map_attribute(attribute, to: nil, using: nil)
62
+ validate_arguments(attribute, to, using)
63
+ @attributes[attribute] = to || using
55
64
  end
56
65
 
57
66
  # Map document's content to object's attribute
@@ -0,0 +1,326 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Shale
6
+ module Type
7
+ # Build composite object. Don't use it directly.
8
+ # It serves as a base type class for @see Shale::Mapper
9
+ #
10
+ # @api private
11
+ class Composite < Base
12
+ class << self
13
+ %i[hash json yaml].each do |format|
14
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
15
+ # Convert Hash to Object using Hash/JSON/YAML mapping
16
+ #
17
+ # @param [Hash] hash Hash to convert
18
+ #
19
+ # @return [Shale::Mapper]
20
+ #
21
+ # @api public
22
+ def out_of_#{format}(hash)
23
+ instance = new
24
+
25
+ mapping_keys = #{format}_mapping.keys
26
+
27
+ hash.each do |key, value|
28
+ mapping = mapping_keys[key]
29
+ next unless mapping
30
+
31
+ if mapping.is_a?(Hash)
32
+ instance.send(mapping[:from], value)
33
+ else
34
+ attribute = attributes[mapping]
35
+ next unless attribute
36
+
37
+ if value.nil?
38
+ instance.public_send("\#{attribute.name}=", nil)
39
+ next
40
+ end
41
+
42
+ if attribute.collection?
43
+ [*value].each do |val|
44
+ val = val ? attribute.type.out_of_#{format}(val) : val
45
+ instance.public_send(attribute.name) << attribute.type.cast(val)
46
+ end
47
+ else
48
+ val = attribute.type.out_of_#{format}(value)
49
+ instance.public_send("\#{attribute.name}=", val)
50
+ end
51
+ end
52
+ end
53
+
54
+ instance
55
+ end
56
+
57
+ # Convert Object to Hash using Hash/JSON/YAML mapping
58
+ #
59
+ # @param [Shale::Type::Base] instance Object to convert
60
+ #
61
+ # @return [Hash]
62
+ #
63
+ # @api public
64
+ def as_#{format}(instance)
65
+ hash = {}
66
+
67
+ instance.class.#{format}_mapping.keys.each do |key, attr|
68
+ if attr.is_a?(Hash)
69
+ hash[key] = instance.send(attr[:to])
70
+ else
71
+ attribute = instance.class.attributes[attr]
72
+ next unless attribute
73
+
74
+ value = instance.public_send(attribute.name)
75
+
76
+ if value.nil?
77
+ hash[key] = nil
78
+ next
79
+ end
80
+
81
+ if attribute.collection?
82
+ hash[key] = [*value].map { |v| v ? attribute.type.as_#{format}(v) : v }
83
+ else
84
+ hash[key] = attribute.type.as_#{format}(value)
85
+ end
86
+ end
87
+ end
88
+
89
+ hash
90
+ end
91
+ RUBY
92
+ end
93
+
94
+ alias from_hash out_of_hash
95
+
96
+ alias to_hash as_hash
97
+
98
+ # Convert JSON to Object
99
+ #
100
+ # @param [String] json JSON to convert
101
+ #
102
+ # @return [Shale::Mapper]
103
+ #
104
+ # @api public
105
+ def from_json(json)
106
+ out_of_json(Shale.json_adapter.load(json))
107
+ end
108
+
109
+ # Convert Object to JSON
110
+ #
111
+ # @param [Shale::Type::Base] instance Object to convert
112
+ #
113
+ # @return [String]
114
+ #
115
+ # @api public
116
+ def to_json(instance)
117
+ Shale.json_adapter.dump(as_json(instance))
118
+ end
119
+
120
+ # Convert YAML to Object
121
+ #
122
+ # @param [String] yaml YAML to convert
123
+ #
124
+ # @return [Shale::Mapper]
125
+ #
126
+ # @api public
127
+ def from_yaml(yaml)
128
+ out_of_yaml(Shale.yaml_adapter.load(yaml))
129
+ end
130
+
131
+ # Convert Object to YAML
132
+ #
133
+ # @param [Shale::Type::Base] instance Object to convert
134
+ #
135
+ # @return [String]
136
+ #
137
+ # @api public
138
+ def to_yaml(instance)
139
+ Shale.yaml_adapter.dump(as_yaml(instance))
140
+ end
141
+
142
+ # Convert XML document to Object
143
+ #
144
+ # @param [Shale::Adapter::<XML adapter>::Node] xml XML to convert
145
+ #
146
+ # @return [Shale::Mapper]
147
+ #
148
+ # @api public
149
+ def out_of_xml(element)
150
+ instance = new
151
+
152
+ element.attributes.each do |key, value|
153
+ mapping = xml_mapping.attributes[key.to_s]
154
+ next unless mapping
155
+
156
+ if mapping.is_a?(Hash)
157
+ instance.send(mapping[:from], value)
158
+ else
159
+ attribute = attributes[mapping]
160
+ next unless attribute
161
+
162
+ if attribute.collection?
163
+ instance.public_send(attribute.name) << attribute.type.cast(value)
164
+ else
165
+ instance.public_send("#{attribute.name}=", value)
166
+ end
167
+ end
168
+ end
169
+
170
+ if xml_mapping.content
171
+ attribute = attributes[xml_mapping.content]
172
+
173
+ if attribute
174
+ instance.public_send("#{attribute.name}=", attribute.type.out_of_xml(element))
175
+ end
176
+ end
177
+
178
+ element.children.each do |node|
179
+ mapping = xml_mapping.elements[node.name]
180
+ next unless mapping
181
+
182
+ if mapping.is_a?(Hash)
183
+ instance.send(mapping[:from], node)
184
+ else
185
+ attribute = attributes[mapping]
186
+ next unless attribute
187
+
188
+ if attribute.collection?
189
+ value = attribute.type.out_of_xml(node)
190
+ instance.public_send(attribute.name) << attribute.type.cast(value)
191
+ else
192
+ instance.public_send("#{attribute.name}=", attribute.type.out_of_xml(node))
193
+ end
194
+ end
195
+ end
196
+
197
+ instance
198
+ end
199
+
200
+ # Convert XML to Object
201
+ #
202
+ # @param [String] xml XML to convert
203
+ #
204
+ # @return [Shale::Mapper]
205
+ #
206
+ # @api public
207
+ def from_xml(xml)
208
+ out_of_xml(Shale.xml_adapter.load(xml))
209
+ end
210
+
211
+ # Convert Object to XML document
212
+ #
213
+ # @param [Shale::Type::Base] instance Object to convert
214
+ # @param [String, nil] node_name XML node name
215
+ # @param [Shale::Adapter::<xml adapter>::Document, nil] doc Object to convert
216
+ #
217
+ # @return [::REXML::Document, ::Nokogiri::Document, ::Ox::Document]
218
+ #
219
+ # @api public
220
+ def as_xml(instance, node_name = nil, doc = nil)
221
+ unless doc
222
+ doc = Shale.xml_adapter.create_document
223
+ doc.add_element(doc.doc, as_xml(instance, instance.class.xml_mapping.root, doc))
224
+ return doc.doc
225
+ end
226
+
227
+ element = doc.create_element(node_name)
228
+
229
+ xml_mapping.attributes.each do |xml_attr, obj_attr|
230
+ if obj_attr.is_a?(Hash)
231
+ instance.send(obj_attr[:to], element, doc)
232
+ else
233
+ attribute = instance.class.attributes[obj_attr]
234
+ next unless attribute
235
+
236
+ value = instance.public_send(attribute.name)
237
+
238
+ if value && !value.empty?
239
+ doc.add_attribute(element, xml_attr, value)
240
+ end
241
+ end
242
+ end
243
+
244
+ if xml_mapping.content
245
+ attribute = instance.class.attributes[xml_mapping.content]
246
+
247
+ if attribute
248
+ value = instance.public_send(attribute.name)
249
+ doc.add_text(element, value.to_s) if value
250
+ end
251
+ end
252
+
253
+ xml_mapping.elements.each do |xml_name, obj_attr|
254
+ if obj_attr.is_a?(Hash)
255
+ instance.send(obj_attr[:to], element, doc)
256
+ else
257
+ attribute = instance.class.attributes[obj_attr]
258
+ next unless attribute
259
+
260
+ value = instance.public_send(attribute.name)
261
+ next if value.nil?
262
+
263
+ if attribute.collection?
264
+ [*value].each do |v|
265
+ next if v.nil?
266
+ doc.add_element(element, attribute.type.as_xml(v, xml_name, doc))
267
+ end
268
+ else
269
+ doc.add_element(element, attribute.type.as_xml(value, xml_name, doc))
270
+ end
271
+ end
272
+ end
273
+
274
+ element
275
+ end
276
+
277
+ # Convert Object to XML
278
+ #
279
+ # @param [Shale::Type::Base] instance Object to convert
280
+ #
281
+ # @return [String]
282
+ #
283
+ # @api public
284
+ def to_xml(instance)
285
+ Shale.xml_adapter.dump(as_xml(instance))
286
+ end
287
+ end
288
+
289
+ # Convert Object to Hash
290
+ #
291
+ # @return [Hash]
292
+ #
293
+ # @api public
294
+ def to_hash
295
+ self.class.to_hash(self)
296
+ end
297
+
298
+ # Convert Object to JSON
299
+ #
300
+ # @return [String]
301
+ #
302
+ # @api public
303
+ def to_json
304
+ self.class.to_json(self)
305
+ end
306
+
307
+ # Convert Object to YAML
308
+ #
309
+ # @return [String]
310
+ #
311
+ # @api public
312
+ def to_yaml
313
+ self.class.to_yaml(self)
314
+ end
315
+
316
+ # Convert Object to XML
317
+ #
318
+ # @return [String]
319
+ #
320
+ # @api public
321
+ def to_xml
322
+ self.class.to_xml(self)
323
+ end
324
+ end
325
+ end
326
+ end
data/lib/shale/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Shale
4
4
  # @api private
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shale
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kamil Giszczak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-30 00:00:00.000000000 Z
11
+ date: 2022-01-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby object mapper and serializer for XML, JSON and YAML.
14
14
  email:
@@ -28,11 +28,12 @@ files:
28
28
  - lib/shale/attribute.rb
29
29
  - lib/shale/error.rb
30
30
  - lib/shale/mapper.rb
31
+ - lib/shale/mapping/base.rb
31
32
  - lib/shale/mapping/key_value.rb
32
33
  - lib/shale/mapping/xml.rb
33
34
  - lib/shale/type/base.rb
34
35
  - lib/shale/type/boolean.rb
35
- - lib/shale/type/complex.rb
36
+ - lib/shale/type/composite.rb
36
37
  - lib/shale/type/date.rb
37
38
  - lib/shale/type/float.rb
38
39
  - lib/shale/type/integer.rb
@@ -1,427 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'base'
4
-
5
- module Shale
6
- module Type
7
- # Build complex object. Don't use it directly.
8
- # It serves as a base type class for @see Shale::Mapper
9
- #
10
- # @api private
11
- class Complex < Base
12
- class << self
13
- # Convert Hash to Object
14
- #
15
- # @param [Hash] hash Hash to convert
16
- #
17
- # @return [Shale::Mapper]
18
- #
19
- # @api public
20
- def out_of_hash(hash)
21
- instance = new
22
-
23
- hash.each do |key, value|
24
- mapping = hash_mapping.keys[key]
25
- next unless mapping
26
-
27
- attribute = attributes[mapping]
28
- next unless attribute
29
-
30
- if value.nil?
31
- instance.public_send("#{attribute.name}=", nil)
32
- next
33
- end
34
-
35
- if attribute.collection?
36
- [*value].each do |val|
37
- val = val ? attribute.type.out_of_hash(val) : val
38
- instance.public_send(attribute.name) << attribute.type.cast(val)
39
- end
40
- else
41
- instance.public_send("#{attribute.name}=", attribute.type.out_of_hash(value))
42
- end
43
- end
44
-
45
- instance
46
- end
47
-
48
- alias from_hash out_of_hash
49
-
50
- # Convert Object to Hash
51
- #
52
- # @param [Shale::Type::Base] instance Object to convert
53
- #
54
- # @return [Hash]
55
- #
56
- # @api public
57
- def as_hash(instance)
58
- hash = {}
59
-
60
- instance.class.hash_mapping.keys.each do |key, attr|
61
- attribute = instance.class.attributes[attr]
62
- next unless attribute
63
-
64
- value = instance.public_send(attribute.name)
65
-
66
- if value.nil?
67
- hash[key] = nil
68
- next
69
- end
70
-
71
- if attribute.collection?
72
- hash[key] = [*value].map { |v| v ? attribute.type.as_hash(v) : v }
73
- else
74
- hash[key] = attribute.type.as_hash(value)
75
- end
76
- end
77
-
78
- hash
79
- end
80
-
81
- alias to_hash as_hash
82
-
83
- # Convert JSON document to Object
84
- #
85
- # @param [Hash] hash JSON document to convert
86
- #
87
- # @return [Shale::Mapper]
88
- #
89
- # @api public
90
- def out_of_json(hash)
91
- instance = new
92
-
93
- hash.each do |key, value|
94
- mapping = json_mapping.keys[key]
95
- next unless mapping
96
-
97
- attribute = attributes[mapping]
98
- next unless attribute
99
-
100
- if value.nil?
101
- instance.public_send("#{attribute.name}=", nil)
102
- next
103
- end
104
-
105
- if attribute.collection?
106
- [*value].each do |val|
107
- val = val ? attribute.type.out_of_json(val) : val
108
- instance.public_send(attribute.name) << attribute.type.cast(val)
109
- end
110
- else
111
- instance.public_send("#{attribute.name}=", attribute.type.out_of_json(value))
112
- end
113
- end
114
-
115
- instance
116
- end
117
-
118
- # Convert JSON to Object
119
- #
120
- # @param [String] json JSON to convert
121
- #
122
- # @return [Shale::Mapper]
123
- #
124
- # @api public
125
- def from_json(json)
126
- out_of_json(Shale.json_adapter.load(json))
127
- end
128
-
129
- # Convert Object to JSON document
130
- #
131
- # @param [Shale::Type::Base] instance Object to convert
132
- #
133
- # @return [Hash]
134
- #
135
- # @api public
136
- def as_json(instance)
137
- hash = {}
138
-
139
- instance.class.json_mapping.keys.each do |key, attr|
140
- attribute = instance.class.attributes[attr]
141
- next unless attribute
142
-
143
- value = instance.public_send(attribute.name)
144
-
145
- if value.nil?
146
- hash[key] = nil
147
- next
148
- end
149
-
150
- if attribute.collection?
151
- hash[key] = [*value].map { |v| v ? attribute.type.as_json(v) : v }
152
- else
153
- hash[key] = attribute.type.as_json(value)
154
- end
155
- end
156
-
157
- hash
158
- end
159
-
160
- # Convert Object to JSON
161
- #
162
- # @param [Shale::Type::Base] instance Object to convert
163
- #
164
- # @return [String]
165
- #
166
- # @api public
167
- def to_json(instance)
168
- Shale.json_adapter.dump(as_json(instance))
169
- end
170
-
171
- # Convert YAML document to Object
172
- #
173
- # @param [Hash] hash YAML document to convert
174
- #
175
- # @return [Shale::Mapper]
176
- #
177
- # @api public
178
- def out_of_yaml(hash)
179
- instance = new
180
-
181
- hash.each do |key, value|
182
- mapping = yaml_mapping.keys[key]
183
- next unless mapping
184
-
185
- attribute = attributes[mapping]
186
- next unless attribute
187
-
188
- if value.nil?
189
- instance.public_send("#{attribute.name}=", nil)
190
- next
191
- end
192
-
193
- if attribute.collection?
194
- [*value].each do |val|
195
- val = val ? attribute.type.out_of_yaml(val) : val
196
- instance.public_send(attribute.name) << attribute.type.cast(val)
197
- end
198
- else
199
- instance.public_send("#{attribute.name}=", attribute.type.out_of_yaml(value))
200
- end
201
- end
202
-
203
- instance
204
- end
205
-
206
- # Convert YAML to Object
207
- #
208
- # @param [String] yaml YAML to convert
209
- #
210
- # @return [Shale::Mapper]
211
- #
212
- # @api public
213
- def from_yaml(yaml)
214
- out_of_yaml(Shale.yaml_adapter.load(yaml))
215
- end
216
-
217
- # Convert Object to YAML document
218
- #
219
- # @param [Shale::Type::Base] instance Object to convert
220
- #
221
- # @return [Hash]
222
- #
223
- # @api public
224
- def as_yaml(instance)
225
- hash = {}
226
-
227
- instance.class.yaml_mapping.keys.each do |key, attr|
228
- attribute = instance.class.attributes[attr]
229
- next unless attribute
230
-
231
- value = instance.public_send(attribute.name)
232
-
233
- if value.nil?
234
- hash[key] = nil
235
- next
236
- end
237
-
238
- if attribute.collection?
239
- hash[key] = [*value].map { |v| v ? attribute.type.as_yaml(v) : v }
240
- else
241
- hash[key] = attribute.type.as_yaml(value)
242
- end
243
- end
244
-
245
- hash
246
- end
247
-
248
- # Convert Object to YAML
249
- #
250
- # @param [Shale::Type::Base] instance Object to convert
251
- #
252
- # @return [String]
253
- #
254
- # @api public
255
- def to_yaml(instance)
256
- Shale.yaml_adapter.dump(as_yaml(instance))
257
- end
258
-
259
- # Convert XML document to Object
260
- #
261
- # @param [Shale::Adapter::<XML adapter>::Node] xml XML to convert
262
- #
263
- # @return [Shale::Mapper]
264
- #
265
- # @api public
266
- def out_of_xml(element)
267
- instance = new
268
-
269
- element.attributes.each do |key, value|
270
- mapping = xml_mapping.attributes[key.to_s]
271
- next unless mapping
272
-
273
- attribute = attributes[mapping]
274
- next unless attribute
275
-
276
- if attribute.collection?
277
- instance.public_send(attribute.name) << attribute.type.cast(value)
278
- else
279
- instance.public_send("#{attribute.name}=", value)
280
- end
281
- end
282
-
283
- if xml_mapping.content
284
- attribute = attributes[xml_mapping.content]
285
-
286
- if attribute
287
- instance.public_send("#{attribute.name}=", attribute.type.out_of_xml(element))
288
- end
289
- end
290
-
291
- element.children.each do |node|
292
- mapping = xml_mapping.elements[node.name]
293
- next unless mapping
294
-
295
- attribute = attributes[mapping]
296
- next unless attribute
297
-
298
- if attribute.collection?
299
- value = attribute.type.out_of_xml(node)
300
- instance.public_send(attribute.name) << attribute.type.cast(value)
301
- else
302
- instance.public_send("#{attribute.name}=", attribute.type.out_of_xml(node))
303
- end
304
- end
305
-
306
- instance
307
- end
308
-
309
- # Convert XML to Object
310
- #
311
- # @param [String] xml XML to convert
312
- #
313
- # @return [Shale::Mapper]
314
- #
315
- # @api public
316
- def from_xml(xml)
317
- out_of_xml(Shale.xml_adapter.load(xml))
318
- end
319
-
320
- # Convert Object to XML document
321
- #
322
- # @param [Shale::Type::Base] instance Object to convert
323
- # @param [String, nil] node_name XML node name
324
- # @param [Shale::Adapter::<xml adapter>::Document, nil] doc Object to convert
325
- #
326
- # @return [::REXML::Document, ::Nokogiri::Document, ::Ox::Document]
327
- #
328
- # @api public
329
- def as_xml(instance, node_name = nil, doc = nil)
330
- unless doc
331
- doc = Shale.xml_adapter.create_document
332
- doc.add_element(doc.doc, as_xml(instance, instance.class.xml_mapping.root, doc))
333
- return doc.doc
334
- end
335
-
336
- element = doc.create_element(node_name)
337
-
338
- xml_mapping.attributes.each do |xml_attr, obj_attr|
339
- attribute = instance.class.attributes[obj_attr]
340
- next unless attribute
341
-
342
- value = instance.public_send(attribute.name)
343
-
344
- if value && !value.empty?
345
- doc.add_attribute(element, xml_attr, value)
346
- end
347
- end
348
-
349
- if xml_mapping.content
350
- attribute = instance.class.attributes[xml_mapping.content]
351
-
352
- if attribute
353
- value = instance.public_send(attribute.name)
354
- doc.add_text(element, value.to_s) if value
355
- end
356
- end
357
-
358
- xml_mapping.elements.each do |xml_name, obj_attr|
359
- attribute = instance.class.attributes[obj_attr]
360
- next unless attribute
361
-
362
- value = instance.public_send(attribute.name)
363
- next if value.nil?
364
-
365
- if attribute.collection?
366
- [*value].each do |v|
367
- next if v.nil?
368
- doc.add_element(element, attribute.type.as_xml(v, xml_name, doc))
369
- end
370
- else
371
- doc.add_element(element, attribute.type.as_xml(value, xml_name, doc))
372
- end
373
- end
374
-
375
- element
376
- end
377
-
378
- # Convert Object to XML
379
- #
380
- # @param [Shale::Type::Base] instance Object to convert
381
- #
382
- # @return [String]
383
- #
384
- # @api public
385
- def to_xml(instance)
386
- Shale.xml_adapter.dump(as_xml(instance))
387
- end
388
- end
389
-
390
- # Convert Object to Hash
391
- #
392
- # @return [Hash]
393
- #
394
- # @api public
395
- def to_hash
396
- self.class.to_hash(self)
397
- end
398
-
399
- # Convert Object to JSON
400
- #
401
- # @return [String]
402
- #
403
- # @api public
404
- def to_json
405
- self.class.to_json(self)
406
- end
407
-
408
- # Convert Object to YAML
409
- #
410
- # @return [String]
411
- #
412
- # @api public
413
- def to_yaml
414
- self.class.to_yaml(self)
415
- end
416
-
417
- # Convert Object to XML
418
- #
419
- # @return [String]
420
- #
421
- # @api public
422
- def to_xml
423
- self.class.to_xml(self)
424
- end
425
- end
426
- end
427
- end