woahdae-consumer 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
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