shale 0.6.0 → 0.7.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: 243b99a073ac189f66ae0fbfedd94a5715399de731d0e0fd1d970dad749d27a0
4
- data.tar.gz: d2f57de2427574d710d6a6fe9ba35868fd162214ce8aa2092ddfb3fe1f5e7c3d
3
+ metadata.gz: 5f3c860cf358f78476f8f22a86006694c3d9fd6cb554c945d7364bbf8bb70815
4
+ data.tar.gz: 5eba158f49fdcfb3012f2e4dcf804fdc4c8fe1e0c37e8e3dbb37972aa469c8bb
5
5
  SHA512:
6
- metadata.gz: 9de1be5eccbb647f05b6573d783491f858fb56614f09c7c625e7fa922c7b89b5d38863a57defbe506d42f0b542b326e4ca61ecf308b94f7b35b4056751e0299e
7
- data.tar.gz: a1e29fc097c130c55e45837af198e3cd4f7d3ad6a94b17d032eb31b8065f7ea013682836256d12127480965ed3733079e5badd7a8b0060e3bc60f00734fcc215
6
+ metadata.gz: b002a6837137fa024788e50b2b408ebd7d80f1fd73c7bf1182a2d0b0ab784a07cbda08147af703c4ded76efcb086958049c3d82573e5f885d1b1745f9312eb0c
7
+ data.tar.gz: e66ba2657a540ad6e9cebd98cf4a61a9ae4b31c0fc6ba1e8085493cc727eda48e2cb87515d399a2524cac8bb4d92932f57dd548afa2ba1d8fb5b97bb5c8ef1a7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.7.0] - [2022-08-09]
2
+
3
+ ### Added
4
+ - `only: []` and `except: []` options that allow to controll what attributes are rendered/parsed
5
+ - `render_nil: true` option that allows to render nil values
6
+ - Allow to pass a context object to extractor/generator methods
7
+
8
+ ### Changed
9
+ - Pass whole document to methods for JSON/YAML/TOML so its behavior is consistent with XML mapping
10
+ - Convert splat arguments to keyword arguments
11
+ - RSpec: enable random spec execution and warnings
12
+
1
13
  ## [0.6.0] - 2022-07-05
2
14
 
3
15
  ### Added
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021 TODO: Write your name
3
+ Copyright (c) 2021 Kamil Giszczak
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -12,7 +12,7 @@ Documentation with interactive examples is available at [Shale website](https://
12
12
  * Convert Ruby data model to JSON, YAML, TOML and XML
13
13
  * Generate JSON and XML Schema from Ruby models
14
14
  * Compile JSON and XML Schema into Ruby models
15
- * Out of the box support for JSON, YAML, toml-rb, Nokogiri, REXML and Ox parsers
15
+ * Out of the box support for JSON, YAML, Tomlib, toml-rb, Nokogiri, REXML and Ox parsers
16
16
  * Support for custom adapters
17
17
 
18
18
  ## Installation
@@ -57,8 +57,9 @@ $ gem install shale
57
57
  * [Mapping Hash keys to object attributes](#mapping-hash-keys-to-object-attributes)
58
58
  * [Mapping XML elements and attributes to object attributes](#mapping-xml-elements-and-attributes-to-object-attributes)
59
59
  * [Using XML namespaces](#using-xml-namespaces)
60
+ * [Rendering nil values](#rendering-nil-values)
60
61
  * [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)
61
- * [Pretty printing and XML declaration](#pretty-printing-and-xml-declaration)
62
+ * [Additional options](#additional-options)
62
63
  * [Using custom models](#using-custom-models)
63
64
  * [Supported types](#supported-types)
64
65
  * [Writing your own type](#writing-your-own-type)
@@ -202,18 +203,23 @@ person.to_yaml
202
203
  ### Converting TOML to object
203
204
 
204
205
  To use TOML with Shale you have to set adapter you want to use.
205
- Shale comes with adapter for [toml-rb](https://github.com/emancu/toml-rb).
206
+ Out of the box Shale suports [Tomlib](https://github.com/kgiszczak/tomlib).
207
+ It also comes with adapter for [toml-rb](https://github.com/emancu/toml-rb) if you prefer that.
206
208
  For details see [Adapters](#adapters) section.
207
209
 
208
- To set it, first make sure toml-rb gem is installed:
210
+ To set it, first make sure Tomlib gem is installed:
209
211
 
210
212
  ```
211
- $ gem install shale
213
+ $ gem install tomlib
212
214
  ```
213
215
 
214
216
  then setup adapter:
215
217
 
216
218
  ```ruby
219
+ require 'tomlib'
220
+ Shale.toml_adapter = Tomlib
221
+
222
+ # Alternatively if you'd like to use toml-rb, use:
217
223
  require 'shale/adapter/toml_rb'
218
224
  Shale.toml_adapter = Shale::Adapter::TomlRB
219
225
  ```
@@ -241,18 +247,16 @@ person.to_toml
241
247
 
242
248
  # =>
243
249
  #
244
- # ---
245
- # first_name: John
246
- # last_name: Doe
247
- # age: 50
248
- # married: false
249
- # hobbies:
250
- # - Singing
251
- # - Dancing
252
- # address:
253
- # city: London
254
- # street: Oxford Street
255
- # zip: E1 6AN
250
+ # first_name = "John"
251
+ # last_name = "Doe"
252
+ # age = 50
253
+ # married = false
254
+ # hobbies = [ "Singing", "Dancing" ]
255
+ #
256
+ # [address]
257
+ # city = "London"
258
+ # street = "Oxford Street"
259
+ # zip = "E1 6AN"
256
260
  ```
257
261
 
258
262
  ### Converting Hash to object
@@ -555,6 +559,52 @@ person = Person.from_xml(<<~DATA)
555
559
  DATA
556
560
  ```
557
561
 
562
+ ### Rendering nil values
563
+
564
+ By default elements with `nil` value are not rendered. You can change this behavior
565
+ by using `render_nil: true` on a mapping.
566
+
567
+ ```ruby
568
+ class Person < Shale::Mapper
569
+ attribute :first_name, Shale::Type::String
570
+ attribute :last_name, Shale::Type::String
571
+ attribute :age, Shale::Type::Integer
572
+
573
+ json do
574
+ map 'first_name', to: :first_name, render_nil: true
575
+ map 'last_name', to: :last_name, render_nil: false
576
+ map 'age', to: :age, render_nil: true
577
+ end
578
+
579
+ xml do
580
+ root 'person'
581
+
582
+ map_element 'first_name', to: :first_name, render_nil: true
583
+ map_element 'last_name', to: :last_name, render_nil: false
584
+ map_attribute 'age', to: :age, render_nil: true
585
+ end
586
+ end
587
+
588
+ person = Person.new(first_name: nil, last_name: nil, age: nil)
589
+
590
+ puts person.to_json(pretty: true)
591
+
592
+ # =>
593
+ #
594
+ # {
595
+ # "first_name": null,
596
+ # "age": "null"
597
+ # }
598
+
599
+ puts person.to_xml(pretty: true)
600
+
601
+ # =>
602
+ #
603
+ # <person age="">
604
+ # <first_name/>
605
+ # </person>
606
+ ```
607
+
558
608
  ### Using methods to extract and generate data
559
609
 
560
610
  If you need full controll over extracting and generating data from/to document,
@@ -582,8 +632,8 @@ class Person < Shale::Mapper
582
632
  model.hobbies = value.split(',').map(&:strip)
583
633
  end
584
634
 
585
- def hobbies_to_json(model)
586
- model.hobbies.join(', ')
635
+ def hobbies_to_json(model, doc)
636
+ doc['hobbies'] = model.hobbies.join(', ')
587
637
  end
588
638
 
589
639
  def address_from_json(model, value)
@@ -591,8 +641,8 @@ class Person < Shale::Mapper
591
641
  model.city = value['city']
592
642
  end
593
643
 
594
- def address_to_json(model)
595
- { 'street' => model.street, 'city' => model.city }
644
+ def address_to_json(model, doc)
645
+ doc['address'] = { 'street' => model.street, 'city' => model.city }
596
646
  end
597
647
 
598
648
  def hobbies_from_xml(model, value)
@@ -649,12 +699,100 @@ DATA
649
699
  # @city="London">
650
700
  ```
651
701
 
652
- ### Pretty printing and XML declaration
702
+ You can also pass a `context` object that will be available in extractor/generator methods:
703
+
704
+ ```ruby
705
+ class Person < Shale::Mapper
706
+ attribute :password, Shale::Type::String
653
707
 
654
- If you need formatted output you can pass `:pretty` parameter to `#to_json` and `#to_xml`
708
+ json do
709
+ map 'password', using: { from: :password_from_json, to: :password_to_json }
710
+ end
711
+
712
+ def password_from_json(model, value, context)
713
+ if context.admin?
714
+ model.password = value
715
+ else
716
+ model.password = '*****'
717
+ end
718
+ end
719
+
720
+ def password_to_json(model, doc, context)
721
+ if context.admin?
722
+ doc['password'] = model.password
723
+ else
724
+ doc['password'] = '*****'
725
+ end
726
+ end
727
+ end
728
+
729
+ Person.new(password: 'secret').to_json(context: current_user)
730
+ ```
731
+
732
+ ### Additional options
733
+
734
+ You can control which attributes to render and parse by
735
+ using `only: []` and `except: []` parameters.
655
736
 
656
737
  ```ruby
657
- person.to_json(:pretty)
738
+ # e.g. if you have this model graph:
739
+ person = Person.new(
740
+ first_name: 'John'
741
+ last_name: 'Doe',
742
+ address: Address.new(city: 'London', street: 'Oxford Street')
743
+ )
744
+
745
+ # if you want to render only `first_name` and `address.city` do:
746
+ person.to_json(only: [:first_name, address: [:city]], pretty: true)
747
+
748
+ # =>
749
+ #
750
+ # {
751
+ # "first_name": "John",
752
+ # "address": {
753
+ # "city": "London"
754
+ # }
755
+ # }
756
+
757
+ # and if you don't need an address you can do:
758
+ person.to_json(except: [:address], pretty: true)
759
+
760
+ # =>
761
+ #
762
+ # {
763
+ # "first_name": "John",
764
+ # "last_name": "Doe"
765
+ # }
766
+ ```
767
+
768
+ It works the same for parsing:
769
+
770
+ ```ruby
771
+ # e.g. if you want to parse only `address.city` do:
772
+ Person.from_json(doc, only: [address: [:city]])
773
+
774
+ # =>
775
+ #
776
+ # #<Person:0x0000000113d7a488
777
+ # @first_name=nil,
778
+ # @last_name=nil,
779
+ # @address=#<Address:0x0000000113d7a140 @street=nil, @city="London">>
780
+
781
+ # and if you don't need an `address`:
782
+ Person.from_json(doc, except: [:address])
783
+
784
+ # =>
785
+ #
786
+ # #<Person:0x0000000113d7a488
787
+ # @first_name="John",
788
+ # @last_name="Doe",
789
+ # @address=nil>
790
+ ```
791
+
792
+ If you need formatted output you can pass `pretty: true` parameter to `#to_json` and `#to_xml`
793
+
794
+ ```ruby
795
+ person.to_json(pretty: true)
658
796
 
659
797
  # =>
660
798
  #
@@ -666,10 +804,10 @@ person.to_json(:pretty)
666
804
  # }
667
805
  ```
668
806
 
669
- You can also add an XML declaration by passing `:declaration` to `#to_xml`
807
+ You can also add an XML declaration by passing `declaration: true` to `#to_xml`
670
808
 
671
809
  ```ruby
672
- person.to_xml(:pretty, :declaration)
810
+ person.to_xml(pretty: true, declaration: true)
673
811
 
674
812
  # =>
675
813
  #
@@ -726,7 +864,7 @@ DATA
726
864
  # @last_name="Doe",
727
865
  # @address=#<Address:0x0000000113d7a140 @street="Oxford Street", @city="London">>
728
866
 
729
- PersonMapper.to_json(person, :pretty)
867
+ PersonMapper.to_json(person, pretty: true)
730
868
 
731
869
  # =>
732
870
  #
@@ -781,12 +919,17 @@ Shale.json_adapter = MultiJson
781
919
  Shale.yaml_adapter = MyYamlAdapter
782
920
  ```
783
921
 
784
- To handle TOML documents you have to set TOML adapter.
785
- Shale provides adapter for `toml-rb` TOML parser:
922
+ To handle TOML documents you have to set TOML adapter. Out of the box `Tomlib` is supported.
923
+ Shale also provides adapter for `toml-rb` parser:
786
924
 
787
925
  ```ruby
788
926
  require 'shale'
789
927
 
928
+ # if you want to use Tomlib
929
+ require 'tomlib'
930
+ Shale.toml_adapter = Tomlib
931
+
932
+ # if you want to use toml-rb
790
933
  require 'shale/adapter/toml_rb'
791
934
  Shale.toml_adapter = Shale::Adapter::TomlRB
792
935
  ```
@@ -22,13 +22,13 @@ module Shale
22
22
  # Serialize Hash into JSON
23
23
  #
24
24
  # @param [Hash] obj Hash object
25
- # @param [Array<Symbol>] options
25
+ # @param [true, false] pretty
26
26
  #
27
27
  # @return [String]
28
28
  #
29
29
  # @api private
30
- def self.dump(obj, *options)
31
- if options.include?(:pretty)
30
+ def self.dump(obj, pretty: false)
31
+ if pretty
32
32
  ::JSON.pretty_generate(obj)
33
33
  else
34
34
  ::JSON.generate(obj)
@@ -36,25 +36,26 @@ module Shale
36
36
  # Serialize Nokogiri document into XML
37
37
  #
38
38
  # @param [::Nokogiri::XML::Document] doc Nokogiri document
39
- # @param [Array<Symbol>] options
39
+ # @param [true, false] pretty
40
+ # @param [true, false] declaration
40
41
  #
41
42
  # @return [String]
42
43
  #
43
44
  # @api private
44
- def self.dump(doc, *options)
45
+ def self.dump(doc, pretty: false, declaration: false)
45
46
  save_with = ::Nokogiri::XML::Node::SaveOptions::AS_XML
46
47
 
47
- if options.include?(:pretty)
48
+ if pretty
48
49
  save_with |= ::Nokogiri::XML::Node::SaveOptions::FORMAT
49
50
  end
50
51
 
51
- unless options.include?(:declaration)
52
+ unless declaration
52
53
  save_with |= ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
53
54
  end
54
55
 
55
56
  result = doc.to_xml(save_with: save_with)
56
57
 
57
- unless options.include?(:pretty)
58
+ unless pretty
58
59
  result = result.sub(/\n/, '')
59
60
  end
60
61
 
@@ -30,19 +30,20 @@ module Shale
30
30
  # Serialize Ox document into XML
31
31
  #
32
32
  # @param [::Ox::Document, ::Ox::Element] doc Ox document
33
- # @param [Array<Symbol>] options
33
+ # @param [true, false] pretty
34
+ # @param [true, false] declaration
34
35
  #
35
36
  # @return [String]
36
37
  #
37
38
  # @api private
38
- def self.dump(doc, *options)
39
+ def self.dump(doc, pretty: false, declaration: false)
39
40
  opts = { indent: -1, with_xml: false }
40
41
 
41
- if options.include?(:pretty)
42
+ if pretty
42
43
  opts[:indent] = 2
43
44
  end
44
45
 
45
- if options.include?(:declaration)
46
+ if declaration
46
47
  doc[:version] = '1.0'
47
48
  opts[:with_xml] = true
48
49
  end
@@ -70,7 +70,7 @@ module Shale
70
70
  #
71
71
  # @api private
72
72
  def add_attribute(element, name, value)
73
- element.add_attribute(name, value)
73
+ element.add_attribute(name, value || '')
74
74
  end
75
75
 
76
76
  # Add child element to REXML element
@@ -31,19 +31,20 @@ module Shale
31
31
  # Serialize REXML document into XML
32
32
  #
33
33
  # @param [::REXML::Document] doc REXML document
34
- # @param [Array<Symbol>] options
34
+ # @param [true, false] pretty
35
+ # @param [true, false] declaration
35
36
  #
36
37
  # @return [String]
37
38
  #
38
39
  # @api private
39
- def self.dump(doc, *options)
40
- if options.include?(:declaration)
40
+ def self.dump(doc, pretty: false, declaration: false)
41
+ if declaration
41
42
  doc.add(::REXML::XMLDecl.new)
42
43
  end
43
44
 
44
45
  io = StringIO.new
45
46
 
46
- if options.include?(:pretty)
47
+ if pretty
47
48
  formatter = ::REXML::Formatters::Pretty.new
48
49
  formatter.compact = true
49
50
  else
data/lib/shale/error.rb CHANGED
@@ -7,6 +7,11 @@ module Shale
7
7
  TOML Adapter is not set.
8
8
  To use Shale with TOML documents you have to install parser and set adapter.
9
9
 
10
+ # To use Tomlib:
11
+ # Make sure tomlib is installed eg. execute: gem install tomlib
12
+ Shale.toml_adapter = Tomlib
13
+
14
+ # To use toml-rb:
10
15
  # Make sure toml-rb is installed eg. execute: gem install toml-rb
11
16
  require 'shale/adapter/toml_rb'
12
17
  Shale.toml_adapter = Shale::Adapter::TomlRB
@@ -18,16 +23,16 @@ module Shale
18
23
  XML Adapter is not set.
19
24
  To use Shale with XML documents you have to install parser and set adapter.
20
25
 
21
- To use REXML:
26
+ # To use REXML:
22
27
  require 'shale/adapter/rexml'
23
28
  Shale.xml_adapter = Shale::Adapter::REXML
24
29
 
25
- To use Nokogiri:
30
+ # To use Nokogiri:
26
31
  # Make sure Nokogiri is installed eg. execute: gem install nokogiri
27
32
  require 'shale/adapter/nokogiri'
28
33
  Shale.xml_adapter = Shale::Adapter::Nokogiri
29
34
 
30
- To use OX:
35
+ # To use OX:
31
36
  # Make sure Ox is installed eg. execute: gem install ox
32
37
  require 'shale/adapter/ox'
33
38
  Shale.xml_adapter = Shale::Adapter::Ox
@@ -40,17 +40,28 @@ module Shale
40
40
  # @param [String] name
41
41
  # @param [Symbol, nil] attribute
42
42
  # @param [Hash, nil] methods
43
+ # @param [true, false] render_nil
43
44
  #
44
45
  # @api private
45
- def initialize(name:, attribute:, methods:)
46
+ def initialize(name:, attribute:, methods:, render_nil:)
46
47
  @name = name
47
48
  @attribute = attribute
49
+ @render_nil = render_nil
48
50
 
49
51
  if methods
50
52
  @method_from = methods[:from]
51
53
  @method_to = methods[:to]
52
54
  end
53
55
  end
56
+
57
+ # Check render_nil
58
+ #
59
+ # @return [true, false]
60
+ #
61
+ # @api private
62
+ def render_nil?
63
+ @render_nil == true
64
+ end
54
65
  end
55
66
  end
56
67
  end
@@ -30,10 +30,11 @@ module Shale
30
30
  # @param [Hash, nil] methods
31
31
  # @param [Shale::Mapping::XmlNamespace] namespace
32
32
  # @param [true, false] cdata
33
+ # @param [true, false] render_nil
33
34
  #
34
35
  # @api private
35
- def initialize(name:, attribute:, methods:, namespace:, cdata:)
36
- super(name: name, attribute: attribute, methods: methods)
36
+ def initialize(name:, attribute:, methods:, namespace:, cdata:, render_nil:)
37
+ super(name: name, attribute: attribute, methods: methods, render_nil: render_nil)
37
38
  @namespace = namespace
38
39
  @cdata = cdata
39
40
  end
@@ -30,13 +30,19 @@ module Shale
30
30
  # @param [String] key Document's key
31
31
  # @param [Symbol, nil] to Object's attribute
32
32
  # @param [Hash, nil] using
33
+ # @param [true, false] render_nil
33
34
  #
34
35
  # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
35
36
  #
36
37
  # @api private
37
- def map(key, to: nil, using: nil)
38
+ def map(key, to: nil, using: nil, render_nil: false)
38
39
  Validator.validate_arguments(key, to, using)
39
- @keys[key] = Descriptor::Dict.new(name: key, attribute: to, methods: using)
40
+ @keys[key] = Descriptor::Dict.new(
41
+ name: key,
42
+ attribute: to,
43
+ methods: using,
44
+ render_nil: render_nil
45
+ )
40
46
  end
41
47
 
42
48
  # Set the "finalized" instance variable to true
@@ -83,7 +83,8 @@ module Shale
83
83
  using: nil,
84
84
  namespace: :undefined,
85
85
  prefix: :undefined,
86
- cdata: false
86
+ cdata: false,
87
+ render_nil: false
87
88
  )
88
89
  Validator.validate_arguments(element, to, using)
89
90
  Validator.validate_namespace(element, namespace, prefix)
@@ -103,7 +104,8 @@ module Shale
103
104
  attribute: to,
104
105
  methods: using,
105
106
  namespace: Descriptor::XmlNamespace.new(nsp, pfx),
106
- cdata: cdata
107
+ cdata: cdata,
108
+ render_nil: render_nil
107
109
  )
108
110
  end
109
111
 
@@ -118,7 +120,14 @@ module Shale
118
120
  # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
119
121
  #
120
122
  # @api private
121
- def map_attribute(attribute, to: nil, using: nil, namespace: nil, prefix: nil)
123
+ def map_attribute(
124
+ attribute,
125
+ to: nil,
126
+ using: nil,
127
+ namespace: nil,
128
+ prefix: nil,
129
+ render_nil: false
130
+ )
122
131
  Validator.validate_arguments(attribute, to, using)
123
132
  Validator.validate_namespace(attribute, namespace, prefix)
124
133
 
@@ -129,7 +138,8 @@ module Shale
129
138
  attribute: to,
130
139
  methods: using,
131
140
  namespace: Descriptor::XmlNamespace.new(namespace, prefix),
132
- cdata: false
141
+ cdata: false,
142
+ render_nil: render_nil
133
143
  )
134
144
  end
135
145
 
@@ -146,7 +156,8 @@ module Shale
146
156
  attribute: to,
147
157
  methods: using,
148
158
  namespace: nil,
149
- cdata: cdata
159
+ cdata: cdata,
160
+ render_nil: false
150
161
  )
151
162
  end
152
163
 
@@ -124,9 +124,7 @@ module Shale
124
124
  # @api public
125
125
  def to_schema(klass, id: nil, title: nil, description: nil, pretty: false)
126
126
  schema = as_schema(klass, id: id, title: title, description: description)
127
- options = pretty ? :pretty : nil
128
-
129
- Shale.json_adapter.dump(schema, options)
127
+ Shale.json_adapter.dump(schema, pretty: pretty)
130
128
  end
131
129
 
132
130
  private
@@ -35,8 +35,8 @@ module Shale
35
35
  def as_xml(doc)
36
36
  import = doc.create_element('xs:import')
37
37
 
38
- doc.add_attribute(import, 'namespace', @namespace)
39
- doc.add_attribute(import, 'schemaLocation', @location)
38
+ doc.add_attribute(import, 'namespace', @namespace) if @namespace
39
+ doc.add_attribute(import, 'schemaLocation', @location) if @location
40
40
 
41
41
  import
42
42
  end
@@ -222,13 +222,11 @@ module Shale
222
222
  def to_schemas(klass, base_name = nil, pretty: false, declaration: false)
223
223
  schemas = as_schemas(klass, base_name)
224
224
 
225
- options = [
226
- pretty ? :pretty : nil,
227
- declaration ? :declaration : nil,
228
- ]
229
-
230
225
  schemas.to_h do |schema|
231
- [schema.name, Shale.xml_adapter.dump(schema.as_xml, *options)]
226
+ [
227
+ schema.name,
228
+ Shale.xml_adapter.dump(schema.as_xml, pretty: pretty, declaration: declaration),
229
+ ]
232
230
  end
233
231
  end
234
232