representable 0.0.1.alpha1

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 (103) hide show
  1. data/.gitignore +7 -0
  2. data/.gitmodules +3 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +3 -0
  5. data/History.txt +354 -0
  6. data/LICENSE +20 -0
  7. data/README.rdoc +186 -0
  8. data/Rakefile +10 -0
  9. data/TODO +37 -0
  10. data/VERSION +1 -0
  11. data/examples/amazon.rb +35 -0
  12. data/examples/current_weather.rb +27 -0
  13. data/examples/dashed_elements.rb +20 -0
  14. data/examples/library.rb +40 -0
  15. data/examples/posts.rb +27 -0
  16. data/examples/rails.rb +70 -0
  17. data/examples/twitter.rb +37 -0
  18. data/examples/xml/active_record.xml +70 -0
  19. data/examples/xml/amazon.xml +133 -0
  20. data/examples/xml/current_weather.xml +89 -0
  21. data/examples/xml/dashed_elements.xml +52 -0
  22. data/examples/xml/posts.xml +23 -0
  23. data/examples/xml/twitter.xml +422 -0
  24. data/lib/representable.rb +257 -0
  25. data/lib/representable/definition.rb +109 -0
  26. data/lib/representable/nokogiri_extensions.rb +19 -0
  27. data/lib/representable/references.rb +153 -0
  28. data/lib/representable/version.rb +3 -0
  29. data/lib/representable/xml.rb +79 -0
  30. data/representable.gemspec +29 -0
  31. data/spec/definition_spec.rb +495 -0
  32. data/spec/examples/active_record_spec.rb +41 -0
  33. data/spec/examples/amazon_spec.rb +54 -0
  34. data/spec/examples/current_weather_spec.rb +37 -0
  35. data/spec/examples/dashed_elements_spec.rb +20 -0
  36. data/spec/examples/library_spec.rb +46 -0
  37. data/spec/examples/post_spec.rb +24 -0
  38. data/spec/examples/twitter_spec.rb +32 -0
  39. data/spec/roxml_integration_test.rb +289 -0
  40. data/spec/roxml_spec.rb +372 -0
  41. data/spec/shared_specs.rb +15 -0
  42. data/spec/spec_helper.rb +5 -0
  43. data/spec/support/libxml.rb +3 -0
  44. data/spec/support/nokogiri.rb +3 -0
  45. data/spec/xml/array_spec.rb +36 -0
  46. data/spec/xml/attributes_spec.rb +71 -0
  47. data/spec/xml/encoding_spec.rb +53 -0
  48. data/spec/xml/namespace_spec.rb +270 -0
  49. data/spec/xml/namespaces_spec.rb +67 -0
  50. data/spec/xml/object_spec.rb +82 -0
  51. data/spec/xml/parser_spec.rb +21 -0
  52. data/spec/xml/text_spec.rb +71 -0
  53. data/test/fixtures/book_malformed.xml +5 -0
  54. data/test/fixtures/book_pair.xml +8 -0
  55. data/test/fixtures/book_text_with_attribute.xml +5 -0
  56. data/test/fixtures/book_valid.xml +5 -0
  57. data/test/fixtures/book_with_authors.xml +7 -0
  58. data/test/fixtures/book_with_contributions.xml +9 -0
  59. data/test/fixtures/book_with_contributors.xml +7 -0
  60. data/test/fixtures/book_with_contributors_attrs.xml +7 -0
  61. data/test/fixtures/book_with_default_namespace.xml +9 -0
  62. data/test/fixtures/book_with_depth.xml +6 -0
  63. data/test/fixtures/book_with_octal_pages.xml +4 -0
  64. data/test/fixtures/book_with_publisher.xml +7 -0
  65. data/test/fixtures/book_with_wrapped_attr.xml +3 -0
  66. data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
  67. data/test/fixtures/dictionary_of_attrs.xml +6 -0
  68. data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
  69. data/test/fixtures/dictionary_of_mixeds.xml +4 -0
  70. data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
  71. data/test/fixtures/dictionary_of_names.xml +4 -0
  72. data/test/fixtures/dictionary_of_texts.xml +10 -0
  73. data/test/fixtures/library.xml +30 -0
  74. data/test/fixtures/library_uppercase.xml +30 -0
  75. data/test/fixtures/muffins.xml +3 -0
  76. data/test/fixtures/nameless_ageless_youth.xml +2 -0
  77. data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
  78. data/test/fixtures/node_with_name_conflicts.xml +4 -0
  79. data/test/fixtures/numerology.xml +4 -0
  80. data/test/fixtures/person.xml +1 -0
  81. data/test/fixtures/person_with_guarded_mothers.xml +13 -0
  82. data/test/fixtures/person_with_mothers.xml +10 -0
  83. data/test/mocks/dictionaries.rb +57 -0
  84. data/test/mocks/mocks.rb +279 -0
  85. data/test/roxml_test.rb +58 -0
  86. data/test/support/fixtures.rb +11 -0
  87. data/test/test_helper.rb +6 -0
  88. data/test/unit/definition_test.rb +235 -0
  89. data/test/unit/deprecations_test.rb +24 -0
  90. data/test/unit/to_xml_test.rb +81 -0
  91. data/test/unit/xml_attribute_test.rb +39 -0
  92. data/test/unit/xml_block_test.rb +81 -0
  93. data/test/unit/xml_bool_test.rb +122 -0
  94. data/test/unit/xml_convention_test.rb +150 -0
  95. data/test/unit/xml_hash_test.rb +115 -0
  96. data/test/unit/xml_initialize_test.rb +49 -0
  97. data/test/unit/xml_name_test.rb +141 -0
  98. data/test/unit/xml_namespace_test.rb +31 -0
  99. data/test/unit/xml_object_test.rb +206 -0
  100. data/test/unit/xml_required_test.rb +94 -0
  101. data/test/unit/xml_text_test.rb +71 -0
  102. data/website/index.html +98 -0
  103. metadata +248 -0
@@ -0,0 +1,372 @@
1
+ require_relative './spec_helper'
2
+
3
+ describe ROXML, "#from_xml" do
4
+ shared_examples_for "from_xml call" do
5
+ it "should fetch values" do
6
+ book = BookWithContributors.from_xml(@path)
7
+ book.title.should == "Programming Ruby - 2nd Edition"
8
+ book.contributors.map(&:name).should == ["David Thomas","Andrew Hunt","Chad Fowler"]
9
+ end
10
+ end
11
+
12
+ context "called with PathName" do
13
+ before do
14
+ @path = Pathname.new(fixture_path(:book_with_contributors))
15
+ end
16
+ it_should_behave_like "from_xml call"
17
+ end
18
+
19
+ context "called with File" do
20
+ before do
21
+ @path = File.new(fixture_path(:book_with_contributors))
22
+ end
23
+ it_should_behave_like "from_xml call"
24
+ end
25
+
26
+ context "called with URI" do
27
+ before do
28
+ require 'uri'
29
+ @path = URI.parse("file://#{File.expand_path(File.expand_path(fixture_path(:book_with_contributors)))}")
30
+ end
31
+ it_should_behave_like "from_xml call"
32
+ end
33
+ end
34
+
35
+ describe ROXML, "#xml" do
36
+ class DescriptionReadonly
37
+ include ROXML
38
+
39
+ xml_reader :writable, :from => :content
40
+ xml_reader :readonly, :from => :content, :frozen => true
41
+ end
42
+
43
+ class Contributor
44
+ include ROXML
45
+
46
+ xml_reader :role, :from => :attr
47
+ xml_reader :name
48
+ end
49
+
50
+ class BookWithContributions
51
+ include ROXML
52
+
53
+ xml_name :book
54
+ xml_reader :isbn, :from => :attr
55
+ xml_reader :title
56
+ xml_reader :description, :as => DescriptionReadonly
57
+ xml_reader :contributions, :as => [Contributor], :from => 'contributor', :in => "contributions"
58
+ end
59
+
60
+ class BookWithContributionsReadonly
61
+ include ROXML
62
+
63
+ xml_name :book
64
+ xml_reader :isbn, :from => :attr, :frozen => true
65
+ xml_reader :title, :frozen => true
66
+ xml_reader :description, :as => DescriptionReadonly, :frozen => true
67
+ xml_reader :contributions, :as => [Contributor], :from => 'contributor', :in => "contributions", :frozen => true
68
+ end
69
+
70
+ before do
71
+ @writable = BookWithContributions.from_xml(fixture(:book_with_contributions))
72
+ @readonly = BookWithContributionsReadonly.from_xml(fixture(:book_with_contributions))
73
+ end
74
+
75
+ it "should raise on duplicate accessor name" do
76
+ proc do
77
+ Class.new do
78
+ include ROXML
79
+
80
+ xml_reader :id
81
+ xml_accessor :id
82
+ end
83
+ end.should raise_error(RuntimeError)
84
+ end
85
+
86
+ class OctalInteger
87
+ def self.from_xml(val)
88
+ new(Integer(val.content))
89
+ end
90
+
91
+ def initialize(value)
92
+ @val = value
93
+ end
94
+
95
+ def ==(other)
96
+ @val == other
97
+ end
98
+
99
+ def to_xml
100
+ sprintf("%#o", @val)
101
+ end
102
+ end
103
+
104
+ describe "overriding output" do
105
+ class BookWithOctalPages
106
+ include ROXML
107
+
108
+ xml_accessor :pages_with_as, :as => Integer, :to_xml => proc {|val| sprintf("%#o", val) }, :required => true
109
+ xml_accessor :pages_with_type, :as => OctalInteger, :required => true
110
+ end
111
+
112
+ # to_xml_test :book_with_octal_pages
113
+
114
+ describe "with :to_xml option" do
115
+ it "should output with to_xml filtering"
116
+ end
117
+
118
+ describe "with #to_xml on the object" do
119
+ it "should output with to_xml filtering"
120
+ end
121
+ end
122
+
123
+ describe "overriding input" do
124
+ before do
125
+ @book_with_octal_pages_xml = %{
126
+ <book>
127
+ <pages>0357</pages>
128
+ </book>
129
+ }
130
+
131
+ @expected_pages = 239
132
+ end
133
+
134
+ describe "with :as block shorthand" do
135
+ class BookWithOctalPagesBlockShorthand
136
+ include ROXML
137
+
138
+ xml_accessor :pages, :as => Integer, :required => true
139
+ end
140
+
141
+ it "should apply filtering on input" do
142
+ book = BookWithOctalPagesBlockShorthand.from_xml(@book_with_octal_pages_xml)
143
+ book.pages.should == @expected_pages
144
+ end
145
+ end
146
+
147
+ describe "with #from_xml defined on the object" do
148
+ class BookWithOctalPagesType
149
+ include ROXML
150
+
151
+ xml_accessor :pages, :as => OctalInteger, :required => true
152
+ end
153
+
154
+ it "should apply filtering on input" do
155
+ book = BookWithOctalPagesType.from_xml(@book_with_octal_pages_xml)
156
+ book.pages.should == @expected_pages
157
+ end
158
+ end
159
+ end
160
+
161
+ describe "attribute reference" do
162
+ before do
163
+ @frozen = @readonly.isbn
164
+ @unfrozen = @writable.isbn
165
+ end
166
+
167
+ it_should_behave_like "freezable xml reference"
168
+ end
169
+
170
+ describe "text reference" do
171
+ before do
172
+ @frozen = @readonly.title
173
+ @unfrozen = @writable.title
174
+ end
175
+
176
+ it_should_behave_like "freezable xml reference"
177
+ end
178
+
179
+ describe "object reference" do
180
+ before do
181
+ @frozen = @readonly.description
182
+ @unfrozen = @writable.description
183
+ end
184
+
185
+ it_should_behave_like "freezable xml reference"
186
+
187
+ describe "indirect reference via an object" do
188
+ it "does not inherit the frozen status from its parent" do
189
+ @frozen.writable.frozen?.should be_false
190
+ @frozen.readonly.frozen?.should be_true
191
+
192
+ @unfrozen.writable.frozen?.should be_false
193
+ @unfrozen.readonly.frozen?.should be_true
194
+ end
195
+ end
196
+ end
197
+
198
+ describe "array reference" do
199
+ before do
200
+ @frozen = @readonly.contributions
201
+ @unfrozen = @writable.contributions
202
+ end
203
+
204
+ it_should_behave_like "freezable xml reference"
205
+
206
+ it "should apply :frozen to the constituent elements" do
207
+ @frozen.all?(&:frozen?).should be_true
208
+ @unfrozen.any?(&:frozen?).should be_false
209
+ end
210
+
211
+ context "no elements are present in root, no :in is specified" do
212
+ class BookWithContributors
213
+ include ROXML
214
+
215
+ xml_name :book
216
+ xml_reader :isbn, :from => :attr
217
+ xml_reader :title
218
+ xml_reader :description
219
+ xml_reader :contributors, :as => [Contributor]
220
+ end
221
+
222
+ it "should look for elements :in the plural of name" do
223
+ book = BookWithContributors.from_xml(%{
224
+ <book isbn="0974514055">
225
+ <contributors>
226
+ <contributor role="author"><name>David Thomas</name></contributor>
227
+ <contributor role="supporting author"><name>Andrew Hunt</name></contributor>
228
+ <contributor role="supporting author"><name>Chad Fowler</name></contributor>
229
+ </contributors>
230
+ </book>
231
+ })
232
+ book.contributors.map(&:name).sort.should == ["David Thomas","Andrew Hunt","Chad Fowler"].sort
233
+ end
234
+ end
235
+ end
236
+
237
+ describe "hash reference" do
238
+ class DictionaryOfGuardedNames
239
+ include ROXML
240
+
241
+ xml_name :dictionary
242
+ xml_reader :definitions, :as => {:key => :name,
243
+ :value => :content}, :in => :definitions
244
+ end
245
+
246
+ class DictionaryOfGuardedNamesReadonly
247
+ include ROXML
248
+
249
+ xml_name :dictionary
250
+ xml_reader :definitions, :as => {:key => :name,
251
+ :value => :content}, :in => :definitions, :frozen => true
252
+ end
253
+
254
+ before do
255
+ @frozen = DictionaryOfGuardedNamesReadonly.from_xml(fixture(:dictionary_of_guarded_names)).definitions
256
+ @unfrozen = DictionaryOfGuardedNames.from_xml(fixture(:dictionary_of_guarded_names)).definitions
257
+ end
258
+
259
+ it_should_behave_like "freezable xml reference"
260
+
261
+ it "should have frozen keys, as with all hashes" do
262
+ @frozen.keys.all?(&:frozen?).should be_true
263
+ @unfrozen.keys.all?(&:frozen?).should be_true
264
+ end
265
+
266
+ it "should apply :frozen to the constituent values" do
267
+ @frozen.values.all?(&:frozen?).should be_true
268
+ @unfrozen.values.any?(&:frozen?).should be_false
269
+ end
270
+ end
271
+ end
272
+
273
+ describe ROXML, "inheritance" do
274
+ class Book
275
+ include ROXML
276
+
277
+ xml_accessor :isbn, :from => '@ISBN'
278
+ xml_reader :title
279
+ xml_reader :description, :cdata => true
280
+ xml_reader :author
281
+ xml_accessor :pages, :from => 'pagecount', :as => Integer
282
+ end
283
+
284
+ class Measurement
285
+ include ROXML
286
+
287
+ xml_reader :units, :from => :attr
288
+ xml_reader :value, :from => :content, :as => Float
289
+
290
+ def initialize(value = 0, units = 'pixels')
291
+ @value = Float(value)
292
+ @units = units.to_s
293
+ normalize_hundredths
294
+ end
295
+
296
+ def to_s
297
+ "#{value} #{units}"
298
+ end
299
+
300
+ def ==(other)
301
+ other.units == @units && other.value == @value
302
+ end
303
+
304
+ private
305
+ def after_parse
306
+ normalize_hundredths
307
+ end
308
+
309
+ def normalize_hundredths
310
+ if @units.starts_with? 'hundredths-'
311
+ @value /= 100
312
+ @units = @units.split('hundredths-')[1]
313
+ end
314
+ end
315
+ end
316
+
317
+ class InheritedBookWithDepth < Book
318
+ xml_reader :depth, :as => Measurement
319
+ end
320
+
321
+ before do
322
+ @book_xml = %{
323
+ <book ISBN="0201710897">
324
+ <title>The PickAxe</title>
325
+ <description><![CDATA[Probably the best Ruby book out there]]></description>
326
+ <author>David Thomas, Andrew Hunt, Dave Thomas</author>
327
+ <depth units="hundredths-meters">1130</depth>
328
+ <publisher>Pragmattic Programmers</publisher>
329
+ </book>
330
+ }
331
+
332
+ @parent = Book.from_xml(@book_xml)
333
+ @child = InheritedBookWithDepth.from_xml(@book_xml)
334
+ end
335
+
336
+ describe "parent" do
337
+ it "should include its attributes" do
338
+ @child.isbn.should == "0201710897"
339
+ @child.title.should == "The PickAxe"
340
+ @child.description.should == "Probably the best Ruby book out there"
341
+ @child.author.should == 'David Thomas, Andrew Hunt, Dave Thomas'
342
+ @child.pages.should == nil
343
+ end
344
+
345
+ it "should not include its child's attributes" do
346
+ @parent.should_not respond_to(:depth)
347
+ end
348
+ end
349
+
350
+ describe "child" do
351
+ it "should include its parent's attributes" do
352
+ @child.isbn.should == @parent.isbn
353
+ @child.title.should == @parent.title
354
+ @child.description.should == @parent.description
355
+ @child.author.should == @parent.author
356
+ @child.pages.should == @parent.pages
357
+ end
358
+
359
+ it "should include its attributes" do
360
+ @child.depth.to_s.should == '11.3 meters'
361
+ end
362
+
363
+ it "should include parent's attributes added after declaration" do
364
+ Book.class_eval do
365
+ xml_reader :publisher, :required => true
366
+ end
367
+
368
+ book = InheritedBookWithDepth.from_xml(@book_xml)
369
+ book.publisher.should == "Pragmattic Programmers"
370
+ end
371
+ end
372
+ end
@@ -0,0 +1,15 @@
1
+ require_relative './spec_helper'
2
+
3
+ shared_examples_for "freezable xml reference" do
4
+ describe "with :frozen option" do
5
+ it "should be frozen" do
6
+ @frozen.frozen?.should be_true
7
+ end
8
+ end
9
+
10
+ describe "without :frozen option" do
11
+ it "should not be frozen" do
12
+ @unfrozen.frozen?.should be_false
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+ require 'pathname'
4
+ require_relative './../test/support/fixtures'
5
+ require_relative './../lib/representable'
@@ -0,0 +1,3 @@
1
+ module ROXML
2
+ XML_PARSER = 'libxml'
3
+ end
@@ -0,0 +1,3 @@
1
+ module ROXML
2
+ XML_PARSER = 'nokogiri'
3
+ end
@@ -0,0 +1,36 @@
1
+ require_relative './../spec_helper'
2
+
3
+ module ArraySpec
4
+ class Book
5
+ include ROXML
6
+ xml_reader :id, :as => Integer
7
+ xml_reader :title
8
+ end
9
+
10
+ class Store
11
+ include ROXML
12
+ xml_reader :books, :from => 'books', :as => [Book]
13
+ end
14
+
15
+ class MyXml
16
+ include ROXML
17
+ xml_reader :store, :as => Store
18
+ end
19
+ end
20
+
21
+
22
+ describe ":as => []" do
23
+ context "with plural from" do
24
+ it "should accept the plural name as the name for each item" do
25
+ ArraySpec::MyXml.from_xml(%(
26
+ <myxml>
27
+ <store>
28
+ <books><id>1</id><title>first book</title></books>
29
+ <books><id>2</id><title>second book</title></books>
30
+ <books><id>3</id><title>third book</title></books>
31
+ </store>
32
+ </myxml>
33
+ )).store.books.size.should == 3
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,71 @@
1
+ require_relative './../spec_helper'
2
+
3
+ describe ROXML::XMLAttributeRef do
4
+ before do
5
+ @xml = ROXML::XML.parse_string %(
6
+ <myxml>
7
+ <node name="first" />
8
+ <node name="second" />
9
+ <node name="third" />
10
+ </myxml>)
11
+ end
12
+
13
+ context "plain vanilla" do
14
+ before do
15
+ @ref = ROXML::XMLAttributeRef.new(OpenStruct.new(:name => 'name', :wrapper => 'node', :array? => false), RoxmlObject.new)
16
+ end
17
+
18
+ it "should return one instance" do
19
+ @ref.value_in(@xml).should == "first"
20
+ end
21
+ it "should output one instance"
22
+ end
23
+
24
+ context "with :as => []" do
25
+ before do
26
+ @ref = ROXML::XMLAttributeRef.new(OpenStruct.new(:name => 'name', :wrapper => 'node', :array? => true), RoxmlObject.new)
27
+ end
28
+
29
+ it "should collect all instances" do
30
+ @ref.value_in(@xml).should == ["first", "second", "third"]
31
+ end
32
+
33
+ it "should output all instances" do
34
+ xml = ROXML::XML.new_node('myxml')
35
+ @ref.update_xml(xml, ["first", "second", "third"])
36
+ xml.to_s.squeeze(' ').should == @xml.root.to_s.squeeze(' ')
37
+ end
38
+ end
39
+
40
+ context "when the namespaces are different" do
41
+ before do
42
+ @xml = ROXML::XML.parse_string %(
43
+ <document>
44
+ <myxml xmlns="http://example.com/three" xmlns:one="http://example.com/one" xmlns:two="http://example.com/two">
45
+ <one:node name="first" />
46
+ <two:node name="second" />
47
+ <node name="third" />
48
+ </myxml>
49
+ </document>
50
+ )
51
+ end
52
+
53
+ context "with :namespace => '*'" do
54
+ before do
55
+ @ref = ROXML::XMLAttributeRef.new(OpenStruct.new(:name => 'name', :wrapper => 'node', :array? => true, :namespace => '*'), RoxmlObject.new)
56
+ end
57
+
58
+ it "should collect all instances" do
59
+ pending "Test bug?"
60
+ @ref.value_in(@xml).should == ["first", "second", "third"]
61
+ end
62
+
63
+ it "should output all instances with namespaces" do
64
+ pending "Full namespace write support"
65
+ xml = ROXML::XML.new_node('result')
66
+ @ref.update_xml(xml, ["first", "second", "third"])
67
+ xml.should == @xml.root
68
+ end
69
+ end
70
+ end
71
+ end