api_object 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in api_object.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 TJ Moskun
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # Initialize an object from an external API
2
+
3
+ There is a number of external APIs, which provide data over XML or JSON. For example, BART system API http://api.bart.gov/docs/overview/index.aspx. This gem is to designed to load the external API data into a set of nested Ruby objects.
4
+
5
+ ## Installation
6
+
7
+ gem install api_object
8
+
9
+ ## Usage
10
+
11
+ ### Example - BART train departure estimate
12
+
13
+ 1) Subclass your objects from ActiveApi::ApiObject
14
+
15
+ ```
16
+ class Station < ActiveApi::ApiObject
17
+ end
18
+
19
+ class Estimate < ActiveApi::ApiObject
20
+ end
21
+
22
+ class Station < ActiveApi::ApiObject
23
+ end
24
+ ```
25
+ 2) Specify the url to load the data from, optionally a command, an api key and parameters(options) for the url; such as the url would look like "http://\<api_url\>/\<command\>?key=\<api_key\>&\<parameter1=value1&parameter2=value2...\>".
26
+
27
+ This will be defined in the upper object over the function "initialize_from_api". Options for this function:
28
+
29
+ ```
30
+ :url - specify url
31
+
32
+ :command - specify command
33
+
34
+ :key - api key
35
+
36
+ :url_options - parameters
37
+ ```
38
+
39
+
40
+ The following is designed to generate real time departure estimates for BART stations:
41
+
42
+ ```
43
+ class Station
44
+
45
+ initialize_from_api :url => "http://api.bart.gov/api/", :command => 'etd.aspx', :key => 'MW9S-E7SL-26DU-VV8V', :url_options => {:cmd => 'etd'}
46
+
47
+ end
48
+ ```
49
+ In this example, the url generated to get real time departure estimates from the Richmond station will be:
50
+ http://api.bart.gov/api/etd.aspx?cmd=etd&orig=RICH&key=MW9S-E7SL-26DU-VV8V
51
+
52
+ 3) Define class attributes and mapping of the attributes to the api where the api name is different. To define api simple type mappings, use "api_column \<attribute name\>, \<api attribute name\>".
53
+ To define api association mapping, use "api_association \<association attribute name\>, \<api attribute name\>, :as => \<association class name\>". Either the second, or the third parameters could be omitted. If the third parameter is omitted, it's mapped to the class name by the attribute name defined in the class.
54
+
55
+ In the following example, a simple attribute name is "abbreviation", but the name defined in the api XML documents is "abbr". An association is defined in the attribute :est, the api mapping is :etd and it's an object of the class Estimate.
56
+
57
+ ```
58
+ class Station < ActiveApi::ApiObject
59
+
60
+ initialize_from_api :url => "http://api.bart.gov/api/", :command => 'etd.aspx', :key => 'MW9S-E7SL-26DU-VV8V', :url_options => {:cmd => 'etd'}
61
+
62
+ attr_reader :name, :abbreviation, :date, :time, :est
63
+
64
+ api_column :abbreviation, :abbr
65
+ api_association :est, :etd, :as => Estimate
66
+
67
+ end
68
+ ```
69
+ 4) To load api data into an object, use the class method "get_results(options)". In the example, get real time estimates for the station Glen Park.
70
+
71
+ ```
72
+ data = Station.get_results(:orig => 'GLEN')
73
+ ```
74
+
75
+ Please, note that data loaded might be either a hash, or an array of hashes, depending on the api.
76
+
77
+ 5) Create an object from the data
78
+
79
+ If the example, the data received is a hash, so create an object.
80
+
81
+ ```
82
+ station = Station.new(data)
83
+ ```
84
+
85
+ If the data is an array of hashes, then it might be used to create an array of objects
86
+
87
+ ```
88
+ stations = data.map {|d| Station.new(d)}
89
+ ```
90
+
91
+ 6) Limitations
92
+
93
+ * Api data must be presented either in XML or in JSON format. The distinction between XML and JSON is determinted automatically.
94
+ * This is still a very early version and needs more testing. If something is not working, feel free to submit bugs and or/contribute.
95
+
96
+ ## Compatibility
97
+
98
+ Ruby 1.9.3
99
+
100
+ ## License
101
+
102
+ MIT License. Copyright 2012 TJ Moskun.
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc 'Run tests'
4
+ task :test do
5
+ result = system("ruby -Ilib -Itest -e 'ARGV.each { |f| load f }' test/unit/*")
6
+ exit(result ? 0 : 1)
7
+ end
8
+
9
+ task :ci => [:test]
10
+ task :default => [:test]
11
+
12
+ namespace :pre_commit do
13
+ desc "run the tests"
14
+ task :ci => [:test]
15
+ end
16
+
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "api_object/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "api_object"
7
+ s.version = ApiObject::VERSION
8
+ s.authors = ["tmoskun"]
9
+ s.email = ["tanyamoskun@gmail.com"]
10
+ s.homepage = "https://github.com/tmoskun/api_object"
11
+ s.summary = %q{An interface to load objects from external APIs provided in XML and JSON formats}
12
+ s.description = %q{An interface to load objects from external APIs provided in XML and JSON formats}
13
+
14
+ s.rubyforge_project = "api_object"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ # s.add_runtime_dependency "rest-client"
24
+
25
+ #s.add_development_dependency "sqlite3"
26
+ s.add_development_dependency "activemodel"
27
+ s.add_development_dependency "rspec"
28
+ s.add_development_dependency "minitest"
29
+ s.add_dependency "activesupport"
30
+ s.add_dependency('nori', '>=1.1')
31
+ s.add_dependency('rest-client', '>= 1.6')
32
+
33
+ end
@@ -0,0 +1,23 @@
1
+ module ConfigParams
2
+
3
+ IPV4_REGEXP = /\A(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)(?:\.(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)){3}\z/
4
+ TIMEOUT = 3
5
+ FALLBACK_TIMEOUT = 3
6
+
7
+ def timeout
8
+ TIMEOUT
9
+ end
10
+
11
+ def timeout= timeout
12
+ self.TIMEOUT = timeout
13
+ end
14
+
15
+ def fallback_timeout
16
+ FALLBACK_TIMEOUT
17
+ end
18
+
19
+ def fallback_timeout= fallback_timeout
20
+ self.FALLBACK_TIMEOUT = fallback_timeout
21
+ end
22
+
23
+ end
@@ -0,0 +1,20 @@
1
+ require 'json'
2
+ require 'nori'
3
+ require 'rest-client'
4
+ require_relative 'config_params'
5
+
6
+ module Query
7
+ include ConfigParams
8
+
9
+ def query_api url, command = nil, url_options = {}, options = {}
10
+ result = nil
11
+ cmd_url = url + (command.nil? ? '':"#{command}?")
12
+ request_url = cmd_url + url_options.to_a.map{|opt| opt[0].to_s + "=" + opt[1]}.join("&")
13
+ Timeout.timeout(self.fallback_timeout) do
14
+ result_raw = RestClient::Request.execute(:method => :get, :url => request_url, :timeout => self.timeout)
15
+ result = result_raw.start_with?('<?xml') ? Nori.parse(result_raw) : JSON.parse(result_raw)
16
+ end
17
+ result
18
+ end
19
+
20
+ end
@@ -0,0 +1,3 @@
1
+ module ApiObject
2
+ VERSION = "0.0.1"
3
+ end
data/lib/api_object.rb ADDED
@@ -0,0 +1,95 @@
1
+ require "api_object/version"
2
+ require "api_object/query"
3
+ require "active_support/all"
4
+
5
+ module ActiveApi
6
+
7
+ module ClassMethods
8
+
9
+ def self.extended(base)
10
+ base.send(:extend, Query)
11
+ end
12
+
13
+ def initialize_from_api options = {}
14
+ class_attribute :url, :command, :key, :url_options, :object_name
15
+ self.url, self.command, self.key, self.url_options, self.object_name = [options[:url], options[:command], options[:key], (options[:url_options] || {}), (options[:object_name] || self.to_s.downcase.gsub(/^(.+::)(.+)$/, '\2'))]
16
+ end
17
+
18
+ def get_results options = {}
19
+ self.url_options.merge!(:key => self.key) unless self.key.nil?
20
+ result = query_api(self.url, self.command, self.url_options.merge(options))
21
+ other_keys = {}
22
+ unless result.nil?
23
+ until result.keys.include?(self.object_name.to_s)
24
+ result = result[result.keys[0]]
25
+ return nil if (result.nil? || result.empty? || result.instance_of?(Array))
26
+ other_keys.merge!(result.keys.inject({}) { |h, k| (self.method_defined?(k) ? h.merge(k => result[k]) : h)})
27
+ end
28
+ end
29
+ other_keys.merge(result[self.object_name.to_s])
30
+ end
31
+
32
+ end
33
+
34
+ module InstanceMethods
35
+
36
+ def initialize(*args)
37
+ args.first.each do |k, v|
38
+ unless defined?(k).nil? # check if it's included as a reader attribute
39
+ k = self.columns[k.to_sym] unless self.columns[k.to_sym].nil?
40
+ klass = self.assoc[k.to_sym] || k
41
+ result = v.instance_of?(Array) ? v.inject([]) {|arr, v1| arr << init_object(v1, klass)} : init_object(v, klass)
42
+ instance_variable_set("@#{k.to_s}", result)
43
+ end
44
+ end if (args.length == 1 && args.first.is_a?(Hash))
45
+ end
46
+
47
+
48
+ def persisted?
49
+ false
50
+ end
51
+
52
+
53
+ private
54
+
55
+ def init_object value, klass
56
+ if value.instance_of?(Hash)
57
+ klass = (get_module_name + klass.to_s.capitalize).constantize unless klass.class == Class
58
+ klass.new(value)
59
+ else
60
+ value
61
+ end
62
+ end
63
+
64
+ def get_module_name
65
+ (self.class.name =~ /^(.+::).+$/) ? $1 : ''
66
+ end
67
+
68
+ end
69
+
70
+ module SingletonMethods
71
+ def api_column column_name, api_name
72
+ self.columns.merge!(api_name => column_name)
73
+ end
74
+
75
+ def api_association (*args)
76
+ options = args.extract_options!
77
+ self.columns.merge!(args[1] => args[0]) if args.length >= 2
78
+ self.assoc.merge!(args[0] => options[:as]) unless options[:as].nil?
79
+ end
80
+
81
+ end
82
+
83
+ class ApiObject
84
+ extend ClassMethods
85
+ include InstanceMethods
86
+ extend SingletonMethods
87
+
88
+ class_attribute :columns, :assoc
89
+ self.columns, self.assoc = [{}, {}]
90
+
91
+ end
92
+
93
+
94
+ end
95
+
@@ -0,0 +1,251 @@
1
+ <root>
2
+ <uri>
3
+ <![CDATA[ http://api.bart.gov/api/etd.aspx?cmd=etd&orig=GLEN ]]>
4
+ </uri>
5
+ <date>03/29/2012</date>
6
+ <time>06:02:01 PM PDT</time>
7
+ <station>
8
+ <name>Glen Park</name>
9
+ <abbr>GLEN</abbr>
10
+ <etd>
11
+ <destination>Daly City</destination>
12
+ <abbreviation>DALY</abbreviation>
13
+ <estimate>
14
+ <minutes>3</minutes>
15
+ <platform>1</platform>
16
+ <direction>South</direction>
17
+ <length>9</length>
18
+ <color>GREEN</color>
19
+ <hexcolor>#339933</hexcolor>
20
+ <bikeflag>1</bikeflag>
21
+ </estimate>
22
+ <estimate>
23
+ <minutes>10</minutes>
24
+ <platform>1</platform>
25
+ <direction>South</direction>
26
+ <length>8</length>
27
+ <color>BLUE</color>
28
+ <hexcolor>#0099cc</hexcolor>
29
+ <bikeflag>0</bikeflag>
30
+ </estimate>
31
+ <estimate>
32
+ <minutes>17</minutes>
33
+ <platform>1</platform>
34
+ <direction>South</direction>
35
+ <length>9</length>
36
+ <color>GREEN</color>
37
+ <hexcolor>#339933</hexcolor>
38
+ <bikeflag>1</bikeflag>
39
+ </estimate>
40
+ </etd>
41
+ <etd>
42
+ <destination>Dublin/Pleasanton</destination>
43
+ <abbreviation>DUBL</abbreviation>
44
+ <estimate>
45
+ <minutes>11</minutes>
46
+ <platform>2</platform>
47
+ <direction>North</direction>
48
+ <length>8</length>
49
+ <color>BLUE</color>
50
+ <hexcolor>#0099cc</hexcolor>
51
+ <bikeflag>1</bikeflag>
52
+ </estimate>
53
+ <estimate>
54
+ <minutes>27</minutes>
55
+ <platform>2</platform>
56
+ <direction>North</direction>
57
+ <length>8</length>
58
+ <color>BLUE</color>
59
+ <hexcolor>#0099cc</hexcolor>
60
+ <bikeflag>1</bikeflag>
61
+ </estimate>
62
+ <estimate>
63
+ <minutes>41</minutes>
64
+ <platform>2</platform>
65
+ <direction>North</direction>
66
+ <length>8</length>
67
+ <color>BLUE</color>
68
+ <hexcolor>#0099cc</hexcolor>
69
+ <bikeflag>1</bikeflag>
70
+ </estimate>
71
+ </etd>
72
+ <etd>
73
+ <destination>Fremont</destination>
74
+ <abbreviation>FRMT</abbreviation>
75
+ <estimate>
76
+ <minutes>4</minutes>
77
+ <platform>2</platform>
78
+ <direction>North</direction>
79
+ <length>8</length>
80
+ <color>GREEN</color>
81
+ <hexcolor>#339933</hexcolor>
82
+ <bikeflag>1</bikeflag>
83
+ </estimate>
84
+ <estimate>
85
+ <minutes>18</minutes>
86
+ <platform>2</platform>
87
+ <direction>North</direction>
88
+ <length>9</length>
89
+ <color>GREEN</color>
90
+ <hexcolor>#339933</hexcolor>
91
+ <bikeflag>1</bikeflag>
92
+ </estimate>
93
+ <estimate>
94
+ <minutes>33</minutes>
95
+ <platform>2</platform>
96
+ <direction>North</direction>
97
+ <length>9</length>
98
+ <color>GREEN</color>
99
+ <hexcolor>#339933</hexcolor>
100
+ <bikeflag>1</bikeflag>
101
+ </estimate>
102
+ </etd>
103
+ <etd>
104
+ <destination>Millbrae</destination>
105
+ <abbreviation>MLBR</abbreviation>
106
+ <estimate>
107
+ <minutes>6</minutes>
108
+ <platform>1</platform>
109
+ <direction>South</direction>
110
+ <length>9</length>
111
+ <color>RED</color>
112
+ <hexcolor>#ff0000</hexcolor>
113
+ <bikeflag>0</bikeflag>
114
+ </estimate>
115
+ <estimate>
116
+ <minutes>15</minutes>
117
+ <platform>1</platform>
118
+ <direction>South</direction>
119
+ <length>8</length>
120
+ <color>RED</color>
121
+ <hexcolor>#ff0000</hexcolor>
122
+ <bikeflag>1</bikeflag>
123
+ </estimate>
124
+ <estimate>
125
+ <minutes>28</minutes>
126
+ <platform>1</platform>
127
+ <direction>South</direction>
128
+ <length>10</length>
129
+ <color>RED</color>
130
+ <hexcolor>#ff0000</hexcolor>
131
+ <bikeflag>1</bikeflag>
132
+ </estimate>
133
+ </etd>
134
+ <etd>
135
+ <destination>Pittsburg/Bay Point</destination>
136
+ <abbreviation>PITT</abbreviation>
137
+ <estimate>
138
+ <minutes>13</minutes>
139
+ <platform>2</platform>
140
+ <direction>North</direction>
141
+ <length>9</length>
142
+ <color>YELLOW</color>
143
+ <hexcolor>#ffff33</hexcolor>
144
+ <bikeflag>1</bikeflag>
145
+ </estimate>
146
+ <estimate>
147
+ <minutes>28</minutes>
148
+ <platform>2</platform>
149
+ <direction>North</direction>
150
+ <length>10</length>
151
+ <color>YELLOW</color>
152
+ <hexcolor>#ffff33</hexcolor>
153
+ <bikeflag>1</bikeflag>
154
+ </estimate>
155
+ <estimate>
156
+ <minutes>43</minutes>
157
+ <platform>2</platform>
158
+ <direction>North</direction>
159
+ <length>9</length>
160
+ <color>YELLOW</color>
161
+ <hexcolor>#ffff33</hexcolor>
162
+ <bikeflag>1</bikeflag>
163
+ </estimate>
164
+ </etd>
165
+ <etd>
166
+ <destination>Richmond</destination>
167
+ <abbreviation>RICH</abbreviation>
168
+ <estimate>
169
+ <minutes>13</minutes>
170
+ <platform>2</platform>
171
+ <direction>North</direction>
172
+ <length>10</length>
173
+ <color>RED</color>
174
+ <hexcolor>#ff0000</hexcolor>
175
+ <bikeflag>1</bikeflag>
176
+ </estimate>
177
+ <estimate>
178
+ <minutes>25</minutes>
179
+ <platform>2</platform>
180
+ <direction>North</direction>
181
+ <length>10</length>
182
+ <color>RED</color>
183
+ <hexcolor>#ff0000</hexcolor>
184
+ <bikeflag>1</bikeflag>
185
+ </estimate>
186
+ <estimate>
187
+ <minutes>37</minutes>
188
+ <platform>2</platform>
189
+ <direction>North</direction>
190
+ <length>10</length>
191
+ <color>RED</color>
192
+ <hexcolor>#ff0000</hexcolor>
193
+ <bikeflag>1</bikeflag>
194
+ </estimate>
195
+ </etd>
196
+ <etd>
197
+ <destination>SF Airport</destination>
198
+ <abbreviation>SFIA</abbreviation>
199
+ <estimate>
200
+ <minutes>8</minutes>
201
+ <platform>1</platform>
202
+ <direction>South</direction>
203
+ <length>10</length>
204
+ <color>YELLOW</color>
205
+ <hexcolor>#ffff33</hexcolor>
206
+ <bikeflag>1</bikeflag>
207
+ </estimate>
208
+ <estimate>
209
+ <minutes>21</minutes>
210
+ <platform>1</platform>
211
+ <direction>South</direction>
212
+ <length>10</length>
213
+ <color>YELLOW</color>
214
+ <hexcolor>#ffff33</hexcolor>
215
+ <bikeflag>1</bikeflag>
216
+ </estimate>
217
+ </etd>
218
+ <etd>
219
+ <destination>SFO/Millbrae</destination>
220
+ <abbreviation>MLBR</abbreviation>
221
+ <estimate>
222
+ <minutes>35</minutes>
223
+ <platform>1</platform>
224
+ <direction>South</direction>
225
+ <length>10</length>
226
+ <color>YELLOW</color>
227
+ <hexcolor>#ffff33</hexcolor>
228
+ <bikeflag>1</bikeflag>
229
+ </estimate>
230
+ <estimate>
231
+ <minutes>50</minutes>
232
+ <platform>1</platform>
233
+ <direction>South</direction>
234
+ <length>9</length>
235
+ <color>YELLOW</color>
236
+ <hexcolor>#ffff33</hexcolor>
237
+ <bikeflag>1</bikeflag>
238
+ </estimate>
239
+ <estimate>
240
+ <minutes>65</minutes>
241
+ <platform>1</platform>
242
+ <direction>South</direction>
243
+ <length>9</length>
244
+ <color>YELLOW</color>
245
+ <hexcolor>#ffff33</hexcolor>
246
+ <bikeflag>1</bikeflag>
247
+ </estimate>
248
+ </etd>
249
+ </station>
250
+ <message/>
251
+ </root>
@@ -0,0 +1,246 @@
1
+ <root>
2
+ <uri>
3
+ <![CDATA[ http://api.bart.gov/api/etd.aspx?cmd=etd&orig=16TH ]]>
4
+ </uri>
5
+ <date>03/29/2012</date>
6
+ <time>06:08:30 PM PDT</time>
7
+ <station>
8
+ <name>16th St. Mission</name>
9
+ <abbr>16TH</abbr>
10
+ <etd>
11
+ <destination>24th Street</destination>
12
+ <abbreviation>24TH</abbreviation>
13
+ <estimate>
14
+ <minutes>2</minutes>
15
+ <platform>1</platform>
16
+ <direction>South</direction>
17
+ <length>9</length>
18
+ <color>YELLOW</color>
19
+ <hexcolor>#ffff33</hexcolor>
20
+ <bikeflag>0</bikeflag>
21
+ </estimate>
22
+ </etd>
23
+ <etd>
24
+ <destination>Daly City</destination>
25
+ <abbreviation>DALY</abbreviation>
26
+ <estimate>
27
+ <minutes>Leaving</minutes>
28
+ <platform>1</platform>
29
+ <direction>South</direction>
30
+ <length>8</length>
31
+ <color>BLUE</color>
32
+ <hexcolor>#0099cc</hexcolor>
33
+ <bikeflag>0</bikeflag>
34
+ </estimate>
35
+ <estimate>
36
+ <minutes>6</minutes>
37
+ <platform>1</platform>
38
+ <direction>South</direction>
39
+ <length>9</length>
40
+ <color>GREEN</color>
41
+ <hexcolor>#339933</hexcolor>
42
+ <bikeflag>0</bikeflag>
43
+ </estimate>
44
+ <estimate>
45
+ <minutes>13</minutes>
46
+ <platform>1</platform>
47
+ <direction>South</direction>
48
+ <length>8</length>
49
+ <color>BLUE</color>
50
+ <hexcolor>#0099cc</hexcolor>
51
+ <bikeflag>0</bikeflag>
52
+ </estimate>
53
+ </etd>
54
+ <etd>
55
+ <destination>Dublin/Pleasanton</destination>
56
+ <abbreviation>DUBL</abbreviation>
57
+ <estimate>
58
+ <minutes>10</minutes>
59
+ <platform>2</platform>
60
+ <direction>North</direction>
61
+ <length>8</length>
62
+ <color>BLUE</color>
63
+ <hexcolor>#0099cc</hexcolor>
64
+ <bikeflag>1</bikeflag>
65
+ </estimate>
66
+ <estimate>
67
+ <minutes>24</minutes>
68
+ <platform>2</platform>
69
+ <direction>North</direction>
70
+ <length>8</length>
71
+ <color>BLUE</color>
72
+ <hexcolor>#0099cc</hexcolor>
73
+ <bikeflag>1</bikeflag>
74
+ </estimate>
75
+ <estimate>
76
+ <minutes>39</minutes>
77
+ <platform>2</platform>
78
+ <direction>North</direction>
79
+ <length>8</length>
80
+ <color>BLUE</color>
81
+ <hexcolor>#0099cc</hexcolor>
82
+ <bikeflag>1</bikeflag>
83
+ </estimate>
84
+ </etd>
85
+ <etd>
86
+ <destination>Fremont</destination>
87
+ <abbreviation>FRMT</abbreviation>
88
+ <estimate>
89
+ <minutes>3</minutes>
90
+ <platform>2</platform>
91
+ <direction>North</direction>
92
+ <length>8</length>
93
+ <color>GREEN</color>
94
+ <hexcolor>#339933</hexcolor>
95
+ <bikeflag>1</bikeflag>
96
+ </estimate>
97
+ <estimate>
98
+ <minutes>16</minutes>
99
+ <platform>2</platform>
100
+ <direction>North</direction>
101
+ <length>9</length>
102
+ <color>GREEN</color>
103
+ <hexcolor>#339933</hexcolor>
104
+ <bikeflag>1</bikeflag>
105
+ </estimate>
106
+ <estimate>
107
+ <minutes>31</minutes>
108
+ <platform>2</platform>
109
+ <direction>North</direction>
110
+ <length>9</length>
111
+ <color>GREEN</color>
112
+ <hexcolor>#339933</hexcolor>
113
+ <bikeflag>1</bikeflag>
114
+ </estimate>
115
+ </etd>
116
+ <etd>
117
+ <destination>Millbrae</destination>
118
+ <abbreviation>MLBR</abbreviation>
119
+ <estimate>
120
+ <minutes>4</minutes>
121
+ <platform>1</platform>
122
+ <direction>South</direction>
123
+ <length>8</length>
124
+ <color>RED</color>
125
+ <hexcolor>#ff0000</hexcolor>
126
+ <bikeflag>0</bikeflag>
127
+ </estimate>
128
+ <estimate>
129
+ <minutes>18</minutes>
130
+ <platform>1</platform>
131
+ <direction>South</direction>
132
+ <length>10</length>
133
+ <color>RED</color>
134
+ <hexcolor>#ff0000</hexcolor>
135
+ <bikeflag>1</bikeflag>
136
+ </estimate>
137
+ </etd>
138
+ <etd>
139
+ <destination>Pittsburg/Bay Point</destination>
140
+ <abbreviation>PITT</abbreviation>
141
+ <estimate>
142
+ <minutes>6</minutes>
143
+ <platform>2</platform>
144
+ <direction>North</direction>
145
+ <length>9</length>
146
+ <color>YELLOW</color>
147
+ <hexcolor>#ffff33</hexcolor>
148
+ <bikeflag>1</bikeflag>
149
+ </estimate>
150
+ <estimate>
151
+ <minutes>12</minutes>
152
+ <platform>2</platform>
153
+ <direction>North</direction>
154
+ <length>9</length>
155
+ <color>YELLOW</color>
156
+ <hexcolor>#ffff33</hexcolor>
157
+ <bikeflag>1</bikeflag>
158
+ </estimate>
159
+ <estimate>
160
+ <minutes>28</minutes>
161
+ <platform>2</platform>
162
+ <direction>North</direction>
163
+ <length>10</length>
164
+ <color>YELLOW</color>
165
+ <hexcolor>#ffff33</hexcolor>
166
+ <bikeflag>1</bikeflag>
167
+ </estimate>
168
+ </etd>
169
+ <etd>
170
+ <destination>Richmond</destination>
171
+ <abbreviation>RICH</abbreviation>
172
+ <estimate>
173
+ <minutes>11</minutes>
174
+ <platform>2</platform>
175
+ <direction>North</direction>
176
+ <length>10</length>
177
+ <color>RED</color>
178
+ <hexcolor>#ff0000</hexcolor>
179
+ <bikeflag>1</bikeflag>
180
+ </estimate>
181
+ <estimate>
182
+ <minutes>20</minutes>
183
+ <platform>2</platform>
184
+ <direction>North</direction>
185
+ <length>10</length>
186
+ <color>RED</color>
187
+ <hexcolor>#ff0000</hexcolor>
188
+ <bikeflag>1</bikeflag>
189
+ </estimate>
190
+ <estimate>
191
+ <minutes>35</minutes>
192
+ <platform>2</platform>
193
+ <direction>North</direction>
194
+ <length>10</length>
195
+ <color>RED</color>
196
+ <hexcolor>#ff0000</hexcolor>
197
+ <bikeflag>1</bikeflag>
198
+ </estimate>
199
+ </etd>
200
+ <etd>
201
+ <destination>SF Airport</destination>
202
+ <abbreviation>SFIA</abbreviation>
203
+ <estimate>
204
+ <minutes>9</minutes>
205
+ <platform>1</platform>
206
+ <direction>South</direction>
207
+ <length>10</length>
208
+ <color>YELLOW</color>
209
+ <hexcolor>#ffff33</hexcolor>
210
+ <bikeflag>0</bikeflag>
211
+ </estimate>
212
+ </etd>
213
+ <etd>
214
+ <destination>SFO/Millbrae</destination>
215
+ <abbreviation>MLBR</abbreviation>
216
+ <estimate>
217
+ <minutes>24</minutes>
218
+ <platform>1</platform>
219
+ <direction>South</direction>
220
+ <length>10</length>
221
+ <color>YELLOW</color>
222
+ <hexcolor>#ffff33</hexcolor>
223
+ <bikeflag>1</bikeflag>
224
+ </estimate>
225
+ <estimate>
226
+ <minutes>39</minutes>
227
+ <platform>1</platform>
228
+ <direction>South</direction>
229
+ <length>9</length>
230
+ <color>YELLOW</color>
231
+ <hexcolor>#ffff33</hexcolor>
232
+ <bikeflag>1</bikeflag>
233
+ </estimate>
234
+ <estimate>
235
+ <minutes>54</minutes>
236
+ <platform>1</platform>
237
+ <direction>South</direction>
238
+ <length>9</length>
239
+ <color>YELLOW</color>
240
+ <hexcolor>#ffff33</hexcolor>
241
+ <bikeflag>1</bikeflag>
242
+ </estimate>
243
+ </etd>
244
+ </station>
245
+ <message/>
246
+ </root>
@@ -0,0 +1,233 @@
1
+ <root>
2
+ <uri>
3
+ <![CDATA[ http://api.bart.gov/api/etd.aspx?cmd=etd&orig=24TH ]]>
4
+ </uri>
5
+ <date>03/29/2012</date>
6
+ <time>06:09:02 PM PDT</time>
7
+ <station>
8
+ <name>24th St. Mission</name>
9
+ <abbr>24TH</abbr>
10
+ <etd>
11
+ <destination>Daly City</destination>
12
+ <abbreviation>DALY</abbreviation>
13
+ <estimate>
14
+ <minutes>2</minutes>
15
+ <platform>1</platform>
16
+ <direction>South</direction>
17
+ <length>8</length>
18
+ <color>BLUE</color>
19
+ <hexcolor>#0099cc</hexcolor>
20
+ <bikeflag>0</bikeflag>
21
+ </estimate>
22
+ <estimate>
23
+ <minutes>8</minutes>
24
+ <platform>1</platform>
25
+ <direction>South</direction>
26
+ <length>9</length>
27
+ <color>GREEN</color>
28
+ <hexcolor>#339933</hexcolor>
29
+ <bikeflag>1</bikeflag>
30
+ </estimate>
31
+ <estimate>
32
+ <minutes>15</minutes>
33
+ <platform>1</platform>
34
+ <direction>South</direction>
35
+ <length>8</length>
36
+ <color>BLUE</color>
37
+ <hexcolor>#0099cc</hexcolor>
38
+ <bikeflag>0</bikeflag>
39
+ </estimate>
40
+ </etd>
41
+ <etd>
42
+ <destination>Dublin/Pleasanton</destination>
43
+ <abbreviation>DUBL</abbreviation>
44
+ <estimate>
45
+ <minutes>8</minutes>
46
+ <platform>2</platform>
47
+ <direction>North</direction>
48
+ <length>8</length>
49
+ <color>BLUE</color>
50
+ <hexcolor>#0099cc</hexcolor>
51
+ <bikeflag>1</bikeflag>
52
+ </estimate>
53
+ <estimate>
54
+ <minutes>22</minutes>
55
+ <platform>2</platform>
56
+ <direction>North</direction>
57
+ <length>8</length>
58
+ <color>BLUE</color>
59
+ <hexcolor>#0099cc</hexcolor>
60
+ <bikeflag>1</bikeflag>
61
+ </estimate>
62
+ <estimate>
63
+ <minutes>37</minutes>
64
+ <platform>2</platform>
65
+ <direction>North</direction>
66
+ <length>8</length>
67
+ <color>BLUE</color>
68
+ <hexcolor>#0099cc</hexcolor>
69
+ <bikeflag>1</bikeflag>
70
+ </estimate>
71
+ </etd>
72
+ <etd>
73
+ <destination>Fremont</destination>
74
+ <abbreviation>FRMT</abbreviation>
75
+ <estimate>
76
+ <minutes>14</minutes>
77
+ <platform>2</platform>
78
+ <direction>North</direction>
79
+ <length>9</length>
80
+ <color>GREEN</color>
81
+ <hexcolor>#339933</hexcolor>
82
+ <bikeflag>1</bikeflag>
83
+ </estimate>
84
+ <estimate>
85
+ <minutes>29</minutes>
86
+ <platform>2</platform>
87
+ <direction>North</direction>
88
+ <length>9</length>
89
+ <color>GREEN</color>
90
+ <hexcolor>#339933</hexcolor>
91
+ <bikeflag>1</bikeflag>
92
+ </estimate>
93
+ <estimate>
94
+ <minutes>44</minutes>
95
+ <platform>2</platform>
96
+ <direction>North</direction>
97
+ <length>8</length>
98
+ <color>GREEN</color>
99
+ <hexcolor>#339933</hexcolor>
100
+ <bikeflag>1</bikeflag>
101
+ </estimate>
102
+ </etd>
103
+ <etd>
104
+ <destination>Millbrae</destination>
105
+ <abbreviation>MLBR</abbreviation>
106
+ <estimate>
107
+ <minutes>5</minutes>
108
+ <platform>1</platform>
109
+ <direction>South</direction>
110
+ <length>8</length>
111
+ <color>RED</color>
112
+ <hexcolor>#ff0000</hexcolor>
113
+ <bikeflag>1</bikeflag>
114
+ </estimate>
115
+ <estimate>
116
+ <minutes>19</minutes>
117
+ <platform>1</platform>
118
+ <direction>South</direction>
119
+ <length>10</length>
120
+ <color>RED</color>
121
+ <hexcolor>#ff0000</hexcolor>
122
+ <bikeflag>1</bikeflag>
123
+ </estimate>
124
+ </etd>
125
+ <etd>
126
+ <destination>Pittsburg/Bay Point</destination>
127
+ <abbreviation>PITT</abbreviation>
128
+ <estimate>
129
+ <minutes>4</minutes>
130
+ <platform>2</platform>
131
+ <direction>North</direction>
132
+ <length>9</length>
133
+ <color>YELLOW</color>
134
+ <hexcolor>#ffff33</hexcolor>
135
+ <bikeflag>1</bikeflag>
136
+ </estimate>
137
+ <estimate>
138
+ <minutes>9</minutes>
139
+ <platform>2</platform>
140
+ <direction>North</direction>
141
+ <length>9</length>
142
+ <color>YELLOW</color>
143
+ <hexcolor>#ffff33</hexcolor>
144
+ <bikeflag>1</bikeflag>
145
+ </estimate>
146
+ <estimate>
147
+ <minutes>26</minutes>
148
+ <platform>2</platform>
149
+ <direction>North</direction>
150
+ <length>10</length>
151
+ <color>YELLOW</color>
152
+ <hexcolor>#ffff33</hexcolor>
153
+ <bikeflag>1</bikeflag>
154
+ </estimate>
155
+ </etd>
156
+ <etd>
157
+ <destination>Richmond</destination>
158
+ <abbreviation>RICH</abbreviation>
159
+ <estimate>
160
+ <minutes>8</minutes>
161
+ <platform>2</platform>
162
+ <direction>North</direction>
163
+ <length>10</length>
164
+ <color>RED</color>
165
+ <hexcolor>#ff0000</hexcolor>
166
+ <bikeflag>1</bikeflag>
167
+ </estimate>
168
+ <estimate>
169
+ <minutes>18</minutes>
170
+ <platform>2</platform>
171
+ <direction>North</direction>
172
+ <length>10</length>
173
+ <color>RED</color>
174
+ <hexcolor>#ff0000</hexcolor>
175
+ <bikeflag>1</bikeflag>
176
+ </estimate>
177
+ <estimate>
178
+ <minutes>33</minutes>
179
+ <platform>2</platform>
180
+ <direction>North</direction>
181
+ <length>10</length>
182
+ <color>RED</color>
183
+ <hexcolor>#ff0000</hexcolor>
184
+ <bikeflag>1</bikeflag>
185
+ </estimate>
186
+ </etd>
187
+ <etd>
188
+ <destination>SF Airport</destination>
189
+ <abbreviation>SFIA</abbreviation>
190
+ <estimate>
191
+ <minutes>11</minutes>
192
+ <platform>1</platform>
193
+ <direction>South</direction>
194
+ <length>10</length>
195
+ <color>YELLOW</color>
196
+ <hexcolor>#ffff33</hexcolor>
197
+ <bikeflag>1</bikeflag>
198
+ </estimate>
199
+ </etd>
200
+ <etd>
201
+ <destination>SFO/Millbrae</destination>
202
+ <abbreviation>MLBR</abbreviation>
203
+ <estimate>
204
+ <minutes>25</minutes>
205
+ <platform>1</platform>
206
+ <direction>South</direction>
207
+ <length>10</length>
208
+ <color>YELLOW</color>
209
+ <hexcolor>#ffff33</hexcolor>
210
+ <bikeflag>1</bikeflag>
211
+ </estimate>
212
+ <estimate>
213
+ <minutes>40</minutes>
214
+ <platform>1</platform>
215
+ <direction>South</direction>
216
+ <length>9</length>
217
+ <color>YELLOW</color>
218
+ <hexcolor>#ffff33</hexcolor>
219
+ <bikeflag>1</bikeflag>
220
+ </estimate>
221
+ <estimate>
222
+ <minutes>55</minutes>
223
+ <platform>1</platform>
224
+ <direction>South</direction>
225
+ <length>9</length>
226
+ <color>YELLOW</color>
227
+ <hexcolor>#ffff33</hexcolor>
228
+ <bikeflag>1</bikeflag>
229
+ </estimate>
230
+ </etd>
231
+ </station>
232
+ <message/>
233
+ </root>
@@ -0,0 +1,42 @@
1
+ require 'minitest/autorun'
2
+ require_relative 'station'
3
+
4
+ class ApiObjectTest < MiniTest::Unit::TestCase
5
+
6
+ include ActiveApi
7
+ include TestObjects
8
+
9
+ @@data_directory = File.expand_path('../../data', __FILE__)
10
+ @@estimate_directory = @@data_directory + "/estimate"
11
+
12
+ @@glen_estimate = Station.load_from_xml(File.read(@@estimate_directory + '/glen.xml'))
13
+ @@sixteenth_estimate = Station.load_from_xml(File.read(@@estimate_directory + '/sixteenth.xml'))
14
+ @@twenty_fourth_estimate = Station.load_from_xml(File.read(@@estimate_directory + '/twenty_fourth.xml'))
15
+
16
+ def test_should_get_correct_estimates
17
+ glen_estimate = Station.new(Station.get_results(:orig => 'GLEN'))
18
+ now = DateTime.strptime(glen_estimate.date + " " + glen_estimate.time, "%m/%d/%Y %I:%M:%S %p %Z")
19
+ if business_time? now
20
+ sixteenth_estimate = Station.new(Station.get_results(:orig => '16TH'))
21
+ twenty_fourth_estimate = Station.new(Station.get_results(:orig => '24TH'))
22
+ assert_equal(glen_estimate, @@glen_estimate)
23
+ assert_equal(sixteenth_estimate, @@sixteenth_estimate)
24
+ assert_equal(twenty_fourth_estimate, @@twenty_fourth_estimate)
25
+ end
26
+ end
27
+
28
+
29
+ private
30
+ def business_time? now
31
+ unless now.to_date.sunday?
32
+ #t = now.to_time.localtime(now.zone)
33
+ #t1 = DateTime.strptime("#{now.month}/#{now.day}/#{now.year} 10:00:00 AM #{now.zone}", "%m/%d/%Y %I:%M:%S %p %:z").to_time.localtime(now.zone)
34
+ #t2 = DateTime.strptime("#{now.month}/#{now.day}/#{now.year} 08:00:00 PM #{now.zone}", "%m/%d/%Y %I:%M:%S %p %:z").to_time.localtime(now.zone)
35
+ t1 = DateTime.strptime("#{now.month}/#{now.day}/#{now.year} 10:00:00 AM #{now.zone}", "%m/%d/%Y %I:%M:%S %p %:z")
36
+ t2 = DateTime.strptime("#{now.month}/#{now.day}/#{now.year} 08:00:00 PM #{now.zone}", "%m/%d/%Y %I:%M:%S %p %:z")
37
+ return now > t1 && now < t2
38
+ end
39
+ return false
40
+ end
41
+
42
+ end
@@ -0,0 +1,10 @@
1
+ module LoadFromXML
2
+ def load_from_xml xml
3
+ hash = Nori.parse(xml)
4
+ hash = hash["root"] if hash.keys.include?("root")
5
+ timestamp = hash["date"].nil? ? {} : {"date" => hash["date"], "time" => hash["time"]}
6
+ hash = hash["stations"] if hash.keys.include?("stations")
7
+ hash = hash["station"] if hash.keys.include?("station")
8
+ new(timestamp.merge(hash))
9
+ end
10
+ end
@@ -0,0 +1,67 @@
1
+ require 'api_object'
2
+ require_relative 'load_from_xml'
3
+ module TestObjects
4
+
5
+ module StationMethods
6
+ def to_s
7
+ inspect
8
+ end
9
+
10
+ end
11
+
12
+ class Departure < ActiveApi::ApiObject
13
+ include StationMethods
14
+ attr_reader :minutes, :platform, :direction, :length, :color, :bikes_allowed
15
+
16
+ api_column :bikes_allowed, :bikeflag
17
+
18
+ def inspect
19
+ "#{@platform} platform - #{@minutes}#{(@minutes == 'Leaving') ? '':' minutes'}"
20
+ end
21
+
22
+ def ==(other_)
23
+ true
24
+ end
25
+
26
+ end
27
+
28
+ class Estimate < ActiveApi::ApiObject
29
+ include StationMethods
30
+ attr_reader :abbreviation, :destination, :estimate
31
+
32
+ api_association :estimate, :as => Departure
33
+
34
+ def inspect
35
+ "destination #{@destination} \n\t\t #{@estimate.inspect}"
36
+ end
37
+
38
+ def ==(other_est)
39
+ return false if other_est.nil?
40
+ self.abbreviation == other_est.abbreviation && self.destination == other_est.destination
41
+ end
42
+
43
+ end
44
+
45
+ class Station < ActiveApi::ApiObject
46
+ extend LoadFromXML
47
+ include StationMethods
48
+
49
+ attr_reader :name, :abbreviation, :date, :time, :est
50
+
51
+ initialize_from_api :url => "http://api.bart.gov/api/", :command => 'etd.aspx', :key => 'MW9S-E7SL-26DU-VV8V', :url_options => {:cmd => 'etd'}
52
+
53
+ api_column :abbreviation, :abbr
54
+ api_association :est, :etd, :as => TestObjects::Estimate
55
+
56
+ def inspect
57
+ "#{@name} at #{@date}, #{@time} \n\t #{@est.instance_of?(Array) ? (@est.inject('') {|str, e| (str + e.inspect + "\n\t")}) : @est}"
58
+ end
59
+
60
+ def ==(other_station)
61
+ return false if other_station.nil?
62
+ self.abbreviation == other_station.abbreviation && self.est == other_station.est
63
+ end
64
+
65
+ end
66
+
67
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: api_object
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - tmoskun
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activemodel
16
+ requirement: &71094620 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *71094620
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &70815040 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70815040
36
+ - !ruby/object:Gem::Dependency
37
+ name: minitest
38
+ requirement: &70814760 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70814760
47
+ - !ruby/object:Gem::Dependency
48
+ name: activesupport
49
+ requirement: &70814480 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70814480
58
+ - !ruby/object:Gem::Dependency
59
+ name: nori
60
+ requirement: &70814230 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '1.1'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70814230
69
+ - !ruby/object:Gem::Dependency
70
+ name: rest-client
71
+ requirement: &70813950 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '1.6'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *70813950
80
+ description: An interface to load objects from external APIs provided in XML and JSON
81
+ formats
82
+ email:
83
+ - tanyamoskun@gmail.com
84
+ executables: []
85
+ extensions: []
86
+ extra_rdoc_files: []
87
+ files:
88
+ - Gemfile
89
+ - LICENSE
90
+ - README.md
91
+ - Rakefile
92
+ - api_object.gemspec
93
+ - lib/api_object.rb
94
+ - lib/api_object/config_params.rb
95
+ - lib/api_object/query.rb
96
+ - lib/api_object/version.rb
97
+ - test/data/estimate/glen.xml
98
+ - test/data/estimate/sixteenth.xml
99
+ - test/data/estimate/twenty_fourth.xml
100
+ - test/unit/api_object_test.rb
101
+ - test/unit/load_from_xml.rb
102
+ - test/unit/station.rb
103
+ homepage: https://github.com/tmoskun/api_object
104
+ licenses: []
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project: api_object
123
+ rubygems_version: 1.8.17
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: An interface to load objects from external APIs provided in XML and JSON
127
+ formats
128
+ test_files: []