woahdae-consumer 0.8.1

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 (70) hide show
  1. data/History.txt +4 -0
  2. data/LICENSE +20 -0
  3. data/Manifest.txt +73 -0
  4. data/PostInstall.txt +8 -0
  5. data/README.rdoc +53 -0
  6. data/Rakefile +30 -0
  7. data/app_generators/consumer/USAGE +14 -0
  8. data/app_generators/consumer/consumer_generator.rb +89 -0
  9. data/app_generators/consumer/templates/LICENSE +20 -0
  10. data/app_generators/consumer/templates/README.rdoc +3 -0
  11. data/app_generators/consumer/templates/Rakefile +57 -0
  12. data/app_generators/consumer/templates/TODO +4 -0
  13. data/app_generators/consumer/templates/config/config.yml +2 -0
  14. data/app_generators/consumer/templates/config/config.yml.sample +1 -0
  15. data/app_generators/consumer/templates/lib/base.rb +6 -0
  16. data/app_generators/consumer/templates/rails/init.rb +1 -0
  17. data/app_generators/consumer/templates/script/destroy +14 -0
  18. data/app_generators/consumer/templates/script/generate +14 -0
  19. data/app_generators/consumer/templates/spec/spec_helper.rb +11 -0
  20. data/bin/consumer +17 -0
  21. data/config/website.yml.sample +2 -0
  22. data/consumer.gemspec +48 -0
  23. data/consumer_generators/request/USAGE +25 -0
  24. data/consumer_generators/request/request_generator.rb +94 -0
  25. data/consumer_generators/request/templates/lib/request.rb +55 -0
  26. data/consumer_generators/request/templates/lib/response.rb +12 -0
  27. data/consumer_generators/request/templates/spec/request_spec.rb +27 -0
  28. data/consumer_generators/request/templates/spec/response_spec.rb +10 -0
  29. data/consumer_generators/request/templates/spec/xml/response.xml +0 -0
  30. data/examples/active_record/README.txt +1 -0
  31. data/examples/active_record/ar_spec.rb +33 -0
  32. data/examples/active_record/database.sqlite +0 -0
  33. data/examples/active_record/environment.rb +15 -0
  34. data/examples/active_record/migration.rb +21 -0
  35. data/examples/active_record/models/book.rb +13 -0
  36. data/examples/active_record/models/contributor.rb +12 -0
  37. data/examples/active_record/xml/book.xml +6 -0
  38. data/examples/active_record/xml/book_with_contributors.xml +11 -0
  39. data/examples/active_record/xml/contributor.xml +3 -0
  40. data/examples/active_record/xml/contributor_with_books.xml +19 -0
  41. data/examples/shipping/environment.rb +3 -0
  42. data/examples/shipping/rate.rb +15 -0
  43. data/examples/shipping/shipping.yml.sample +8 -0
  44. data/examples/shipping/shipping_spec.rb +27 -0
  45. data/examples/shipping/ups_rate_request.rb +182 -0
  46. data/examples/shipping/ups_rate_response.xml +340 -0
  47. data/lib/consumer/helper.rb +111 -0
  48. data/lib/consumer/mapping.rb +184 -0
  49. data/lib/consumer/request.rb +280 -0
  50. data/lib/consumer.rb +28 -0
  51. data/script/console +10 -0
  52. data/script/destroy +14 -0
  53. data/script/generate +14 -0
  54. data/script/txt2html +71 -0
  55. data/spec/helper_spec.rb +136 -0
  56. data/spec/mapping_spec.rb +94 -0
  57. data/spec/request_spec.rb +75 -0
  58. data/spec/spec.opts +1 -0
  59. data/spec/spec_helper.rb +12 -0
  60. data/spec/xml/rate_response.xml +14 -0
  61. data/spec/xml/rate_response_error.xml +35 -0
  62. data/tasks/rspec.rake +21 -0
  63. data/test/test_consumer_generator.rb +68 -0
  64. data/test/test_generator_helper.rb +29 -0
  65. data/website/index.html +11 -0
  66. data/website/index.txt +81 -0
  67. data/website/javascripts/rounded_corners_lite.inc.js +285 -0
  68. data/website/stylesheets/screen.css +159 -0
  69. data/website/template.html.erb +50 -0
  70. metadata +180 -0
@@ -0,0 +1,340 @@
1
+ <?xml version="1.0"?>
2
+ <RatingServiceSelectionResponse>
3
+ <Response>
4
+ <TransactionReference>
5
+ <CustomerContext>RatingandService</CustomerContext>
6
+ <XpciVersion>1.0001</XpciVersion>
7
+ </TransactionReference>
8
+ <ResponseStatusCode>1</ResponseStatusCode>
9
+ <ResponseStatusDescription>Success</ResponseStatusDescription>
10
+ </Response>
11
+ <RatedShipment>
12
+ <Service>
13
+ <Code>03</Code>
14
+ </Service>
15
+ <RatedShipmentWarning>Yourinvoicemayvaryfromthedisplayedreferencerates</RatedShipmentWarning>
16
+ <BillingWeight>
17
+ <UnitOfMeasurement>
18
+ <Code>LBS</Code>
19
+ </UnitOfMeasurement>
20
+ <Weight>5.0</Weight>
21
+ </BillingWeight>
22
+ <TransportationCharges>
23
+ <CurrencyCode>USD</CurrencyCode>
24
+ <MonetaryValue>5.03</MonetaryValue>
25
+ </TransportationCharges>
26
+ <ServiceOptionsCharges>
27
+ <CurrencyCode>USD</CurrencyCode>
28
+ <MonetaryValue>0.00</MonetaryValue>
29
+ </ServiceOptionsCharges>
30
+ <TotalCharges>
31
+ <CurrencyCode>USD</CurrencyCode>
32
+ <MonetaryValue>5.03</MonetaryValue>
33
+ </TotalCharges>
34
+ <GuaranteedDaysToDelivery/>
35
+ <ScheduledDeliveryTime/>
36
+ <RatedPackage>
37
+ <TransportationCharges>
38
+ <CurrencyCode>USD</CurrencyCode>
39
+ <MonetaryValue>5.03</MonetaryValue>
40
+ </TransportationCharges>
41
+ <ServiceOptionsCharges>
42
+ <CurrencyCode>USD</CurrencyCode>
43
+ <MonetaryValue>0.00</MonetaryValue>
44
+ </ServiceOptionsCharges>
45
+ <TotalCharges>
46
+ <CurrencyCode>USD</CurrencyCode>
47
+ <MonetaryValue>5.03</MonetaryValue>
48
+ </TotalCharges>
49
+ <Weight>5.0</Weight>
50
+ <BillingWeight>
51
+ <UnitOfMeasurement>
52
+ <Code>LBS</Code>
53
+ </UnitOfMeasurement>
54
+ <Weight>5.0</Weight>
55
+ </BillingWeight>
56
+ </RatedPackage>
57
+ </RatedShipment>
58
+ <RatedShipment>
59
+ <Service>
60
+ <Code>12</Code>
61
+ </Service>
62
+ <RatedShipmentWarning>Yourinvoicemayvaryfromthedisplayedreferencerates</RatedShipmentWarning>
63
+ <BillingWeight>
64
+ <UnitOfMeasurement>
65
+ <Code>LBS</Code>
66
+ </UnitOfMeasurement>
67
+ <Weight>5.0</Weight>
68
+ </BillingWeight>
69
+ <TransportationCharges>
70
+ <CurrencyCode>USD</CurrencyCode>
71
+ <MonetaryValue>9.12</MonetaryValue>
72
+ </TransportationCharges>
73
+ <ServiceOptionsCharges>
74
+ <CurrencyCode>USD</CurrencyCode>
75
+ <MonetaryValue>0.00</MonetaryValue>
76
+ </ServiceOptionsCharges>
77
+ <TotalCharges>
78
+ <CurrencyCode>USD</CurrencyCode>
79
+ <MonetaryValue>9.12</MonetaryValue>
80
+ </TotalCharges>
81
+ <GuaranteedDaysToDelivery>3</GuaranteedDaysToDelivery>
82
+ <ScheduledDeliveryTime/>
83
+ <RatedPackage>
84
+ <TransportationCharges>
85
+ <CurrencyCode>USD</CurrencyCode>
86
+ <MonetaryValue>9.12</MonetaryValue>
87
+ </TransportationCharges>
88
+ <ServiceOptionsCharges>
89
+ <CurrencyCode>USD</CurrencyCode>
90
+ <MonetaryValue>0.00</MonetaryValue>
91
+ </ServiceOptionsCharges>
92
+ <TotalCharges>
93
+ <CurrencyCode>USD</CurrencyCode>
94
+ <MonetaryValue>9.12</MonetaryValue>
95
+ </TotalCharges>
96
+ <Weight>5.0</Weight>
97
+ <BillingWeight>
98
+ <UnitOfMeasurement>
99
+ <Code>LBS</Code>
100
+ </UnitOfMeasurement>
101
+ <Weight>5.0</Weight>
102
+ </BillingWeight>
103
+ </RatedPackage>
104
+ </RatedShipment>
105
+ <RatedShipment>
106
+ <Service>
107
+ <Code>59</Code>
108
+ </Service>
109
+ <RatedShipmentWarning>Yourinvoicemayvaryfromthedisplayedreferencerates</RatedShipmentWarning>
110
+ <BillingWeight>
111
+ <UnitOfMeasurement>
112
+ <Code>LBS</Code>
113
+ </UnitOfMeasurement>
114
+ <Weight>5.0</Weight>
115
+ </BillingWeight>
116
+ <TransportationCharges>
117
+ <CurrencyCode>USD</CurrencyCode>
118
+ <MonetaryValue>14.91</MonetaryValue>
119
+ </TransportationCharges>
120
+ <ServiceOptionsCharges>
121
+ <CurrencyCode>USD</CurrencyCode>
122
+ <MonetaryValue>0.00</MonetaryValue>
123
+ </ServiceOptionsCharges>
124
+ <TotalCharges>
125
+ <CurrencyCode>USD</CurrencyCode>
126
+ <MonetaryValue>14.91</MonetaryValue>
127
+ </TotalCharges>
128
+ <GuaranteedDaysToDelivery>2</GuaranteedDaysToDelivery>
129
+ <ScheduledDeliveryTime>10:30A.M.</ScheduledDeliveryTime>
130
+ <RatedPackage>
131
+ <TransportationCharges>
132
+ <CurrencyCode>USD</CurrencyCode>
133
+ <MonetaryValue>14.91</MonetaryValue>
134
+ </TransportationCharges>
135
+ <ServiceOptionsCharges>
136
+ <CurrencyCode>USD</CurrencyCode>
137
+ <MonetaryValue>0.00</MonetaryValue>
138
+ </ServiceOptionsCharges>
139
+ <TotalCharges>
140
+ <CurrencyCode>USD</CurrencyCode>
141
+ <MonetaryValue>14.91</MonetaryValue>
142
+ </TotalCharges>
143
+ <Weight>5.0</Weight>
144
+ <BillingWeight>
145
+ <UnitOfMeasurement>
146
+ <Code>LBS</Code>
147
+ </UnitOfMeasurement>
148
+ <Weight>5.0</Weight>
149
+ </BillingWeight>
150
+ </RatedPackage>
151
+ </RatedShipment>
152
+ <RatedShipment>
153
+ <Service>
154
+ <Code>02</Code>
155
+ </Service>
156
+ <RatedShipmentWarning>Yourinvoicemayvaryfromthedisplayedreferencerates</RatedShipmentWarning>
157
+ <BillingWeight>
158
+ <UnitOfMeasurement>
159
+ <Code>LBS</Code>
160
+ </UnitOfMeasurement>
161
+ <Weight>5.0</Weight>
162
+ </BillingWeight>
163
+ <TransportationCharges>
164
+ <CurrencyCode>USD</CurrencyCode>
165
+ <MonetaryValue>12.79</MonetaryValue>
166
+ </TransportationCharges>
167
+ <ServiceOptionsCharges>
168
+ <CurrencyCode>USD</CurrencyCode>
169
+ <MonetaryValue>0.00</MonetaryValue>
170
+ </ServiceOptionsCharges>
171
+ <TotalCharges>
172
+ <CurrencyCode>USD</CurrencyCode>
173
+ <MonetaryValue>12.79</MonetaryValue>
174
+ </TotalCharges>
175
+ <GuaranteedDaysToDelivery>2</GuaranteedDaysToDelivery>
176
+ <ScheduledDeliveryTime/>
177
+ <RatedPackage>
178
+ <TransportationCharges>
179
+ <CurrencyCode>USD</CurrencyCode>
180
+ <MonetaryValue>12.79</MonetaryValue>
181
+ </TransportationCharges>
182
+ <ServiceOptionsCharges>
183
+ <CurrencyCode>USD</CurrencyCode>
184
+ <MonetaryValue>0.00</MonetaryValue>
185
+ </ServiceOptionsCharges>
186
+ <TotalCharges>
187
+ <CurrencyCode>USD</CurrencyCode>
188
+ <MonetaryValue>12.79</MonetaryValue>
189
+ </TotalCharges>
190
+ <Weight>5.0</Weight>
191
+ <BillingWeight>
192
+ <UnitOfMeasurement>
193
+ <Code>LBS</Code>
194
+ </UnitOfMeasurement>
195
+ <Weight>5.0</Weight>
196
+ </BillingWeight>
197
+ </RatedPackage>
198
+ </RatedShipment>
199
+ <RatedShipment>
200
+ <Service>
201
+ <Code>13</Code>
202
+ </Service>
203
+ <RatedShipmentWarning>Yourinvoicemayvaryfromthedisplayedreferencerates</RatedShipmentWarning>
204
+ <BillingWeight>
205
+ <UnitOfMeasurement>
206
+ <Code>LBS</Code>
207
+ </UnitOfMeasurement>
208
+ <Weight>5.0</Weight>
209
+ </BillingWeight>
210
+ <TransportationCharges>
211
+ <CurrencyCode>USD</CurrencyCode>
212
+ <MonetaryValue>23.71</MonetaryValue>
213
+ </TransportationCharges>
214
+ <ServiceOptionsCharges>
215
+ <CurrencyCode>USD</CurrencyCode>
216
+ <MonetaryValue>0.00</MonetaryValue>
217
+ </ServiceOptionsCharges>
218
+ <TotalCharges>
219
+ <CurrencyCode>USD</CurrencyCode>
220
+ <MonetaryValue>23.71</MonetaryValue>
221
+ </TotalCharges>
222
+ <GuaranteedDaysToDelivery>1</GuaranteedDaysToDelivery>
223
+ <ScheduledDeliveryTime>3:00P.M.</ScheduledDeliveryTime>
224
+ <RatedPackage>
225
+ <TransportationCharges>
226
+ <CurrencyCode>USD</CurrencyCode>
227
+ <MonetaryValue>23.71</MonetaryValue>
228
+ </TransportationCharges>
229
+ <ServiceOptionsCharges>
230
+ <CurrencyCode>USD</CurrencyCode>
231
+ <MonetaryValue>0.00</MonetaryValue>
232
+ </ServiceOptionsCharges>
233
+ <TotalCharges>
234
+ <CurrencyCode>USD</CurrencyCode>
235
+ <MonetaryValue>23.71</MonetaryValue>
236
+ </TotalCharges>
237
+ <Weight>5.0</Weight>
238
+ <BillingWeight>
239
+ <UnitOfMeasurement>
240
+ <Code>LBS</Code>
241
+ </UnitOfMeasurement>
242
+ <Weight>5.0</Weight>
243
+ </BillingWeight>
244
+ </RatedPackage>
245
+ </RatedShipment>
246
+ <RatedShipment>
247
+ <Service>
248
+ <Code>14</Code>
249
+ </Service>
250
+ <RatedShipmentWarning>Yourinvoicemayvaryfromthedisplayedreferencerates</RatedShipmentWarning>
251
+ <BillingWeight>
252
+ <UnitOfMeasurement>
253
+ <Code>LBS</Code>
254
+ </UnitOfMeasurement>
255
+ <Weight>5.0</Weight>
256
+ </BillingWeight>
257
+ <TransportationCharges>
258
+ <CurrencyCode>USD</CurrencyCode>
259
+ <MonetaryValue>64.25</MonetaryValue>
260
+ </TransportationCharges>
261
+ <ServiceOptionsCharges>
262
+ <CurrencyCode>USD</CurrencyCode>
263
+ <MonetaryValue>0.00</MonetaryValue>
264
+ </ServiceOptionsCharges>
265
+ <TotalCharges>
266
+ <CurrencyCode>USD</CurrencyCode>
267
+ <MonetaryValue>64.25</MonetaryValue>
268
+ </TotalCharges>
269
+ <GuaranteedDaysToDelivery>1</GuaranteedDaysToDelivery>
270
+ <ScheduledDeliveryTime>8:00A.M.</ScheduledDeliveryTime>
271
+ <RatedPackage>
272
+ <TransportationCharges>
273
+ <CurrencyCode>USD</CurrencyCode>
274
+ <MonetaryValue>64.25</MonetaryValue>
275
+ </TransportationCharges>
276
+ <ServiceOptionsCharges>
277
+ <CurrencyCode>USD</CurrencyCode>
278
+ <MonetaryValue>0.00</MonetaryValue>
279
+ </ServiceOptionsCharges>
280
+ <TotalCharges>
281
+ <CurrencyCode>USD</CurrencyCode>
282
+ <MonetaryValue>64.25</MonetaryValue>
283
+ </TotalCharges>
284
+ <Weight>5.0</Weight>
285
+ <BillingWeight>
286
+ <UnitOfMeasurement>
287
+ <Code>LBS</Code>
288
+ </UnitOfMeasurement>
289
+ <Weight>5.0</Weight>
290
+ </BillingWeight>
291
+ </RatedPackage>
292
+ </RatedShipment>
293
+ <RatedShipment>
294
+ <Service>
295
+ <Code>01</Code>
296
+ </Service>
297
+ <RatedShipmentWarning>Yourinvoicemayvaryfromthedisplayedreferencerates</RatedShipmentWarning>
298
+ <BillingWeight>
299
+ <UnitOfMeasurement>
300
+ <Code>LBS</Code>
301
+ </UnitOfMeasurement>
302
+ <Weight>5.0</Weight>
303
+ </BillingWeight>
304
+ <TransportationCharges>
305
+ <CurrencyCode>USD</CurrencyCode>
306
+ <MonetaryValue>27.63</MonetaryValue>
307
+ </TransportationCharges>
308
+ <ServiceOptionsCharges>
309
+ <CurrencyCode>USD</CurrencyCode>
310
+ <MonetaryValue>0.00</MonetaryValue>
311
+ </ServiceOptionsCharges>
312
+ <TotalCharges>
313
+ <CurrencyCode>USD</CurrencyCode>
314
+ <MonetaryValue>27.63</MonetaryValue>
315
+ </TotalCharges>
316
+ <GuaranteedDaysToDelivery>1</GuaranteedDaysToDelivery>
317
+ <ScheduledDeliveryTime>10:30A.M.</ScheduledDeliveryTime>
318
+ <RatedPackage>
319
+ <TransportationCharges>
320
+ <CurrencyCode>USD</CurrencyCode>
321
+ <MonetaryValue>27.63</MonetaryValue>
322
+ </TransportationCharges>
323
+ <ServiceOptionsCharges>
324
+ <CurrencyCode>USD</CurrencyCode>
325
+ <MonetaryValue>0.00</MonetaryValue>
326
+ </ServiceOptionsCharges>
327
+ <TotalCharges>
328
+ <CurrencyCode>USD</CurrencyCode>
329
+ <MonetaryValue>27.63</MonetaryValue>
330
+ </TotalCharges>
331
+ <Weight>5.0</Weight>
332
+ <BillingWeight>
333
+ <UnitOfMeasurement>
334
+ <Code>LBS</Code>
335
+ </UnitOfMeasurement>
336
+ <Weight>5.0</Weight>
337
+ </BillingWeight>
338
+ </RatedPackage>
339
+ </RatedShipment>
340
+ </RatingServiceSelectionResponse>
@@ -0,0 +1,111 @@
1
+ module Consumer::Helper
2
+ # if you pass in a newline-less glob of xml it'll return an indented copy
3
+ # for improved readability.
4
+ def self.tidy(xml)
5
+ xml = xml.clone # avoid modifying @response_xml due to pass by reference
6
+
7
+ # remove all formatting to start from a common base
8
+ xml.gsub!(/\>[\t\n\r ]*\</, "><")
9
+ # replace empty tag pairs with <tag/>
10
+ xml.gsub!(/\<(\w*?)\>\<\/\1\>/, "<\\1/>")
11
+ # add in newlines after >, and sometimse before <
12
+ xml.gsub!(/\>\</,">\n<")
13
+
14
+ declaration = /\<\?xml/
15
+ start_tag = /\<[^\/]+?\>[\n\t\r ]+/
16
+ end_tag = /^[\t ]*\<\//
17
+
18
+ ## add appropriate spacing before each newline.
19
+ tab = 0
20
+ siblings = false
21
+ first_tag = true
22
+ return xml.collect do |line|
23
+ next line if line =~ declaration
24
+
25
+ # calculate the indentation.
26
+ # In general we want to add spacing if it's a start tag or leaf node, and
27
+ # remove spacing if it's an end tag. The only times we don't want to add
28
+ # spacing is if it's the very first node or the previous node was a sibling
29
+ # instead of a parent.
30
+ if line =~ end_tag
31
+ tab -= 1
32
+ else
33
+ tab += 1 unless first_tag || siblings
34
+ end
35
+
36
+ # if the line is a start tag, the next lines will no longer be siblings
37
+ siblings = line =~ start_tag ? false : true
38
+ first_tag = false
39
+
40
+ # return line with appropriate amount of preceding spaces #
41
+ line = " " * (tab > 0 ? tab : 0) + line
42
+ end.join << "\n"
43
+ end
44
+
45
+ # returns a copy of the xml without empty nodes. Also removes internal
46
+ # (non-leaf) nodes that only contain empty leaves. Nodes containing only
47
+ # whitespace characters (space, newline, tab, and return) are considered empty.
48
+ def self.compact_xml(xml)
49
+ old_xml = xml
50
+ loop do
51
+ new_xml = old_xml.gsub(/\<(\w*?)\>[ \t\r\n]*\<\/\1\>\n?/, "")
52
+ if old_xml == new_xml # nothing was changed
53
+ return new_xml
54
+ else # something changed, so we'll go through it again
55
+ old_xml = new_xml
56
+ end
57
+ end
58
+ end
59
+
60
+ # returns a hash of defaults if +self.yaml_defaults+ is defined and the file
61
+ # defined there exists, empty hash otherwise.
62
+ #
63
+ # Also takes a namespace, i.e. a sub-hash, and when given a namespace it also
64
+ # enables a global namespace called 'all'. Thus if you had the yaml:
65
+ #
66
+ # <pre>
67
+ # all:
68
+ # my_name: Buster
69
+ # greetings:
70
+ # hello: world
71
+ # other:
72
+ # irrelevant: data
73
+ # </pre>
74
+ #
75
+ # a namespace of 'greetings' would return the hash:
76
+ #
77
+ # <code>
78
+ # {"my_name" => "Buster", "hello" => "world"}
79
+ # </code>
80
+ #
81
+ # You don't have to use the global namespace, but if you do, it will be included
82
+ # everywhere.
83
+ def self.hash_from_yaml(base_path, file, namespace = nil)
84
+ begin
85
+ hash = (base_path && file) ? YAML.load(File.read(File.join(base_path, file))) : {}
86
+ rescue => e
87
+ raise ArgumentError, "YAML load error: #{e.message}"
88
+ end
89
+
90
+ return {} if !hash
91
+
92
+ if namespace
93
+ global = hash["all"] || {}
94
+ namespaced_hash = hash[namespace] || {}
95
+ hash = global.merge(namespaced_hash)
96
+ end
97
+
98
+ return hash
99
+ end
100
+
101
+ def self.http_from_url(url)
102
+ uri = URI.parse url
103
+ http = Net::HTTP.new uri.host, uri.port
104
+ if uri.port == 443
105
+ http.use_ssl = true
106
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
107
+ end
108
+ return http, uri
109
+ end
110
+
111
+ end
@@ -0,0 +1,184 @@
1
+ module Consumer::Mapping
2
+ def self.included(base)
3
+ base.extend ClassMethods
4
+ end
5
+
6
+ ### our lone instance method ###
7
+
8
+ ##
9
+ # Same as doing:
10
+ #
11
+ # <code>self.association(s) = Association.from_xml(xml)</code>
12
+ # === Behaviors
13
+ # * sets the association to a call to Klass.from_xml
14
+ # === Parameters
15
+ # [+xml+] String of xml
16
+ # [+association+] The association name (i.e. self.something)
17
+ # [+klass+] The association class (i.e. Something.from_xml).
18
+ # Defaults to singular, capitalized +association+.
19
+ # === Returns
20
+ # Nothing in particular
21
+ def association_from_xml(xml, association, klass = nil)
22
+ klass ||= association.to_s.capitalize.singularize.constantize
23
+
24
+ association_instance = klass.from_xml(xml)
25
+ return if association_instance.nil?
26
+
27
+ self.send(association.to_s + "=", association_instance)
28
+ end
29
+
30
+ module ClassMethods
31
+ # Creates a new mapping hash in self.maps
32
+ # === Parameters
33
+ # The parameters here are used to add a new mapping hash to self.maps, which
34
+ # in turn is used by from_xml_via_map to instantiate new objects
35
+ # [+first_or_all+] If +:all+, from_xml_via_map will return an array instead
36
+ # of a single instance (i.e. sets map[:all] to true)
37
+ # [+base_path+] A fully qualified xpath, such as //SomethingResponse/bah.
38
+ # Also, uniquely identifies the map for a particular xml
39
+ # response in the case of multiple maps.
40
+ # [+registry+] A hash of :attribute => 'xpath' pairs. Xpaths are
41
+ # relative to +base_path+, but fully qualified xpaths are
42
+ # also valid.
43
+ #
44
+ # For example, if the base path was //Response/Bah and the
45
+ # registry was {:foo => "FooElm"}, then from_xml_via_map
46
+ # will set instance.foo to the value at
47
+ # //Response/Bah/FooElm in the xml. However, if you set
48
+ # {:foo => "//Response/Woot/Ack"}, then it will pull the
49
+ # value from //Response/Woot/Ack (not
50
+ # //Response/Bah/Response/Woot/Ack).
51
+ # [+options+] Valid options are:
52
+ # * +:include+ - sets map[:associations], which is an
53
+ # array of parameters to association_from_xml
54
+ # [+block+] In from_xml_via_map this gets called with the new
55
+ # instance as the last step of instantiation.
56
+ # === Returns
57
+ # The newly created map
58
+ # === Raises
59
+ # * RuntimeError if the base path is already defined in another mapping
60
+ # in the class
61
+ def map(first_or_all, base_path, registry, options = {}, &block)
62
+ if self.maps.find {|m| m[:base_path] == base_path}
63
+ raise "Base path exists: #{base_path}"
64
+ end
65
+
66
+ map = {
67
+ :all => first_or_all == :all ? true : false,
68
+ :base_path => base_path,
69
+ :registry => registry,
70
+ :associations => [*options[:include]].compact,
71
+ :block => block
72
+ }
73
+ self.maps << map
74
+
75
+ return map
76
+ end
77
+
78
+ # Returns @maps or []
79
+ def maps
80
+ @maps ||= []
81
+ end
82
+
83
+ # Pulls attributes from the xml using xpaths defined in the mapping whose
84
+ # base path matches the xml, and instantiates a new object with those attrs.
85
+ # === Behaviors
86
+ # * If multiple elements match the base path and map[:all] is true, it will
87
+ # return an array of objects.
88
+ # * Calls from_xml on elements in map[:associations] (see
89
+ # association_from_xml)
90
+ # * Calls map[:block] with the new instance (and optionally the libxml node)
91
+ # just before adding it to the return array for custom post-processing
92
+ # === Returns
93
+ # * An array of instances or a new instance depending on whether map[:all]
94
+ # is true or false, respectively
95
+ def from_xml_via_map(xml)
96
+ nodes, map = find_nodes_and_map(xml)
97
+ return nil if map.nil?
98
+ instances = []
99
+
100
+ nodes.each do |node|
101
+ attrs = attrs_from_node_and_registry(node, map[:registry])
102
+ instance = self.from_hash(attrs)
103
+
104
+ map[:associations].each do |association|
105
+ # TODO: spec
106
+ instance.association_from_xml(xml, association)
107
+ end
108
+
109
+ b = map[:block]
110
+ if b
111
+ case b.arity # number of parameters
112
+ when 1
113
+ b.call(instance)
114
+ when 2
115
+ b.call(instance, node)
116
+ end
117
+ end
118
+
119
+ return instance unless map[:all]
120
+ instances << instance
121
+ end
122
+
123
+ return instances
124
+ end
125
+
126
+ # you can override this to what you want. Defaults to
127
+ # an alias for from_xml_via_map.
128
+ def from_xml(xml)
129
+ @xml = xml
130
+ self.before_from_xml if defined?(before_from_xml)
131
+ from_xml_via_map(@xml)
132
+ end
133
+
134
+ # initializes a new instance of self and uses the attribute setters to
135
+ # populate attributes from the hash. Objects inheriting from ActiveRecord
136
+ # do this automatically in initialize, but the world doesn't (always) revolve
137
+ # around AR
138
+ def from_hash(attrs)
139
+ object = self.new
140
+
141
+ attrs.each do |attribute, value|
142
+ object.send("#{attribute}=", value)
143
+ end
144
+
145
+ object
146
+ end
147
+
148
+ private
149
+
150
+ # find the first map whose base path matches the xml,
151
+ # and return the nodes found at that base path along with the map
152
+ # === Parameters
153
+ # [+xml+] - String of xml
154
+ # === Returns
155
+ # two-member array of the form [[LibXML::XML::XPath::Object], Consumer::Mapping::Map]
156
+ # === Raises
157
+ # nothing
158
+ def find_nodes_and_map(xml)
159
+ self.maps.each do |map|
160
+ doc = LibXML::XML::Parser.string(xml).parse
161
+ nodes = doc.find(map[:base_path])
162
+ return nodes, map if !nodes.empty?
163
+ end
164
+ return [], nil
165
+ puts "No map found in #{self.class} for xml #{xml[0..100]}..." if $DEBUG
166
+ end
167
+
168
+ # returns a hash of attribute => value pairs given an xpath node and a
169
+ # map registry
170
+ # (LibXML::XML::XPath::Object and Consumer::Mapping::Map#registry, respectively)
171
+ def attrs_from_node_and_registry(node, registry)
172
+ attrs = {}
173
+
174
+ registry.each do |attribute, path|
175
+ leaf = node.find(path).first
176
+ attrs[attribute] = leaf.content if leaf.respond_to?(:content)
177
+ attrs[attribute] = leaf.value if leaf.respond_to?(:value)
178
+ end
179
+
180
+ attrs
181
+ end
182
+
183
+ end
184
+ end