unhappymapper 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.md +479 -0
  2. data/TODO +0 -0
  3. data/lib/happymapper/attribute.rb +3 -0
  4. data/lib/happymapper/element.rb +3 -0
  5. data/lib/happymapper/item.rb +250 -0
  6. data/lib/happymapper/text_node.rb +3 -0
  7. data/lib/happymapper.rb +574 -0
  8. data/spec/fixtures/address.xml +8 -0
  9. data/spec/fixtures/ambigous_items.xml +22 -0
  10. data/spec/fixtures/analytics.xml +61 -0
  11. data/spec/fixtures/analytics_profile.xml +127 -0
  12. data/spec/fixtures/commit.xml +52 -0
  13. data/spec/fixtures/current_weather.xml +89 -0
  14. data/spec/fixtures/dictionary.xml +20 -0
  15. data/spec/fixtures/family_tree.xml +21 -0
  16. data/spec/fixtures/inagy.xml +86 -0
  17. data/spec/fixtures/lastfm.xml +355 -0
  18. data/spec/fixtures/multiple_namespaces.xml +170 -0
  19. data/spec/fixtures/multiple_primitives.xml +5 -0
  20. data/spec/fixtures/pita.xml +133 -0
  21. data/spec/fixtures/posts.xml +23 -0
  22. data/spec/fixtures/product_default_namespace.xml +17 -0
  23. data/spec/fixtures/product_no_namespace.xml +10 -0
  24. data/spec/fixtures/product_single_namespace.xml +10 -0
  25. data/spec/fixtures/quarters.xml +19 -0
  26. data/spec/fixtures/radar.xml +21 -0
  27. data/spec/fixtures/statuses.xml +422 -0
  28. data/spec/fixtures/subclass_namespace.xml +50 -0
  29. data/spec/happymapper_attribute_spec.rb +21 -0
  30. data/spec/happymapper_element_spec.rb +21 -0
  31. data/spec/happymapper_item_spec.rb +115 -0
  32. data/spec/happymapper_spec.rb +941 -0
  33. data/spec/happymapper_text_node_spec.rb +21 -0
  34. data/spec/happymapper_to_xml_namespaces_spec.rb +196 -0
  35. data/spec/happymapper_to_xml_spec.rb +196 -0
  36. data/spec/ignay_spec.rb +95 -0
  37. data/spec/spec_helper.rb +7 -0
  38. data/spec/xpath_spec.rb +88 -0
  39. metadata +150 -0
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe HappyMapper::Attribute do
4
+ describe "initialization" do
5
+ before do
6
+ @attr = HappyMapper::TextNode.new(:foo, String)
7
+ end
8
+
9
+ it 'should know that it is a text node' do
10
+ @attr.text_node?.should be_true
11
+ end
12
+
13
+ it 'should know that it is NOT an element' do
14
+ @attr.element?.should be_false
15
+ end
16
+
17
+ it 'should know that it is NOT an attribute' do
18
+ @attr.attribute?.should be_false
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,196 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ module ToXMLWithNamespaces
4
+
5
+ #
6
+ # Similar example as the to_xml but this time with namespacing
7
+ #
8
+ class Address
9
+ include HappyMapper
10
+
11
+ register_namespace 'address', 'http://www.company.com/address'
12
+ register_namespace 'country', 'http://www.company.com/country'
13
+
14
+ tag 'Address'
15
+ namespace 'address'
16
+
17
+ element :country, 'Country', :tag => 'country', :namespace => 'country'
18
+
19
+ attribute :location, String, :on_save => :when_saving_location
20
+
21
+ element :street, String
22
+ element :postcode, String
23
+ element :city, String
24
+
25
+ element :housenumber, String
26
+
27
+ #
28
+ # to_xml will default to the attr_accessor method and not the attribute,
29
+ # allowing for that to be overwritten
30
+ #
31
+ def housenumber
32
+ "[#{@housenumber}]"
33
+ end
34
+
35
+ def when_saving_location(loc)
36
+ loc + '-live'
37
+ end
38
+
39
+ #
40
+ # Write a empty element even if this is not specified
41
+ #
42
+ element :description, String, :state_when_nil => true
43
+
44
+ #
45
+ # Perform the on_save operation when saving
46
+ #
47
+ has_one :date_created, Time, :on_save => lambda {|time| DateTime.parse(time).strftime("%T %D") if time }
48
+
49
+ #
50
+ # Write multiple elements and call on_save when saving
51
+ #
52
+ has_many :dates_updated, Time, :on_save => lambda {|times|
53
+ times.compact.map {|time| DateTime.parse(time).strftime("%T %D") } if times }
54
+
55
+ #
56
+ # Class composition
57
+ #
58
+
59
+ def initialize(parameters)
60
+ parameters.each_pair do |property,value|
61
+ send("#{property}=",value) if respond_to?("#{property}=")
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ #
68
+ # Country is composed above the in Address class. Here is a demonstration
69
+ # of how to_xml will handle class composition as well as utilizing the tag
70
+ # value.
71
+ #
72
+ class Country
73
+ include HappyMapper
74
+
75
+ register_namespace 'countryName', 'http://www.company.com/countryName'
76
+
77
+ attribute :code, String, :tag => 'countryCode'
78
+ has_one :name, String, :tag => 'countryName', :namespace => 'countryName'
79
+
80
+ def initialize(parameters)
81
+ parameters.each_pair do |property,value|
82
+ send("#{property}=",value) if respond_to?("#{property}=")
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+
89
+ #
90
+ # This class is an example of a class that has a default namespace
91
+ #xmlns="urn:eventis:prodis:onlineapi:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
92
+ #
93
+ class Recipe
94
+ include HappyMapper
95
+
96
+ # this is the default namespace of the document
97
+ register_namespace 'xmlns', 'urn:eventis:prodis:onlineapi:1.0'
98
+ register_namespace 'xsi', "http://www.w3.org/2001/XMLSchema-instance"
99
+ register_namespace 'xsd', "http://www.w3.org/2001/XMLSchema"
100
+
101
+ has_many :ingredients, String
102
+
103
+ def initialize(parameters)
104
+ parameters.each_pair {|property,value| send("#{property}=",value) if respond_to?("#{property}=") }
105
+ end
106
+ end
107
+
108
+ describe HappyMapper do
109
+
110
+ context "#to_xml", "with namespaces" do
111
+
112
+ before(:all) do
113
+ address = Address.new('street' => 'Mockingbird Lane',
114
+ 'location' => 'Home',
115
+ 'housenumber' => '1313',
116
+ 'postcode' => '98103',
117
+ 'city' => 'Seattle',
118
+ 'country' => Country.new(:name => 'USA', :code => 'us'),
119
+ 'date_created' => '2011-01-01 15:00:00')
120
+
121
+
122
+ address.dates_updated = ["2011-01-01 16:01:00","2011-01-02 11:30:01"]
123
+
124
+ @address_xml = Nokogiri::XML(address.to_xml).root
125
+ end
126
+
127
+ it "should save elements" do
128
+
129
+ { 'street' => 'Mockingbird Lane',
130
+ 'postcode' => '98103',
131
+ 'city' => 'Seattle' }.each_pair do |property,value|
132
+
133
+ @address_xml.xpath("address:#{property}").text.should == value
134
+
135
+ end
136
+
137
+ end
138
+
139
+ it "should save the element with the result of a function call and not the value of the instance variable" do
140
+ @address_xml.xpath("address:housenumber").text.should == "[1313]"
141
+ end
142
+
143
+ it "should save attributes" do
144
+ @address_xml.xpath('@location').text.should == "Home-live"
145
+ end
146
+
147
+ context "state_when_nil options" do
148
+
149
+ it "should save an empty element" do
150
+ @address_xml.xpath('address:description').text.should == ""
151
+ end
152
+
153
+ end
154
+
155
+ context "on_save option" do
156
+
157
+ it "should save the result of the lambda" do
158
+ @address_xml.xpath('address:date_created').text.should == "15:00:00 01/01/11"
159
+ end
160
+
161
+ it "should save the result of a method" do
162
+ @address_xml.xpath('@location').text.should == "Home-live"
163
+ end
164
+
165
+ end
166
+
167
+ it "should save elements defined with the 'has_many' relationship" do
168
+ dates_updated = @address_xml.xpath('address:dates_updated')
169
+ dates_updated.length.should == 2
170
+ dates_updated.first.text.should == "16:01:00 01/01/11"
171
+ dates_updated.last.text.should == "11:30:01 01/02/11"
172
+ end
173
+
174
+ context "class types that also include HappyMapper mappings" do
175
+
176
+ it "should save attributes" do
177
+ @address_xml.xpath('country:country/@country:countryCode').text.should == "us"
178
+ end
179
+
180
+ it "should save elements" do
181
+ @address_xml.xpath('country:country/countryName:countryName').text.should == "USA"
182
+ end
183
+
184
+ end
185
+
186
+ end
187
+
188
+ context "#to_xml", "with a default namespace" do
189
+ it "should write the default namespace to xml without repeating xmlns" do
190
+ recipe = Recipe.new(:ingredients => ['One Cup Flour', 'Two Scoops of Lovin'])
191
+ recipe.to_xml.should =~ /xmlns=\"urn:eventis:prodis:onlineapi:1\.0\"/
192
+ end
193
+ end
194
+ end
195
+
196
+ end
@@ -0,0 +1,196 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ module ToXML
4
+
5
+ class Address
6
+ include HappyMapper
7
+
8
+ tag 'address'
9
+
10
+ attribute :location, String, :on_save => :when_saving_location
11
+
12
+ element :street, String
13
+ element :postcode, String
14
+ element :city, String
15
+
16
+ element :housenumber, String
17
+
18
+ attribute :modified, Boolean, :read_only => true
19
+ element :temporary, Boolean, :read_only => true
20
+ #
21
+ # to_xml will default to the attr_accessor method and not the attribute,
22
+ # allowing for that to be overwritten
23
+ #
24
+ def housenumber
25
+ "[#{@housenumber}]"
26
+ end
27
+
28
+ def when_saving_location(loc)
29
+ loc + "-live"
30
+ end
31
+
32
+ #
33
+ # Write a empty element even if this is not specified
34
+ #
35
+ element :description, String, :state_when_nil => true
36
+
37
+ #
38
+ # Perform the on_save operation when saving
39
+ #
40
+ has_one :date_created, Time, :on_save => lambda {|time| DateTime.parse(time).strftime("%T %D") if time }
41
+
42
+
43
+ #
44
+ # Execute the method with the same name
45
+
46
+ #
47
+ # Write multiple elements and call on_save when saving
48
+ #
49
+ has_many :dates_updated, Time, :on_save => lambda {|times|
50
+ times.compact.map {|time| DateTime.parse(time).strftime("%T %D") } if times }
51
+
52
+ #
53
+ # Class composition
54
+ #
55
+ element :country, 'Country', :tag => 'country'
56
+
57
+ def initialize(parameters)
58
+ parameters.each_pair do |property,value|
59
+ send("#{property}=",value) if respond_to?("#{property}=")
60
+ end
61
+ @modified = @temporary = true
62
+ end
63
+
64
+ end
65
+
66
+ #
67
+ # Country is composed above the in Address class. Here is a demonstration
68
+ # of how to_xml will handle class composition as well as utilizing the tag
69
+ # value.
70
+ #
71
+ class Country
72
+ include HappyMapper
73
+
74
+ attribute :code, String, :tag => 'countryCode'
75
+ has_one :name, String, :tag => 'countryName'
76
+ has_one :description, 'Description', :tag => 'description'
77
+
78
+ #
79
+ # This inner-class here is to demonstrate saving a text node
80
+ # and optional attributes
81
+ #
82
+ class Description
83
+ include HappyMapper
84
+ text_node :description, String
85
+ attribute :category, String, :tag => 'category'
86
+ attribute :rating, String, :tag => 'rating', :state_when_nil => true
87
+
88
+ def initialize(desc)
89
+ @description = desc
90
+ end
91
+ end
92
+
93
+ def initialize(parameters)
94
+ parameters.each_pair do |property,value|
95
+ send("#{property}=",value) if respond_to?("#{property}=")
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ describe HappyMapper do
102
+
103
+ context "#to_xml" do
104
+
105
+ before(:all) do
106
+ address = Address.new('street' => 'Mockingbird Lane',
107
+ 'location' => 'Home',
108
+ 'housenumber' => '1313',
109
+ 'postcode' => '98103',
110
+ 'city' => 'Seattle',
111
+ 'country' => Country.new(:name => 'USA', :code => 'us', :empty_code => nil,
112
+ :description => Country::Description.new("A lovely country") ),
113
+ 'date_created' => '2011-01-01 15:00:00')
114
+
115
+
116
+ address.dates_updated = ["2011-01-01 16:01:00","2011-01-02 11:30:01"]
117
+
118
+ @address_xml = Nokogiri::XML(address.to_xml).root
119
+ end
120
+
121
+ it "should save elements" do
122
+ { 'street' => 'Mockingbird Lane',
123
+ 'postcode' => '98103',
124
+ 'city' => 'Seattle' }.each_pair do |property,value|
125
+
126
+ @address_xml.xpath("#{property}").text.should == value
127
+
128
+ end
129
+ end
130
+
131
+ it "should save the element with the result of a function call and not the value of the instance variable" do
132
+ @address_xml.xpath("housenumber").text.should == "[1313]"
133
+ end
134
+
135
+ it "should not save elements marked as read_only" do
136
+ @address_xml.xpath('temporary').should be_empty
137
+ end
138
+
139
+ it "should save attribues" do
140
+ @address_xml.xpath('@location').text.should == "Home-live"
141
+ end
142
+
143
+ it "should not save attributes marked as read_only" do
144
+ @address_xml.xpath("@modified").should be_empty
145
+ end
146
+
147
+ context "state_when_nil option" do
148
+
149
+ it "should save an empty element" do
150
+ @address_xml.xpath('description').text.should == ""
151
+ end
152
+
153
+ end
154
+
155
+ context "on_save option" do
156
+
157
+ it "should save the result of the lambda" do
158
+ @address_xml.xpath('date_created').text.should == "15:00:00 01/01/11"
159
+ end
160
+
161
+ it "should save the result of a method" do
162
+ @address_xml.xpath('@location').text.should == "Home-live"
163
+ end
164
+
165
+ end
166
+
167
+
168
+ it "should save elements defined with the 'has_many' relationship" do
169
+ dates_updated = @address_xml.xpath('dates_updated')
170
+ dates_updated.length.should == 2
171
+ dates_updated.first.text.should == "16:01:00 01/01/11"
172
+ dates_updated.last.text.should == "11:30:01 01/02/11"
173
+ end
174
+
175
+ context "class types that also contain HappyMapper mappings" do
176
+
177
+ it "should save attributes" do
178
+ @address_xml.xpath('country/@countryCode').text.should == "us"
179
+ end
180
+
181
+ it "should save elements" do
182
+ @address_xml.xpath('country/countryName').text.should == "USA"
183
+ end
184
+
185
+ it "should save elements" do
186
+ @address_xml.xpath('country/description').text.should == "A lovely country"
187
+ end
188
+
189
+ end
190
+
191
+ end
192
+
193
+
194
+ end
195
+
196
+ end
@@ -0,0 +1,95 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ class CatalogTree
4
+ include HappyMapper
5
+
6
+ tag 'CatalogTree'
7
+ register_namespace 'xmlns', 'urn:eventis:prodis:onlineapi:1.0'
8
+ register_namespace 'xsi', 'http://www.w3.org/2001/XMLSchema-instance'
9
+ register_namespace 'xsd', 'http://www.w3.org/2001/XMLSchema'
10
+
11
+ attribute :code, String
12
+
13
+ has_many :nodes, 'CatalogNode', :tag => 'Node', :xpath => '.'
14
+
15
+ end
16
+
17
+
18
+ class CatalogNode
19
+ include HappyMapper
20
+
21
+ tag 'Node'
22
+
23
+ attribute :back_office_id, String, :tag => 'vodBackOfficeId'
24
+
25
+ has_one :name, String, :tag => 'Name'
26
+ # other important fields
27
+
28
+ has_many :translations, 'CatalogNode::Translations', :tag => 'Translation', :xpath => 'child::*'
29
+
30
+ class Translations
31
+ include HappyMapper
32
+ tag 'Translation'
33
+
34
+ attribute :language, String, :tag => 'Language'
35
+ has_one :name, String, :tag => 'Name'
36
+
37
+ end
38
+
39
+ has_many :nodes, CatalogNode, :tag => 'Node', :xpath => 'child::*'
40
+
41
+ end
42
+
43
+ describe HappyMapper do
44
+
45
+ it "should not be nil" do
46
+ catalog_tree.should_not be_nil
47
+ end
48
+
49
+ it "should have the attribute code" do
50
+ catalog_tree.code.should == "NLD"
51
+ end
52
+
53
+ it "should have many nodes" do
54
+ catalog_tree.nodes.should_not be_empty
55
+ catalog_tree.nodes.length.should == 2
56
+ end
57
+
58
+ context "first node" do
59
+
60
+ it "should have a name" do
61
+ first_node.name.should == "Parent 1"
62
+ end
63
+
64
+ it "should have translations" do
65
+ first_node.translations.length.should == 2
66
+
67
+ first_node.translations.first.language.should == "en-GB"
68
+
69
+ first_node.translations.last.name.should == "Parent 1 de"
70
+ end
71
+
72
+ it "should have subnodes" do
73
+ first_node.nodes.should be_kind_of(Enumerable)
74
+ first_node.nodes.should_not be_empty
75
+ first_node.nodes.length.should == 1
76
+ end
77
+
78
+ it "first node - first node name" do
79
+ first_node.nodes.first.name.should == "First"
80
+ end
81
+
82
+ def first_node
83
+ @first_node = catalog_tree.nodes.first
84
+ end
85
+
86
+ end
87
+
88
+
89
+ def catalog_tree ; @catalog_tree ; end
90
+
91
+ before(:all) do
92
+ xml_reference = "#{File.dirname(__FILE__)}/fixtures/inagy.xml"
93
+ @catalog_tree = CatalogTree.parse(File.read(xml_reference), :single => true)
94
+ end
95
+ end
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'happymapper')
4
+
5
+ def fixture_file(filename)
6
+ File.read(File.dirname(__FILE__) + "/fixtures/#{filename}")
7
+ end
@@ -0,0 +1,88 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ test_xml = %{
4
+ <rss>
5
+ <amazing:item xmlns:amazing="http://www.amazing.com/amazing" xmlns:different="http://www.different.com/different">
6
+ <amazing:title>Test XML</amazing:/title>
7
+ <different:link href="different_link" />
8
+ <amazing:link href="link_to_resources" />
9
+ <amazing:subitem>
10
+ <amazing:detail>I want to parse this</amazing:detail>
11
+ <amazing:more first="this one">more 1</amazing:more>
12
+ <amazing:more alternative="another one">more 2</amazing:more>
13
+ </amazing:subitem>
14
+ <amazing:baby>
15
+ <amazing:name>Jumbo</amazing:name>
16
+ </amazing:baby>
17
+ </amazing:item>
18
+ </rss>
19
+ }
20
+
21
+ class Item
22
+ include HappyMapper
23
+
24
+ tag 'item'
25
+ namespace 'amazing'
26
+
27
+ element :title, String
28
+ attribute :link, String, :xpath => 'amazing:link/@href'
29
+ has_one :different_link, String, :xpath => 'different:link/@href'
30
+ element :detail, String, :xpath => 'amazing:subitem/amazing:detail'
31
+ has_many :more_details_text, String, :xpath => 'amazing:subitem/amazing:more'
32
+ has_many :more_details, String, :xpath => 'amazing:subitem/amazing:more/@first|amazing:subitem/amazing:more/@alternative'
33
+ has_many :more_details_alternative, String, :xpath => 'amazing:subitem/amazing:more/@*'
34
+
35
+ has_one :baby, 'Baby', :name => 'baby', :namespace => 'amazing'
36
+
37
+ end
38
+
39
+ class Baby
40
+ include HappyMapper
41
+
42
+ has_one :name, String
43
+ end
44
+
45
+ describe HappyMapper do
46
+
47
+ it "should have a title" do
48
+ @item.title.should == "Test XML"
49
+ end
50
+
51
+ it "should find the link href value" do
52
+ @item.link.should == 'link_to_resources'
53
+ end
54
+
55
+ it "should find the link href value" do
56
+ @item.different_link.should == 'different_link'
57
+ end
58
+
59
+ it "should find this subitem based on the xpath" do
60
+ @item.detail.should == 'I want to parse this'
61
+ end
62
+
63
+ it "should find the subitems based on the xpath" do
64
+ @item.more_details_text.length.should == 2
65
+ @item.more_details_text.first.should == "more 1"
66
+ @item.more_details_text.last.should == "more 2"
67
+ end
68
+
69
+ it "should find the subitems based on the xpath" do
70
+ @item.more_details.length.should == 2
71
+ @item.more_details.first.should == "this one"
72
+ @item.more_details.last.should == "another one"
73
+ end
74
+
75
+ it "should find the subitems based on the xpath" do
76
+ @item.more_details.length.should == 2
77
+ @item.more_details_alternative.first.should == "this one"
78
+ @item.more_details_alternative.last.should == "another one"
79
+ end
80
+ it "should have a baby name" do
81
+ @item.baby.name.should == "Jumbo"
82
+ end
83
+
84
+ before(:all) do
85
+ @item = Item.parse(test_xml,:single => true)
86
+ end
87
+
88
+ end