representable 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +17 -0
  3. data/CHANGES.md +25 -0
  4. data/Gemfile +4 -12
  5. data/LICENSE +1 -1
  6. data/README.md +6 -6
  7. data/Rakefile +1 -6
  8. data/TODO +1 -3
  9. data/TODO-4.0.md +72 -0
  10. data/lib/representable.rb +19 -25
  11. data/lib/representable/binding.rb +32 -12
  12. data/lib/representable/cached.rb +1 -1
  13. data/lib/representable/coercion.rb +8 -6
  14. data/lib/representable/config.rb +13 -3
  15. data/lib/representable/debug.rb +23 -15
  16. data/lib/representable/declarative.rb +12 -7
  17. data/lib/representable/decorator.rb +1 -1
  18. data/lib/representable/definition.rb +7 -3
  19. data/lib/representable/deserializer.rb +5 -4
  20. data/lib/representable/for_collection.rb +1 -1
  21. data/lib/representable/hash.rb +9 -2
  22. data/lib/representable/hash/allow_symbols.rb +9 -11
  23. data/lib/representable/hash/binding.rb +1 -0
  24. data/lib/representable/hash/collection.rb +4 -2
  25. data/lib/representable/hash_methods.rb +3 -2
  26. data/lib/representable/insert.rb +1 -1
  27. data/lib/representable/json.rb +8 -7
  28. data/lib/representable/json/collection.rb +3 -0
  29. data/lib/representable/object.rb +1 -1
  30. data/lib/representable/object/binding.rb +5 -1
  31. data/lib/representable/option.rb +19 -0
  32. data/lib/representable/pipeline.rb +3 -2
  33. data/lib/representable/pipeline_factories.rb +4 -2
  34. data/lib/representable/populator.rb +1 -1
  35. data/lib/representable/represent.rb +1 -0
  36. data/lib/representable/serializer.rb +3 -2
  37. data/lib/representable/version.rb +1 -1
  38. data/lib/representable/virtus_coercion.rb +38 -0
  39. data/lib/representable/xml.rb +12 -10
  40. data/lib/representable/xml/binding.rb +19 -13
  41. data/lib/representable/xml/namespace.rb +122 -0
  42. data/lib/representable/yaml.rb +6 -2
  43. data/lib/representable/yaml/binding.rb +1 -0
  44. data/representable.gemspec +8 -9
  45. data/test/as_test.rb +7 -7
  46. data/test/binding_test.rb +14 -14
  47. data/test/cached_test.rb +59 -49
  48. data/test/class_test.rb +9 -9
  49. data/test/coercion_test.rb +33 -22
  50. data/test/config/inherit_test.rb +14 -14
  51. data/test/config_test.rb +20 -20
  52. data/test/decorator_scope_test.rb +4 -4
  53. data/test/decorator_test.rb +33 -20
  54. data/test/default_test.rb +8 -8
  55. data/test/defaults_options_test.rb +3 -3
  56. data/test/definition_test.rb +38 -40
  57. data/test/{example.rb → examples/example.rb} +0 -1
  58. data/test/examples/object.rb +1 -5
  59. data/test/exec_context_test.rb +8 -8
  60. data/test/features_test.rb +6 -6
  61. data/test/filter_test.rb +8 -8
  62. data/test/for_collection_test.rb +10 -10
  63. data/test/generic_test.rb +13 -13
  64. data/test/getter_setter_test.rb +5 -5
  65. data/test/hash_bindings_test.rb +1 -1
  66. data/test/hash_test.rb +45 -23
  67. data/test/heritage_test.rb +16 -13
  68. data/test/if_test.rb +9 -9
  69. data/test/include_exclude_test.rb +14 -14
  70. data/test/inherit_test.rb +18 -18
  71. data/test/inline_test.rb +24 -24
  72. data/test/instance_test.rb +31 -31
  73. data/test/is_representable_test.rb +10 -10
  74. data/test/json_test.rb +29 -7
  75. data/test/lonely_test.rb +31 -31
  76. data/test/nested_test.rb +13 -13
  77. data/test/object_test.rb +9 -9
  78. data/test/option_test.rb +36 -0
  79. data/test/parse_pipeline_test.rb +3 -5
  80. data/test/pipeline_test.rb +50 -50
  81. data/test/populator_test.rb +18 -18
  82. data/test/prepare_test.rb +4 -4
  83. data/test/private_options_test.rb +2 -2
  84. data/test/reader_writer_test.rb +2 -2
  85. data/test/render_nil_test.rb +2 -2
  86. data/test/represent_test.rb +14 -14
  87. data/test/representable_test.rb +34 -36
  88. data/test/schema_test.rb +8 -11
  89. data/test/serialize_deserialize_test.rb +2 -2
  90. data/test/skip_test.rb +14 -14
  91. data/test/stringify_hash_test.rb +3 -3
  92. data/test/test_helper.rb +26 -14
  93. data/test/uncategorized_test.rb +10 -10
  94. data/test/user_options_test.rb +4 -4
  95. data/test/virtus_coercion_test.rb +52 -0
  96. data/test/wrap_test.rb +19 -19
  97. data/test/xml_bindings_test.rb +0 -4
  98. data/test/xml_namespace_test.rb +186 -0
  99. data/test/xml_test.rb +103 -43
  100. data/test/yaml_test.rb +51 -26
  101. metadata +101 -39
  102. data/.travis.yml +0 -7
  103. data/lib/representable/TODO.getting_serious +0 -11
  104. data/lib/representable/autoload.rb +0 -10
  105. data/test/mongoid_test.rb +0 -31
@@ -17,11 +17,11 @@ describe "#from_hash" do
17
17
  end
18
18
 
19
19
  it "parses symbols, too" do
20
- OpenStruct.new.extend(representer).from_hash({:song => {:title => "Der Optimist"}}).song.title.must_equal "Der Optimist"
20
+ _(OpenStruct.new.extend(representer).from_hash({:song => {:title => "Der Optimist"}}).song.title).must_equal "Der Optimist"
21
21
  end
22
22
 
23
23
  it "still parses strings" do
24
- OpenStruct.new.extend(representer).from_hash({"song" => {"title" => "Der Optimist"}}).song.title.must_equal "Der Optimist"
24
+ _(OpenStruct.new.extend(representer).from_hash({"song" => {"title" => "Der Optimist"}}).song.title).must_equal "Der Optimist"
25
25
  end
26
26
 
27
27
  describe "with :wrap" do
@@ -33,7 +33,7 @@ describe "#from_hash" do
33
33
  end
34
34
 
35
35
  it "parses symbols, too" do
36
- OpenStruct.new.extend(representer).from_hash({:album => {:song => {:title => "Der Optimist"}}}).song.title.must_equal "Der Optimist"
36
+ _(OpenStruct.new.extend(representer).from_hash({:album => {:song => {:title => "Der Optimist"}}}).song.title).must_equal "Der Optimist"
37
37
  end
38
38
  end
39
39
  end
data/test/test_helper.rb CHANGED
@@ -1,16 +1,20 @@
1
+ require 'pry-byebug'
1
2
  require 'representable'
2
3
 
3
- require "json"
4
- require "psych"
5
-
6
- require 'representable/json'
7
- require 'representable/xml'
8
- require 'representable/yaml'
9
4
  require 'minitest/autorun'
10
5
  require 'test_xml/mini_test'
11
6
 
12
7
  require "representable/debug"
8
+ require 'minitest/assertions'
13
9
 
10
+ module MiniTest::Assertions
11
+ def assert_equal_xml(text, subject)
12
+ assert_equal (text.gsub("\n", "").gsub(/(\s\s+)/, "")), subject.gsub("\n", "").gsub(/(\s\s+)/, "")
13
+ end
14
+ end
15
+ String.infect_an_assertion :assert_equal_xml, :must_xml
16
+
17
+ # TODO: delete all that in 4.0:
14
18
  class Album
15
19
  attr_accessor :songs, :best_song
16
20
  def initialize(songs=nil, best_song=nil)
@@ -45,7 +49,7 @@ module AssertJson
45
49
  module Assertions
46
50
  def assert_json(expected, actual, msg=nil)
47
51
  msg = message(msg, "") { diff expected, actual }
48
- assert(expected.split("").sort == actual.split("").sort, msg)
52
+ assert_equal(expected.split("").sort, actual.split("").sort, msg)
49
53
  end
50
54
  end
51
55
  end
@@ -83,7 +87,7 @@ MiniTest::Spec.class_eval do
83
87
  end
84
88
 
85
89
  def self.representer!(options={}, &block)
86
- fmt = options # we need that so the 2nd call to ::let (within a ::describe) remembers the right format.
90
+ fmt = options # we need that so the 2nd call to ::let(within a ::describe) remembers the right format.
87
91
 
88
92
  name = options[:name] || :representer
89
93
  format = options[:module] || Representable::Hash
@@ -101,6 +105,8 @@ MiniTest::Spec.class_eval do
101
105
  mod
102
106
  end
103
107
 
108
+ undef :inject_representer if method_defined? :inject_representer
109
+
104
110
  def inject_representer(mod, options)
105
111
  return unless options[:inject]
106
112
 
@@ -116,7 +122,7 @@ MiniTest::Spec.class_eval do
116
122
  def representer_for(modules=[Representable::Hash], &block)
117
123
  Module.new do
118
124
  extend TestMethods
119
- include *modules
125
+ include(*modules)
120
126
  module_exec(&block)
121
127
  end
122
128
  end
@@ -127,9 +133,15 @@ MiniTest::Spec.class_eval do
127
133
  end
128
134
 
129
135
  class BaseTest < MiniTest::Spec
130
- let (:new_album) { OpenStruct.new.extend(representer) }
131
- let (:album) { OpenStruct.new(:songs => ["Fuck Armageddon"]).extend(representer) }
132
- let (:song) { OpenStruct.new(:title => "Resist Stance") }
133
- let (:song_representer) { Module.new do include Representable::Hash; property :title end }
136
+ let(:new_album) { OpenStruct.new.extend(representer) }
137
+ let(:album) { OpenStruct.new(:songs => ["Fuck Armageddon"]).extend(representer) }
138
+ let(:song) { OpenStruct.new(:title => "Resist Stance") }
139
+ let(:song_representer) { Module.new do include Representable::Hash; property :title end }
134
140
 
135
- end
141
+ end
142
+
143
+ Band = Struct.new(:id, :name) do
144
+ def [](*attrs)
145
+ attrs.collect { |attr| send(attr) }
146
+ end
147
+ end
@@ -15,12 +15,12 @@ class StopWhenIncomingObjectFragmentIsNilTest < MiniTest::Spec
15
15
 
16
16
  it do
17
17
  album = Album.new
18
- representer.new(album).from_hash({"id"=>1, "songs"=>[{"title"=>"Walkie Talkie"}]}).songs.must_equal [Song.new("Walkie Talkie")]
18
+ _(representer.new(album).from_hash({"id"=>1, "songs"=>[{"title"=>"Walkie Talkie"}]}).songs).must_equal [Song.new("Walkie Talkie")]
19
19
  end
20
20
 
21
21
  it do
22
22
  album = Album.new(2, ["original"])
23
- representer.new(album).from_hash({"id"=>1, "songs"=>nil}).songs.must_equal ["original"]
23
+ _(representer.new(album).from_hash({"id"=>1, "songs"=>nil}).songs).must_equal ["original"]
24
24
  end
25
25
 
26
26
  end
@@ -35,13 +35,13 @@ class RenderPipelineOptionTest < MiniTest::Spec
35
35
  end
36
36
  end
37
37
 
38
- it { representer.new(Album.new).to_hash(user_options: {function: NilToNA}).must_equal({"id"=>"n/a"}) }
39
- it { representer.new(Album.new(1)).to_hash(user_options: {function: NilToNA}).must_equal({"id"=>1}) }
38
+ it { _(representer.new(Album.new).to_hash(user_options: {function: NilToNA})).must_equal({"id"=>"n/a"}) }
39
+ it { _(representer.new(Album.new(1)).to_hash(user_options: {function: NilToNA})).must_equal({"id"=>1}) }
40
40
 
41
41
  it "is cached" do
42
42
  decorator = representer.new(Album.new)
43
- decorator.to_hash(user_options: {function: NilToNA}).must_equal({"id"=>"n/a"})
44
- decorator.to_hash(user_options: {function: nil}).must_equal({"id"=>"n/a"}) # non-sense function is not applied.
43
+ _(decorator.to_hash(user_options: {function: NilToNA})).must_equal({"id"=>"n/a"})
44
+ _(decorator.to_hash(user_options: {function: nil})).must_equal({"id"=>"n/a"}) # non-sense function is not applied.
45
45
  end
46
46
 
47
47
  end
@@ -56,12 +56,12 @@ class ParsePipelineOptionTest < MiniTest::Spec
56
56
  end
57
57
  end
58
58
 
59
- it { representer.new(Album.new).from_hash({"id"=>nil}, user_options: {function: NilToNA}).id.must_equal "n/a" }
60
- it { representer.new(Album.new(1)).to_hash(user_options: {function: NilToNA}).must_equal({"id"=>1}) }
59
+ it { _(representer.new(Album.new).from_hash({"id"=>nil}, user_options: {function: NilToNA}).id).must_equal "n/a" }
60
+ it { _(representer.new(Album.new(1)).to_hash(user_options: {function: NilToNA})).must_equal({"id"=>1}) }
61
61
 
62
62
  it "is cached" do
63
63
  decorator = representer.new(Album.new)
64
- decorator.from_hash({"id"=>nil}, user_options: {function: NilToNA}).id.must_equal "n/a"
65
- decorator.from_hash({"id"=>nil}, user_options: {function: "nonsense"}).id.must_equal "n/a" # non-sense function is not applied.
64
+ _(decorator.from_hash({"id"=>nil}, user_options: {function: NilToNA}).id).must_equal "n/a"
65
+ _(decorator.from_hash({"id"=>nil}, user_options: {function: "nonsense"}).id).must_equal "n/a" # non-sense function is not applied.
66
66
  end
67
67
  end
@@ -7,9 +7,9 @@ class UserOptionsTest < Minitest::Spec
7
7
  property :title, if: ->(options) { options[:user_options][:visible] }
8
8
  end
9
9
 
10
- it { Song.new("Run With It").extend(representer).to_hash.must_equal({}) }
11
- it { Song.new("Run With It").extend(representer).to_hash(user_options: {visible: true}).must_equal({"title"=>"Run With It"}) }
12
- it { Song.new("Run With It").extend(representer).from_hash("title"=>"Business Conduct").title.must_equal "Run With It" }
13
- it { Song.new("Run With It").extend(representer).from_hash({"title"=>"Business Conduct"}, user_options: {visible: true}).title.must_equal "Business Conduct" }
10
+ it { _(Song.new("Run With It").extend(representer).to_hash).must_equal({}) }
11
+ it { _(Song.new("Run With It").extend(representer).to_hash(user_options: {visible: true})).must_equal({"title"=>"Run With It"}) }
12
+ it { _(Song.new("Run With It").extend(representer).from_hash("title"=>"Business Conduct").title).must_equal "Run With It" }
13
+ it { _(Song.new("Run With It").extend(representer).from_hash({"title"=>"Business Conduct"}, user_options: {visible: true}).title).must_equal "Business Conduct" }
14
14
  end
15
15
  # Representable.deprecations=false
@@ -0,0 +1,52 @@
1
+ require 'test_helper'
2
+ require 'representable/virtus_coercion'
3
+
4
+ class VirtusCoercionTest < MiniTest::Spec
5
+ representer! do
6
+ include Representable::VirtusCoercion
7
+
8
+ property :title # no coercion.
9
+ property :length, :type => Float
10
+
11
+ property :band, :class => OpenStruct do
12
+ property :founded, :type => Integer
13
+ end
14
+
15
+ collection :songs, :class => OpenStruct do
16
+ property :ok, :type => Virtus::Attribute::Boolean
17
+ end
18
+ end
19
+
20
+ let(:album) { OpenStruct.new(:title => "Dire Straits", :length => 41.34,
21
+ :band => OpenStruct.new(:founded => "1977"),
22
+ :songs => [OpenStruct.new(:ok => 1), OpenStruct.new(:ok => 0)]) }
23
+
24
+ it { _(album.extend(representer).to_hash).must_equal({"title"=>"Dire Straits", "length"=>41.34, "band"=>{"founded"=>1977}, "songs"=>[{"ok"=>true}, {"ok"=>false}]}) }
25
+
26
+ it {
27
+ album = OpenStruct.new
28
+ album.extend(representer)
29
+ album.from_hash({"title"=>"Dire Straits", "length"=>"41.34", "band"=>{"founded"=>"1977"}, "songs"=>[{"ok"=>1}, {"ok"=>0}]})
30
+
31
+ # it
32
+ _(album.length).must_equal 41.34
33
+ _(album.band.founded).must_equal 1977
34
+ _(album.songs[0].ok).must_equal true
35
+ }
36
+
37
+
38
+ describe "with user :parse_filter and :render_filter" do
39
+ representer! do
40
+ include Representable::VirtusCoercion
41
+
42
+ property :length, :type => Float,
43
+ :parse_filter => lambda { |input, options| "#{input}.1" }, # happens BEFORE coercer.
44
+ :render_filter => lambda { |fragment,*| "#{fragment}.1" }
45
+ end
46
+
47
+ # user's :parse_filter(s) are run before coercion.
48
+ it { _(OpenStruct.new.extend(representer).from_hash("length"=>"1").length).must_equal 1.1 }
49
+ # user's :render_filter(s) are run before coercion.
50
+ it { _(OpenStruct.new(:length=>1).extend(representer).to_hash).must_equal({"length" => 1.1}) }
51
+ end
52
+ end
data/test/wrap_test.rb CHANGED
@@ -8,10 +8,10 @@ class WrapTest < MiniTest::Spec
8
8
  class SoftcoreBand < HardcoreBand
9
9
  end
10
10
 
11
- let (:band) { HardcoreBand.new }
11
+ let(:band) { HardcoreBand.new }
12
12
 
13
13
  it "returns false per default" do
14
- assert_equal nil, SoftcoreBand.new.send(:representation_wrap)
14
+ assert_nil SoftcoreBand.new.send(:representation_wrap)
15
15
  end
16
16
 
17
17
  it "infers a printable class name if set to true" do
@@ -32,17 +32,17 @@ class WrapTest < MiniTest::Spec
32
32
  ) do |format, mod, output, input|
33
33
 
34
34
  describe "[#{format}] dynamic wrap" do
35
- let (:band) { representer.prepare(Struct.new(:name, :genre).new("Blink", "Pop")) }
36
- let (:format) { format }
35
+ let(:band) { representer.prepare(Struct.new(:name, :genre).new("Blink", "Pop")) }
36
+ let(:format) { format }
37
37
 
38
38
  representer!(:module => mod) do
39
- self.representation_wrap = lambda { |args| "#{name}#{args[:number]}" }
39
+ self.representation_wrap = lambda { |number:, **| "#{name}#{number}" }
40
40
  property :genre
41
41
  end
42
42
 
43
43
  it { render(band, {:number => 182}).must_equal_document(output) }
44
44
 
45
- it { parse(band, input, {:number => 182}).genre.must_equal "Poppunk" } # TODO: better test. also, xml parses _any_ wrap.
45
+ it { _(parse(band, input, {:number => 182}).genre).must_equal "Poppunk" } # TODO: better test. also, xml parses _any_ wrap.
46
46
  end
47
47
  end
48
48
  end
@@ -65,19 +65,19 @@ class HashDisableWrapTest < MiniTest::Spec
65
65
  end
66
66
  end
67
67
 
68
- let (:band) { BandDecorator.prepare(Band.new("Social Distortion")) }
68
+ let(:band) { BandDecorator.prepare(Band.new("Social Distortion")) }
69
69
 
70
70
  # direct, local api.
71
71
  it do
72
- band.to_hash.must_equal({"bands" => {"name"=>"Social Distortion"}})
73
- band.to_hash(wrap: false).must_equal({"name"=>"Social Distortion"})
74
- band.to_hash(wrap: :band).must_equal(:band=>{"name"=>"Social Distortion"})
72
+ _(band.to_hash).must_equal({"bands" => {"name"=>"Social Distortion"}})
73
+ _(band.to_hash(wrap: false)).must_equal({"name"=>"Social Distortion"})
74
+ _(band.to_hash(wrap: :band)).must_equal(:band=>{"name"=>"Social Distortion"})
75
75
  end
76
76
 
77
77
  it do
78
- band.from_hash({"bands" => {"name"=>"Social Distortion"}}).name.must_equal "Social Distortion"
79
- band.from_hash({"name"=>"Social Distortion"}, wrap: false).name.must_equal "Social Distortion"
80
- band.from_hash({band: {"name"=>"Social Distortion"}}, wrap: :band).name.must_equal "Social Distortion"
78
+ _(band.from_hash({"bands" => {"name"=>"Social Distortion"}}).name).must_equal "Social Distortion"
79
+ _(band.from_hash({"name"=>"Social Distortion"}, wrap: false).name).must_equal "Social Distortion"
80
+ _(band.from_hash({band: {"name"=>"Social Distortion"}}, wrap: :band).name).must_equal "Social Distortion"
81
81
  end
82
82
 
83
83
 
@@ -90,15 +90,15 @@ class HashDisableWrapTest < MiniTest::Spec
90
90
  end
91
91
 
92
92
 
93
- let (:album) { AlbumDecorator.prepare(Album.new(Band.new("Social Distortion", Label.new("Epitaph")))) }
93
+ let(:album) { AlbumDecorator.prepare(Album.new(Band.new("Social Distortion", Label.new("Epitaph")))) }
94
94
 
95
95
  # band has wrap turned off per property definition, however, label still has wrap.
96
96
  it "renders" do
97
- album.to_hash.must_equal({"albums" => {"band" => {"name"=>"Social Distortion", "label"=>{"important"=>{"name"=>"Epitaph"}}}}})
97
+ _(album.to_hash).must_equal({"albums" => {"band" => {"name"=>"Social Distortion", "label"=>{"important"=>{"name"=>"Epitaph"}}}}})
98
98
  end
99
99
 
100
100
  it "parses" do
101
- album.from_hash({"albums" => {"band" => {"name"=>"Rvivr"}}}).band.name.must_equal "Rvivr"
101
+ _(album.from_hash({"albums" => {"band" => {"name"=>"Rvivr"}}}).band.name).must_equal "Rvivr"
102
102
  end
103
103
  end
104
104
 
@@ -120,7 +120,7 @@ class XMLDisableWrapTest < MiniTest::Spec
120
120
  # end
121
121
  end
122
122
 
123
- let (:band) { BandDecorator.prepare(Band.new("Social Distortion")) }
123
+ let(:band) { BandDecorator.prepare(Band.new("Social Distortion")) }
124
124
 
125
125
  it do
126
126
  band.to_xml.must_equal_xml "<bands><name>Social Distortion</name></bands>"
@@ -137,12 +137,12 @@ class XMLDisableWrapTest < MiniTest::Spec
137
137
  end
138
138
 
139
139
 
140
- let (:album) { AlbumDecorator.prepare(Album.new(Band.new("Social Distortion", Label.new("Epitaph")))) }
140
+ let(:album) { AlbumDecorator.prepare(Album.new(Band.new("Social Distortion", Label.new("Epitaph")))) }
141
141
 
142
142
  # band has wrap turned of per property definition, however, label still has wrap.
143
143
  it "rendersxx" do
144
144
  skip
145
- album.to_xml.must_equal({"albums" => {"band" => {"name"=>"Social Distortion", "label"=>{"important"=>{"name"=>"Epitaph"}}}}})
145
+ _(album.to_xml).must_equal({"albums" => {"band" => {"name"=>"Social Distortion", "label"=>{"important"=>{"name"=>"Epitaph"}}}}})
146
146
  end
147
147
 
148
148
  # it "parses" do
@@ -1,10 +1,6 @@
1
1
  require 'test_helper'
2
- require 'representable/json' # FIXME.
3
- require 'representable/xml/collection'
4
2
  require 'representable/xml/hash'
5
3
 
6
- require 'representable/xml'
7
-
8
4
  class XMLBindingTest < MiniTest::Spec
9
5
  module SongRepresenter
10
6
  include Representable::XML
@@ -0,0 +1,186 @@
1
+ require "test_helper"
2
+
3
+ # <lib:library
4
+ # xmlns:lib="http://eric.van-der-vlist.com/ns/library"
5
+ # xmlns:hr="http://eric.van-der-vlist.com/ns/person">
6
+ # <lib:book id="b0836217462" available="true">
7
+ # <lib:isbn>0836217462</lib:isbn>
8
+ # <lib:title xml:lang="en">Being a Dog Is a Full-Time Job</lib:title>
9
+ # <hr:author id="CMS">
10
+ # <hr:name>Charles M Schulz</hr:name>
11
+ # <hr:born>1922-11-26</hr:born>
12
+ # <hr:dead>2000-02-12</hr:dead>
13
+ # </hr:author>
14
+ # <lib:character id="PP">
15
+ # <hr:name>Peppermint Patty</hr:name>
16
+ # <hr:born>1966-08-22</hr:born>
17
+ # <lib:qualification>bold, brash and tomboyish</lib:qualification>
18
+ # </lib:character>
19
+ # <lib:character id="Snoopy">
20
+ # <hr:name>Snoopy</hr:name>
21
+ # <hr:born>1950-10-04</hr:born>
22
+ # <lib:qualification>extroverted beagle</lib:qualification>
23
+ # </lib:character>
24
+ # <lib:character id="Schroeder">
25
+ # <hr:name>Schroeder</hr:name>
26
+ # <hr:born>1951-05-30</hr:born>
27
+ # <lib:qualification>brought classical music to the Peanuts strip
28
+ # </lib:qualification>
29
+ # </lib:character>
30
+ # <lib:character id="Lucy">
31
+ # <hr:name>Lucy</hr:name>
32
+ # <hr:born>1952-03-03</hr:born>
33
+ # <lib:qualification>bossy, crabby and selfish</lib:qualification>
34
+ # </lib:character>
35
+ # </lib:book>
36
+ # </lib:library>
37
+
38
+ # Theoretically, every property (scalar) needs/should have a namespace.
39
+ # property :name, namespace: "http://eric.van-der-vlist.com/ns/person"
40
+ # # This is implicit if contained:
41
+ # class Person < Decorator
42
+ # namespace: "http://eric.van-der-vlist.com/ns/person"
43
+ # property :name #, namespace: "http://eric.van-der-vlist.com/ns/person"
44
+ # end
45
+ # class Library
46
+ # namespace "http://eric.van-der-vlist.com/ns/library"
47
+
48
+ # namespace_def lib: "http://eric.van-der-vlist.com/ns/library"
49
+ # namespace_def person: "http://eric.van-der-vlist.com/ns/person"
50
+ # # namespace_def person: Person
51
+
52
+ # property :person, decorator: Person
53
+ # end
54
+ class NamespaceXMLTest < Minitest::Spec
55
+ module Model
56
+ Library = Struct.new(:book)
57
+ Book = Struct.new(:id, :isbn)
58
+ end
59
+
60
+ let (:book) { Model::Book.new(1, 666) }
61
+
62
+ #:simple-class
63
+ class Library < Representable::Decorator
64
+ feature Representable::XML
65
+ feature Representable::XML::Namespace
66
+
67
+ namespace "http://eric.van-der-vlist.com/ns/library"
68
+
69
+ property :book do
70
+ namespace "http://eric.van-der-vlist.com/ns/library"
71
+
72
+ property :id, attribute: true
73
+ property :isbn
74
+ end
75
+ end
76
+ #:simple-class end
77
+
78
+
79
+ # default namespace for library
80
+ it "what" do
81
+ Library.new(Model::Library.new(book)).to_xml.must_xml(
82
+
83
+ #:simple-xml
84
+ %{<library xmlns="http://eric.van-der-vlist.com/ns/library">
85
+ <book id="1">
86
+ <isbn>666</isbn>
87
+ </book>
88
+ </library>}
89
+ #:simple-xml end
90
+ )
91
+ end
92
+ end
93
+
94
+ class Namespace2XMLTest < Minitest::Spec
95
+ module Model
96
+ Library = Struct.new(:book)
97
+ Book = Struct.new(:id, :isbn, :author, :character)
98
+ Character = Struct.new(:name, :born, :qualification)
99
+ end
100
+
101
+ let (:book) { Model::Book.new(1, 666, Model::Character.new("Fowler"), [Model::Character.new("Frau Java", 1991, "typed")]) }
102
+
103
+ #:map-class
104
+ class Library < Representable::Decorator
105
+ feature Representable::XML
106
+ feature Representable::XML::Namespace
107
+
108
+ namespace "http://eric.van-der-vlist.com/ns/library"
109
+ namespace_def lib: "http://eric.van-der-vlist.com/ns/library"
110
+ namespace_def hr: "http://eric.van-der-vlist.com/ns/person"
111
+
112
+ property :book, class: Model::Book do
113
+ namespace "http://eric.van-der-vlist.com/ns/library"
114
+
115
+ property :id, attribute: true
116
+ property :isbn
117
+
118
+ property :author, class: Model::Character do
119
+ namespace "http://eric.van-der-vlist.com/ns/person"
120
+
121
+ property :name
122
+ property :born
123
+ end
124
+
125
+ collection :character, class: Model::Character do
126
+ namespace "http://eric.van-der-vlist.com/ns/library"
127
+
128
+ property :qualification
129
+
130
+ property :name, namespace: "http://eric.van-der-vlist.com/ns/person"
131
+ property :born, namespace: "http://eric.van-der-vlist.com/ns/person"
132
+ end
133
+ end
134
+ end
135
+ #:map-class end
136
+
137
+ it "renders" do
138
+ Library.new(Model::Library.new(book)).to_xml.must_xml(
139
+ #:map-xml
140
+ %{<lib:library xmlns:lib=\"http://eric.van-der-vlist.com/ns/library\" xmlns:hr=\"http://eric.van-der-vlist.com/ns/person\">
141
+ <lib:book id=\"1\">
142
+ <lib:isbn>666</lib:isbn>
143
+ <hr:author>
144
+ <hr:name>Fowler</hr:name>
145
+ </hr:author>
146
+ <lib:character>
147
+ <lib:qualification>typed</lib:qualification>
148
+ <hr:name>Frau Java</hr:name>
149
+ <hr:born>1991</hr:born>
150
+ </lib:character>
151
+ </lib:book>
152
+ </lib:library>}
153
+ #:map-xml end
154
+ )
155
+ end
156
+
157
+ it "parses" do
158
+ lib = Model::Library.new
159
+ #:parse-call
160
+ Library.new(lib).from_xml(%{<lib:library
161
+ xmlns:lib="http://eric.van-der-vlist.com/ns/library"
162
+ xmlns:hr="http://eric.van-der-vlist.com/ns/person">
163
+ <lib:book id="1">
164
+ <lib:isbn>666</lib:isbn>
165
+ <hr:author>
166
+ <hr:name>Fowler</hr:name>
167
+ </hr:author>
168
+ <lib:character>
169
+ <lib:qualification>typed</lib:qualification>
170
+ <hr:name>Frau Java</hr:name>
171
+ <bogus:name>Mr. Ruby</hr:name>
172
+ <name>Dr. Elixir</hr:name>
173
+ <hr:born>1991</hr:born>
174
+ </lib:character>
175
+ </lib:book>
176
+ </lib:library>}
177
+ #:parse-call end
178
+ )
179
+
180
+ _(lib.book.inspect).must_equal %{#<struct Namespace2XMLTest::Model::Book id=\"1\", isbn=\"666\", author=#<struct Namespace2XMLTest::Model::Character name=\"Fowler\", born=nil, qualification=nil>, character=[#<struct Namespace2XMLTest::Model::Character name=\"Frau Java\", born=\"1991\", qualification=\"typed\">]>}
181
+
182
+ #:parse-res
183
+ lib.book.character[0].name #=> "Frau Java"
184
+ #:parse-res end
185
+ end
186
+ end