api_object 0.5.1 → 0.6.0

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.
data/README.md CHANGED
@@ -129,7 +129,16 @@ The function takes parameters to define what fields from the location object are
129
129
  data = Weather.get_results(:weather => '99.156.82.20')
130
130
  ```
131
131
 
132
- 7) Testing
132
+ 7) Error handling
133
+
134
+ In case data cannot be retrived (possible causes might be a wrong url or service downtime), the object returned is empty. Error messages could be checked using functions errors and has_errors?
135
+
136
+ ```
137
+ station.has_errors?
138
+ errors = station.errors
139
+ ```
140
+
141
+ 8) Testing
133
142
 
134
143
  The gem has been tested on BART, Google Weather and NextBus APIs.
135
144
 
@@ -143,9 +152,10 @@ API_KEY='<your key>' rake test
143
152
 
144
153
  There is no existing api key provided with this gem as per the Terms and Conditions of the ipinfodb service.
145
154
 
146
- 8) Limitations
155
+ 9) Limitations
147
156
 
148
157
  * Api data must be presented either in XML or in JSON format. The distinction between XML and JSON is determinted automatically.
158
+ * Location by ip service uses a free database which is not always reliable.
149
159
  * When using this gem with external APIs, check Terms and Conditions of the API usage.
150
160
  * If something is not working, feel free to submit bugs and or/contribute.
151
161
 
@@ -1,3 +1,3 @@
1
1
  module ApiObject
2
- VERSION = "0.5.1"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/api_object.rb CHANGED
@@ -4,17 +4,19 @@ require "active_support/all"
4
4
  require "geo_ip"
5
5
 
6
6
  module ActiveApi
7
-
7
+
8
8
  module ClassMethods
9
-
9
+
10
10
  def self.extended(base)
11
11
  base.send(:extend, Query)
12
12
  end
13
13
 
14
14
  def initialize_from_api options = {}
15
+
15
16
  class_attribute :url, :action, :key, :mode, :url_options, :data_tags, :object_name
16
17
  self.url, self.action, self.key, self.mode, self.url_options, self.data_tags, self.object_name = [options[:url], options[:action], options[:key], options[:mode], (options[:url_options] || {}), ([*options[:data_tags]] || []), (options[:object_name] || self.to_s.downcase.gsub(/^(.+::)(.+)$/, '\2'))]
17
18
  instance_eval do
19
+
18
20
 
19
21
  def get_results_by_ip ip, arguments = {}
20
22
  self.api_key = arguments.delete(:key) if arguments[:key]
@@ -22,8 +24,8 @@ module ActiveApi
22
24
  raise unless location[:status_code] == "OK"
23
25
  get_results [*arguments.keys].inject({}) { |opts, a| opts.merge(a.to_sym => location[arguments[a.to_sym]]) }
24
26
  rescue
25
- puts "WARNING: Cannot get results or location by ip. Verify that you have a valid key for the ipinfodb.com service"
26
- return {}
27
+ puts "ERROR: Cannot get results or location by ip. Verify that you have a valid key for the ipinfodb.com service"
28
+ return ApiObjectError.new(:class => self, :errors => invalid_loc_msg)
27
29
  end
28
30
 
29
31
  def get_results options = {}
@@ -32,8 +34,8 @@ module ActiveApi
32
34
  result = query_api(self.url, self.action, self.mode, self.url_options.merge(options))
33
35
  process_result result
34
36
  rescue
35
- puts "WARNING: The request returned no valid data. #{warning_invalid_url result[:url]}"
36
- return {}
37
+ puts "ERROR: The request returned no valid data. #{error_invalid_url result[:url]}"
38
+ return ApiObjectError.new(:class => self, :errors => invalid_url_msg)
37
39
  end
38
40
 
39
41
  def api_key=(key)
@@ -44,9 +46,21 @@ module ActiveApi
44
46
  GeoIp.api_key
45
47
  end
46
48
 
47
- def warning_invalid_url url
49
+ def error_invalid_url url
48
50
  "The request url is #{url}, please, check if it's invalid of there is no connectivity." unless url.nil?
49
51
  end
52
+
53
+ def invalid_url_msg
54
+ "Cannot get results from the url"
55
+ end
56
+
57
+ def invalid_loc_msg
58
+ "Cannot obtain results by ip, check the location"
59
+ end
60
+
61
+ def invalid_data_msg
62
+ "Cannot initialize the object"
63
+ end
50
64
 
51
65
  private
52
66
  def process_result result
@@ -74,9 +88,10 @@ module ActiveApi
74
88
  end
75
89
 
76
90
  module InstanceMethods
77
-
91
+
78
92
  def initialize(*args)
79
93
  url = args.first.delete(:url)
94
+ errors = args.first.delete(:errors)
80
95
  tags = respond_to?('data_tags') ? self.data_tags : args.last[:tags]
81
96
  args.first.each do |k, v|
82
97
  k = k.gsub(/@/, '')
@@ -87,11 +102,18 @@ module ActiveApi
87
102
  instance_variable_set("@#{k.to_s}", result)
88
103
  end
89
104
  end if args.first.is_a?(Hash)
90
- puts "WARNING: data passed for #{self.class} initialization was invalid. #{self.class.warning_invalid_url url}" if self.empty?
105
+ if self.empty?
106
+ puts "ERROR: data passed for #{self.class} initialization was invalid. #{self.class.error_invalid_url url}"
107
+ @errors = errors || self.class.invalid_data_msg
108
+ end
91
109
  end
92
110
 
93
111
  def empty?
94
- self.instance_variables.empty?
112
+ (self.instance_variables - [:@errors]).empty?
113
+ end
114
+
115
+ def has_errors?
116
+ self.instance_variables.include?(:@errors)
95
117
  end
96
118
 
97
119
 
@@ -142,8 +164,41 @@ module ActiveApi
142
164
  class_attribute :columns, :assoc
143
165
  self.columns, self.assoc = [{}, {}]
144
166
 
145
- end
167
+ attr_reader :errors
168
+
169
+ end
146
170
 
147
-
171
+ class ApiObjectError < Hash
172
+ attr_reader :errors, :klass
173
+
174
+ def initialize *args
175
+ options = args.extract_options!
176
+ @errors = options.delete(:errors)
177
+ @klass = options.delete(:class)
178
+ end
179
+
180
+ def keys
181
+ [:errors]
182
+ end
183
+
184
+ def [](key)
185
+ eval("self.#{key.to_s}") if keys.include?(key)
186
+ end
187
+
188
+ def map &block
189
+ klass.new({:errors => @errors})
190
+ end
191
+
192
+ alias_method :collect, :map
193
+
194
+ def inspect
195
+ "ApiObjectError: #{@errors}"
196
+ end
197
+
198
+ def to_s
199
+ inspect
200
+ end
201
+ end
202
+
148
203
  end
149
-
204
+
@@ -8,11 +8,15 @@ class ApiObjectTest < MiniTest::Unit::TestCase
8
8
  include ActiveApi
9
9
  include TestObjects
10
10
 
11
+ IP = '99.156.82.20'
12
+ GeoIp.api_key = nil
13
+
11
14
  @@data_directory = File.expand_path('../../data', __FILE__)
12
15
  @@estimate_directory = @@data_directory + "/estimate"
13
16
  @@weather_directory = @@data_directory + "/weather"
14
17
  @@bus_directory = @@data_directory + "/bus"
15
18
  @@key_directory = @@data_directory +"/keys"
19
+
16
20
 
17
21
  @@glen_estimate = Station.load_from_xml(File.read(@@estimate_directory + '/glen.xml'))
18
22
  @@sixteenth_estimate = Station.load_from_xml(File.read(@@estimate_directory + '/sixteenth.xml'))
@@ -21,8 +25,6 @@ class ApiObjectTest < MiniTest::Unit::TestCase
21
25
  @@weather_mv = Weather.load_from_xml(File.read(@@weather_directory + '/mountain_view.xml'))
22
26
  @@weather_au = Weather.load_from_xml(File.read(@@weather_directory + '/austin.xml'))
23
27
  @@ip_key = File.read(@@key_directory + "/ipinfodb_key.txt") rescue ENV['API_KEY']
24
- IP = '99.156.82.20'
25
- GeoIp.api_key = nil
26
28
 
27
29
  @@muni_routes = Route.load_from_xml(File.read(@@bus_directory + "/muni_routes.xml"))
28
30
  @@muni_F = Route.load_from_xml(File.read(@@bus_directory + "/muni_F.xml"))
@@ -64,6 +66,7 @@ class ApiObjectTest < MiniTest::Unit::TestCase
64
66
  unless @@ip_key.nil?
65
67
  weather_au = Weather.new(Weather.get_results_by_ip(IP, :key => @@ip_key, :weather => :zip_code))
66
68
  assert_equal(weather_au, @@weather_au)
69
+ refute_has_errors(weather_au)
67
70
  end
68
71
  end
69
72
 
@@ -71,33 +74,44 @@ class ApiObjectTest < MiniTest::Unit::TestCase
71
74
  GeoIp.api_key = @@ip_key
72
75
  weather_au = Weather.new(Weather.get_results_by_ip(IP, :weather => :zip_code))
73
76
  assert_equal(weather_au, @@weather_au)
77
+ refute_has_errors(weather_au)
74
78
  end
75
-
79
+
76
80
  def test_should_not_get_correct_weather_with_no_key
77
81
  GeoIp.api_key = nil
78
82
  weather_au = Weather.new(Weather.get_results_by_ip(IP, :weather => :zip_code))
79
83
  assert_empty(weather_au)
84
+ assert_has_errors(weather_au)
80
85
  end
81
86
 
82
87
  def test_should_get_correct_bus_routes
83
88
  routes = Route.get_results(:a => 'sf-muni', :command => 'routeList').map {|r| Route.new(r)}
84
89
  assert_equal_route_list(routes, @@muni_routes)
90
+ routes.map {|r| refute_has_errors(r)}
85
91
  end
86
-
92
+
87
93
  def test_should_get_correct_route_information
88
94
  route_F = Route.new(Route.get_results(:a => 'sf-muni', :command => 'routeConfig', :r => 'F', :mode => 'terse'))
89
95
  assert_equal(route_F, @@muni_F)
96
+ refute_has_errors(route_F)
90
97
  end
91
-
98
+
92
99
  def test_should_handle_invalid_url
93
- estimate = Station.new(Station.get_results(:orig => 'YES'))
100
+ estimate = Station.new(Station.get_results(:orig => 'YES'))
94
101
  assert(estimate.empty?, "Estimate should be an empty object")
102
+ assert_has_errors(estimate)
103
+ assert(estimate.errors, Station.invalid_url_msg)
95
104
  weather = Weather.new(Weather.get_results(:weather => "Here"))
96
105
  assert(weather.empty?, "Weather should be an empty object")
97
- routes = Route.get_results(:a => 'sffg').map {|r| Route.new(r)}
106
+ assert_has_errors(weather)
107
+ assert(weather.errors, Weather.invalid_data_msg)
108
+ routes = Route.get_results(:a => 'sffg').collect {|r| Route.new(r)}
98
109
  assert(routes.empty?, "Routes should be an empty object")
110
+ assert_has_errors(routes)
111
+ assert(routes.errors, Route.invalid_url_msg)
99
112
  end
100
113
 
114
+
101
115
  private
102
116
 
103
117
  #ensure that the estimates of the first station include the sample
@@ -119,6 +133,14 @@ private
119
133
  return assert(route_list.include_all?(sample_route_list), "Route list should include all the sample routes")
120
134
  end
121
135
 
136
+ def assert_has_errors obj
137
+ return assert(obj.has_errors?, "Object should have errors")
138
+ end
139
+
140
+ def refute_has_errors obj
141
+ return refute(obj.has_errors?, "Object shouldn't have errors")
142
+ end
143
+
122
144
 
123
145
  def business_time? now
124
146
  unless now.to_date.sunday?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api_object
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-13 00:00:00.000000000 Z
12
+ date: 2012-06-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel
16
- requirement: &74735810 !ruby/object:Gem::Requirement
16
+ requirement: &76922910 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *74735810
24
+ version_requirements: *76922910
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &74735560 !ruby/object:Gem::Requirement
27
+ requirement: &76922660 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *74735560
35
+ version_requirements: *76922660
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: minitest
38
- requirement: &74735340 !ruby/object:Gem::Requirement
38
+ requirement: &77001800 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *74735340
46
+ version_requirements: *77001800
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: activesupport
49
- requirement: &74735110 !ruby/object:Gem::Requirement
49
+ requirement: &77001550 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *74735110
57
+ version_requirements: *77001550
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: nori
60
- requirement: &74734810 !ruby/object:Gem::Requirement
60
+ requirement: &77001240 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '1.1'
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *74734810
68
+ version_requirements: *77001240
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rest-client
71
- requirement: &74734530 !ruby/object:Gem::Requirement
71
+ requirement: &77000930 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '1.6'
77
77
  type: :runtime
78
78
  prerelease: false
79
- version_requirements: *74734530
79
+ version_requirements: *77000930
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: geo_ip
82
- requirement: &74734330 !ruby/object:Gem::Requirement
82
+ requirement: &77000700 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: '0'
88
88
  type: :runtime
89
89
  prerelease: false
90
- version_requirements: *74734330
90
+ version_requirements: *77000700
91
91
  description: An interface to load objects from external APIs provided in XML and JSON
92
92
  formats
93
93
  email: