google_distance_matrix 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ ## v.0.0.2
2
+
3
+ * Renamed RequestError to ServerError.
4
+ * Added ability to cache API responses using an ActiveSupport::Cache::Store. Simply using URL as cache key.
5
+ * Explicit instrumentation and logging how many elements requested from the API.
6
+
7
+
8
+ ## v.0.0.1
9
+
10
+ * First release.
data/README.md CHANGED
@@ -96,6 +96,25 @@ Or install it yourself as:
96
96
 
97
97
 
98
98
 
99
+ ## Configuration
100
+
101
+ Configuration is done directly on a matrix or via GoogleDistanceMatrix.default_configuration.
102
+ Apart from configuration on requests it is also possible to provide your own logger class and
103
+ set a cache.
104
+
105
+ ### Request cache
106
+
107
+ Given Google's limit to the service you may have the need to cache requests. This is done by simply
108
+ using URL as cache keys. Cache we'll accept should provide a default ActiveSupport::Chache::Store interface.
109
+
110
+ GoogleDistanceMatrix.default_configuration do |config|
111
+ config.cache = ActiveSupport::Cache.lookup_store :your_store, {
112
+ expires_in: 12.hours
113
+ # ..or other options you like for your store
114
+ }
115
+ end
116
+
117
+
99
118
 
100
119
  ## Contributing
101
120
 
@@ -11,6 +11,7 @@ require "google_distance_matrix/errors"
11
11
  require "google_distance_matrix/configuration"
12
12
  require "google_distance_matrix/url_builder"
13
13
  require "google_distance_matrix/client"
14
+ require "google_distance_matrix/client_cache"
14
15
  require "google_distance_matrix/routes_finder"
15
16
  require "google_distance_matrix/matrix"
16
17
  require "google_distance_matrix/places"
@@ -8,10 +8,11 @@ module GoogleDistanceMatrix
8
8
  UNKNOWN_ERROR
9
9
  ]
10
10
 
11
- def get(url)
11
+ def get(url, options = {})
12
12
  uri = URI.parse url
13
+ instrumentation = {url: url}.merge(options[:instrumentation] || {})
13
14
 
14
- response = ActiveSupport::Notifications.instrument "client_request_matrix_data.google_distance_matrix", url: url do
15
+ response = ActiveSupport::Notifications.instrument "client_request_matrix_data.google_distance_matrix", instrumentation do
15
16
  Net::HTTP.get_response uri
16
17
  end
17
18
 
@@ -23,12 +24,12 @@ module GoogleDistanceMatrix
23
24
  when Net::HTTPClientError
24
25
  fail ClientError.new response
25
26
  when Net::HTTPServerError
26
- fail RequestError.new response
27
+ fail ServerError.new response
27
28
  else # Handle this as a request error for now. Maybe fine tune this more later.
28
- fail RequestError.new response
29
+ fail ServerError.new response
29
30
  end
30
31
  rescue Timeout::Error => error
31
- fail RequestError.new error
32
+ fail ServerError.new error
32
33
  end
33
34
 
34
35
 
@@ -0,0 +1,20 @@
1
+ module GoogleDistanceMatrix
2
+ class ClientCache
3
+ attr_reader :client, :cache
4
+
5
+ def self.key(url)
6
+ url
7
+ end
8
+
9
+ def initialize(client, cache)
10
+ @client = client
11
+ @cache = cache
12
+ end
13
+
14
+ def get(url, options = {})
15
+ cache.fetch self.class.key(url) do
16
+ client.get url, options
17
+ end
18
+ end
19
+ end
20
+ end
@@ -18,6 +18,7 @@ module GoogleDistanceMatrix
18
18
 
19
19
  attr_accessor *ATTRIBUTES, :protocol, :logger, :lat_lng_scale
20
20
  attr_accessor :google_business_api_client_id, :google_business_api_private_key
21
+ attr_accessor :cache
21
22
 
22
23
 
23
24
  validates :sensor, inclusion: {in: [true, false]}
@@ -34,7 +34,7 @@ module GoogleDistanceMatrix
34
34
  # This includes wire errors like timeouts etc, and server errors
35
35
  # like 5xx. Inspect error_or_response for more information.
36
36
  #
37
- class RequestError < Error
37
+ class ServerError < Error
38
38
  attr_reader :error_or_response
39
39
 
40
40
  def initialize(error_or_response)
@@ -42,7 +42,7 @@ module GoogleDistanceMatrix
42
42
  end
43
43
 
44
44
  def to_s
45
- "GoogleDistanceMatrix::RequestError - #{error_or_response.inspect}."
45
+ "GoogleDistanceMatrix::ServerError - #{error_or_response.inspect}."
46
46
  end
47
47
  end
48
48
 
@@ -5,7 +5,7 @@ module GoogleDistanceMatrix
5
5
  end
6
6
 
7
7
  def client_request_matrix_data(event)
8
- logger.info "(#{event.duration}ms) GET #{event.payload[:url]}", tag: :client
8
+ logger.info "(#{event.duration}ms) (elements: #{event.payload[:elements]}) GET #{event.payload[:url]}", tag: :client
9
9
  end
10
10
  end
11
11
  end
@@ -68,10 +68,15 @@ module GoogleDistanceMatrix
68
68
  end
69
69
 
70
70
  def reload
71
+ clear_from_cache!
71
72
  @data = load_matrix
72
73
  self
73
74
  end
74
75
 
76
+ def reset!
77
+ @data = nil
78
+ end
79
+
75
80
  def loaded?
76
81
  @data.present?
77
82
  end
@@ -103,7 +108,7 @@ module GoogleDistanceMatrix
103
108
  end
104
109
 
105
110
  def load_matrix
106
- parsed = JSON.parse client.get(url).body
111
+ parsed = JSON.parse client.get(url, instrumentation: {elements: origins.length * destinations.length}).body
107
112
 
108
113
  parsed["rows"].each_with_index.map do |row, origin_index|
109
114
  origin = origins[origin_index]
@@ -116,7 +121,19 @@ module GoogleDistanceMatrix
116
121
  end
117
122
 
118
123
  def client
119
- @client ||= Client.new
124
+ client = Client.new
125
+
126
+ if configuration.cache
127
+ ClientCache.new client, configuration.cache
128
+ else
129
+ client
130
+ end
131
+ end
132
+
133
+ def clear_from_cache!
134
+ if configuration.cache
135
+ configuration.cache.delete ClientCache.key(url)
136
+ end
120
137
  end
121
138
  end
122
139
  end
@@ -72,20 +72,40 @@ module GoogleDistanceMatrix
72
72
  end
73
73
 
74
74
 
75
+ # Public: Finds shortes route by distance to a place.
76
+ #
77
+ # place - The place, or object place was built from, you want the shortest route to
78
+ #
79
+ # Returns shortest route, or nil if no routes had status ok
75
80
  def shortest_route_by_distance_to(place_or_object_place_was_built_from)
76
81
  routes = routes_for place_or_object_place_was_built_from
77
82
  select_ok_routes(routes).min_by &:distance_in_meters
78
83
  end
79
84
 
85
+ # Public: Finds shortes route by distance to a place.
86
+ #
87
+ # place - The place, or object place was built from, you want the shortest route to
88
+ #
89
+ # Returns shortest route, fails if any of the routes are not ok
80
90
  def shortest_route_by_distance_to!(place_or_object_place_was_built_from)
81
91
  routes_for!(place_or_object_place_was_built_from).min_by &:distance_in_meters
82
92
  end
83
93
 
94
+ # Public: Finds shortes route by duration to a place.
95
+ #
96
+ # place - The place, or object place was built from, you want the shortest route to
97
+ #
98
+ # Returns shortest route, or nil if no routes had status ok
84
99
  def shortest_route_by_duration_to(place_or_object_place_was_built_from)
85
100
  routes = routes_for place_or_object_place_was_built_from
86
101
  select_ok_routes(routes).min_by &:duration_in_seconds
87
102
  end
88
103
 
104
+ # Public: Finds shortes route by duration to a place.
105
+ #
106
+ # place - The place, or object place was built from, you want the shortest route to
107
+ #
108
+ # Returns shortest route, fails if any of the routes are not ok
89
109
  def shortest_route_by_duration_to!(place_or_object_place_was_built_from)
90
110
  routes_for!(place_or_object_place_was_built_from).min_by &:duration_in_seconds
91
111
  end
@@ -1,3 +1,3 @@
1
1
  module GoogleDistanceMatrix
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ describe GoogleDistanceMatrix::ClientCache do
4
+ let(:url) { "http://www.example.com" }
5
+ let(:options) { {hello: :options} }
6
+
7
+ let(:client) { mock get: "data" }
8
+ let(:cache) { mock }
9
+
10
+ subject { described_class.new client, cache }
11
+
12
+ describe "#get" do
13
+ it "returns from cache if it hits" do
14
+ cache.should_receive(:fetch).with(url).and_return "cached-data"
15
+ expect(subject.get(url, options)).to eq "cached-data"
16
+ end
17
+
18
+ it "asks client when cache miss" do
19
+ client.should_receive(:get).with(url, options).and_return "api-data"
20
+ cache.should_receive(:fetch) { |&block| block.call }
21
+
22
+ expect(subject.get(url, options)).to eq "api-data"
23
+ end
24
+ end
25
+ end
@@ -44,7 +44,7 @@ describe GoogleDistanceMatrix::Client, :request_recordings do
44
44
  before { stub_request(:get, url).to_return status: [500, "Internal Server Error"] }
45
45
 
46
46
  it "wraps the error http response" do
47
- expect { subject.get(url_builder.url) }.to raise_error GoogleDistanceMatrix::RequestError
47
+ expect { subject.get(url_builder.url) }.to raise_error GoogleDistanceMatrix::ServerError
48
48
  end
49
49
  end
50
50
 
@@ -52,7 +52,7 @@ describe GoogleDistanceMatrix::Client, :request_recordings do
52
52
  before { stub_request(:get, url).to_timeout }
53
53
 
54
54
  it "wraps the error from Net::HTTP" do
55
- expect { subject.get(url_builder.url).body }.to raise_error GoogleDistanceMatrix::RequestError
55
+ expect { subject.get(url_builder.url).body }.to raise_error GoogleDistanceMatrix::ServerError
56
56
  end
57
57
  end
58
58
 
@@ -60,7 +60,7 @@ describe GoogleDistanceMatrix::Client, :request_recordings do
60
60
  before { stub_request(:get, url).to_return status: [999, "Unknown"] }
61
61
 
62
62
  it "wraps the error http response" do
63
- expect { subject.get(url_builder.url) }.to raise_error GoogleDistanceMatrix::RequestError
63
+ expect { subject.get(url_builder.url) }.to raise_error GoogleDistanceMatrix::ServerError
64
64
  end
65
65
  end
66
66
  end
@@ -31,6 +31,7 @@ describe GoogleDistanceMatrix::Configuration do
31
31
  its(:google_business_api_private_key) { should be_nil }
32
32
 
33
33
  its(:logger) { should be_nil }
34
+ its(:cache) { should be_nil }
34
35
  end
35
36
 
36
37
 
@@ -93,6 +93,50 @@ describe GoogleDistanceMatrix::Matrix do
93
93
  end
94
94
  end
95
95
 
96
+ describe "making API requests", :request_recordings do
97
+ it "loads correctly" do
98
+ stub_request(:get, url).to_return body: recorded_request_for(:success)
99
+ expect(subject.data[0][0].distance_in_meters).to eq 2032
100
+ end
101
+
102
+ context "no cache" do
103
+ it "makes multiple requests to same url" do
104
+ stub = stub_request(:get, url).to_return body: recorded_request_for(:success)
105
+ subject.data
106
+ subject.reset!
107
+ subject.data
108
+
109
+ stub.should have_been_requested.twice
110
+ end
111
+ end
112
+
113
+ context "with cache" do
114
+ before do
115
+ subject.configure do |config|
116
+ config.cache = ActiveSupport::Cache.lookup_store :memory_store
117
+ end
118
+ end
119
+
120
+ it "makes one requests to same url" do
121
+ stub = stub_request(:get, url).to_return body: recorded_request_for(:success)
122
+ subject.data
123
+ subject.reset!
124
+ subject.data
125
+
126
+ stub.should have_been_requested.once
127
+ end
128
+
129
+ it "clears the cache key on reload" do
130
+ stub = stub_request(:get, url).to_return body: recorded_request_for(:success)
131
+ subject.data
132
+ subject.reload
133
+ subject.data
134
+
135
+ stub.should have_been_requested.twice
136
+ end
137
+ end
138
+ end
139
+
96
140
  describe "#data", :request_recordings do
97
141
  context "success" do
98
142
  let!(:api_request_stub) { stub_request(:get, url).to_return body: recorded_request_for(:success) }
metadata CHANGED
@@ -2,25 +2,25 @@
2
2
  name: google_distance_matrix
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.1
5
+ version: 0.0.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - Thorbjørn Hermansen
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-16 00:00:00.000000000 Z
12
+ date: 2013-08-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- prerelease: false
16
- name: activesupport
17
- type: :runtime
18
15
  version_requirements: !ruby/object:Gem::Requirement
19
16
  requirements:
20
17
  - - ~>
21
18
  - !ruby/object:Gem::Version
22
19
  version: 3.2.13
23
20
  none: false
21
+ name: activesupport
22
+ type: :runtime
23
+ prerelease: false
24
24
  requirement: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - ~>
@@ -28,15 +28,15 @@ dependencies:
28
28
  version: 3.2.13
29
29
  none: false
30
30
  - !ruby/object:Gem::Dependency
31
- prerelease: false
32
- name: activemodel
33
- type: :runtime
34
31
  version_requirements: !ruby/object:Gem::Requirement
35
32
  requirements:
36
33
  - - ~>
37
34
  - !ruby/object:Gem::Version
38
35
  version: 3.2.13
39
36
  none: false
37
+ name: activemodel
38
+ type: :runtime
39
+ prerelease: false
40
40
  requirement: !ruby/object:Gem::Requirement
41
41
  requirements:
42
42
  - - ~>
@@ -44,15 +44,15 @@ dependencies:
44
44
  version: 3.2.13
45
45
  none: false
46
46
  - !ruby/object:Gem::Dependency
47
- prerelease: false
48
- name: google_business_api_url_signer
49
- type: :runtime
50
47
  version_requirements: !ruby/object:Gem::Requirement
51
48
  requirements:
52
49
  - - ~>
53
50
  - !ruby/object:Gem::Version
54
51
  version: 0.0.1
55
52
  none: false
53
+ name: google_business_api_url_signer
54
+ type: :runtime
55
+ prerelease: false
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ~>
@@ -60,15 +60,15 @@ dependencies:
60
60
  version: 0.0.1
61
61
  none: false
62
62
  - !ruby/object:Gem::Dependency
63
- prerelease: false
64
- name: bundler
65
- type: :development
66
63
  version_requirements: !ruby/object:Gem::Requirement
67
64
  requirements:
68
65
  - - ~>
69
66
  - !ruby/object:Gem::Version
70
67
  version: '1.3'
71
68
  none: false
69
+ name: bundler
70
+ type: :development
71
+ prerelease: false
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
74
  - - ~>
@@ -76,15 +76,15 @@ dependencies:
76
76
  version: '1.3'
77
77
  none: false
78
78
  - !ruby/object:Gem::Dependency
79
- prerelease: false
80
- name: rspec
81
- type: :development
82
79
  version_requirements: !ruby/object:Gem::Requirement
83
80
  requirements:
84
81
  - - ~>
85
82
  - !ruby/object:Gem::Version
86
83
  version: '2.13'
87
84
  none: false
85
+ name: rspec
86
+ type: :development
87
+ prerelease: false
88
88
  requirement: !ruby/object:Gem::Requirement
89
89
  requirements:
90
90
  - - ~>
@@ -92,15 +92,15 @@ dependencies:
92
92
  version: '2.13'
93
93
  none: false
94
94
  - !ruby/object:Gem::Dependency
95
- prerelease: false
96
- name: shoulda-matchers
97
- type: :development
98
95
  version_requirements: !ruby/object:Gem::Requirement
99
96
  requirements:
100
97
  - - ~>
101
98
  - !ruby/object:Gem::Version
102
99
  version: 2.0.0
103
100
  none: false
101
+ name: shoulda-matchers
102
+ type: :development
103
+ prerelease: false
104
104
  requirement: !ruby/object:Gem::Requirement
105
105
  requirements:
106
106
  - - ~>
@@ -108,15 +108,15 @@ dependencies:
108
108
  version: 2.0.0
109
109
  none: false
110
110
  - !ruby/object:Gem::Dependency
111
- prerelease: false
112
- name: webmock
113
- type: :development
114
111
  version_requirements: !ruby/object:Gem::Requirement
115
112
  requirements:
116
113
  - - ~>
117
114
  - !ruby/object:Gem::Version
118
115
  version: '1.11'
119
116
  none: false
117
+ name: webmock
118
+ type: :development
119
+ prerelease: false
120
120
  requirement: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - ~>
@@ -124,15 +124,15 @@ dependencies:
124
124
  version: '1.11'
125
125
  none: false
126
126
  - !ruby/object:Gem::Dependency
127
- prerelease: false
128
- name: rake
129
- type: :development
130
127
  version_requirements: !ruby/object:Gem::Requirement
131
128
  requirements:
132
129
  - - ! '>='
133
130
  - !ruby/object:Gem::Version
134
131
  version: '0'
135
132
  none: false
133
+ name: rake
134
+ type: :development
135
+ prerelease: false
136
136
  requirement: !ruby/object:Gem::Requirement
137
137
  requirements:
138
138
  - - ! '>='
@@ -148,6 +148,7 @@ extra_rdoc_files: []
148
148
  files:
149
149
  - .gitignore
150
150
  - .rbenv-version
151
+ - CHANGELOG.md
151
152
  - Gemfile
152
153
  - LICENSE.txt
153
154
  - README.md
@@ -155,6 +156,7 @@ files:
155
156
  - google_distance_matrix.gemspec
156
157
  - lib/google_distance_matrix.rb
157
158
  - lib/google_distance_matrix/client.rb
159
+ - lib/google_distance_matrix/client_cache.rb
158
160
  - lib/google_distance_matrix/configuration.rb
159
161
  - lib/google_distance_matrix/errors.rb
160
162
  - lib/google_distance_matrix/log_subscriber.rb
@@ -167,6 +169,7 @@ files:
167
169
  - lib/google_distance_matrix/routes_finder.rb
168
170
  - lib/google_distance_matrix/url_builder.rb
169
171
  - lib/google_distance_matrix/version.rb
172
+ - spec/lib/google_distance_matrix/client_cache_spec.rb
170
173
  - spec/lib/google_distance_matrix/client_spec.rb
171
174
  - spec/lib/google_distance_matrix/configuration_spec.rb
172
175
  - spec/lib/google_distance_matrix/logger_spec.rb
@@ -190,18 +193,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
190
193
  requirements:
191
194
  - - ! '>='
192
195
  - !ruby/object:Gem::Version
193
- segments:
194
- - 0
195
- hash: -254032637546983924
196
196
  version: '0'
197
197
  none: false
198
198
  required_rubygems_version: !ruby/object:Gem::Requirement
199
199
  requirements:
200
200
  - - ! '>='
201
201
  - !ruby/object:Gem::Version
202
- segments:
203
- - 0
204
- hash: -254032637546983924
205
202
  version: '0'
206
203
  none: false
207
204
  requirements: []
@@ -211,6 +208,7 @@ signing_key:
211
208
  specification_version: 3
212
209
  summary: Ruby client for The Google Distance Matrix API
213
210
  test_files:
211
+ - spec/lib/google_distance_matrix/client_cache_spec.rb
214
212
  - spec/lib/google_distance_matrix/client_spec.rb
215
213
  - spec/lib/google_distance_matrix/configuration_spec.rb
216
214
  - spec/lib/google_distance_matrix/logger_spec.rb