google_distance_matrix 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rbenv-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +106 -0
- data/Rakefile +1 -0
- data/google_distance_matrix.gemspec +30 -0
- data/lib/google_distance_matrix.rb +38 -0
- data/lib/google_distance_matrix/client.rb +47 -0
- data/lib/google_distance_matrix/configuration.rb +68 -0
- data/lib/google_distance_matrix/errors.rb +88 -0
- data/lib/google_distance_matrix/log_subscriber.rb +14 -0
- data/lib/google_distance_matrix/logger.rb +32 -0
- data/lib/google_distance_matrix/matrix.rb +122 -0
- data/lib/google_distance_matrix/place.rb +101 -0
- data/lib/google_distance_matrix/places.rb +43 -0
- data/lib/google_distance_matrix/railtie.rb +9 -0
- data/lib/google_distance_matrix/route.rb +49 -0
- data/lib/google_distance_matrix/routes_finder.rb +149 -0
- data/lib/google_distance_matrix/url_builder.rb +63 -0
- data/lib/google_distance_matrix/version.rb +3 -0
- data/spec/lib/google_distance_matrix/client_spec.rb +67 -0
- data/spec/lib/google_distance_matrix/configuration_spec.rb +63 -0
- data/spec/lib/google_distance_matrix/logger_spec.rb +38 -0
- data/spec/lib/google_distance_matrix/matrix_spec.rb +169 -0
- data/spec/lib/google_distance_matrix/place_spec.rb +93 -0
- data/spec/lib/google_distance_matrix/places_spec.rb +77 -0
- data/spec/lib/google_distance_matrix/route_spec.rb +28 -0
- data/spec/lib/google_distance_matrix/routes_finder_spec.rb +190 -0
- data/spec/lib/google_distance_matrix/url_builder_spec.rb +105 -0
- data/spec/request_recordings/success +62 -0
- data/spec/request_recordings/zero_results +57 -0
- data/spec/spec_helper.rb +24 -0
- metadata +225 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
module GoogleDistanceMatrix
|
2
|
+
class UrlBuilder
|
3
|
+
BASE_URL = "maps.googleapis.com/maps/api/distancematrix/json"
|
4
|
+
DELIMITER = CGI.escape("|")
|
5
|
+
MAX_URL_SIZE = 2048
|
6
|
+
|
7
|
+
attr_reader :matrix
|
8
|
+
delegate :configuration, to: :matrix
|
9
|
+
|
10
|
+
def initialize(matrix)
|
11
|
+
@matrix = matrix
|
12
|
+
|
13
|
+
fail InvalidMatrix.new matrix if matrix.invalid?
|
14
|
+
end
|
15
|
+
|
16
|
+
def url
|
17
|
+
@url ||= build_url
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def build_url
|
24
|
+
url = [protocol, BASE_URL, "?", get_params_string].join
|
25
|
+
|
26
|
+
if sign_url?
|
27
|
+
url = GoogleBusinessApiUrlSigner.add_signature(url, configuration.google_business_api_private_key)
|
28
|
+
end
|
29
|
+
|
30
|
+
if url.length > MAX_URL_SIZE
|
31
|
+
fail MatrixUrlTooLong.new url, MAX_URL_SIZE
|
32
|
+
end
|
33
|
+
|
34
|
+
url
|
35
|
+
end
|
36
|
+
|
37
|
+
def sign_url?
|
38
|
+
configuration.google_business_api_client_id.present? and
|
39
|
+
configuration.google_business_api_private_key.present?
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_params_string
|
43
|
+
params.to_a.map { |key_value| key_value.join("=") }.join("&")
|
44
|
+
end
|
45
|
+
|
46
|
+
def params
|
47
|
+
places_to_param_config = {lat_lng_scale: configuration.lat_lng_scale}
|
48
|
+
|
49
|
+
configuration.to_param.merge(
|
50
|
+
origins: matrix.origins.map { |o| escape o.to_param(places_to_param_config) }.join(DELIMITER),
|
51
|
+
destinations: matrix.destinations.map { |d| escape d.to_param(places_to_param_config) }.join(DELIMITER),
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def protocol
|
56
|
+
configuration.protocol + "://"
|
57
|
+
end
|
58
|
+
|
59
|
+
def escape(string)
|
60
|
+
CGI.escape string
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GoogleDistanceMatrix::Client, :request_recordings do
|
4
|
+
let(:origin_1) { GoogleDistanceMatrix::Place.new address: "Karl Johans gate, Oslo" }
|
5
|
+
let(:destination_1) { GoogleDistanceMatrix::Place.new address: "Drammensveien 1, Oslo" }
|
6
|
+
let(:matrix) { GoogleDistanceMatrix::Matrix.new(origins: [origin_1], destinations: [destination_1]) }
|
7
|
+
|
8
|
+
let(:url_builder) { GoogleDistanceMatrix::UrlBuilder.new matrix }
|
9
|
+
let(:url) { url_builder.url }
|
10
|
+
|
11
|
+
subject { GoogleDistanceMatrix::Client.new }
|
12
|
+
|
13
|
+
describe "success" do
|
14
|
+
before { stub_request(:get, url).to_return body: recorded_request_for(:success) }
|
15
|
+
|
16
|
+
it "makes the request" do
|
17
|
+
expect(subject.get(url_builder.url).body).to eq recorded_request_for(:success).read
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "client errors" do
|
22
|
+
describe "server issues 4xx client error" do
|
23
|
+
it "wraps the error http response" do
|
24
|
+
stub_request(:get, url).to_return status: [400, "Client error"]
|
25
|
+
expect { subject.get(url_builder.url) }.to raise_error GoogleDistanceMatrix::ClientError
|
26
|
+
end
|
27
|
+
|
28
|
+
it "wraps uri too long error" do
|
29
|
+
stub_request(:get, url).to_return status: [414, "Client error"]
|
30
|
+
expect { subject.get(url_builder.url) }.to raise_error GoogleDistanceMatrix::MatrixUrlTooLong
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
described_class::CLIENT_ERRORS.each do |error|
|
35
|
+
it "wraps '#{error}' client error" do
|
36
|
+
stub_request(:get, url).to_return body: JSON.generate({status: error})
|
37
|
+
expect { subject.get(url_builder.url) }.to raise_error GoogleDistanceMatrix::ClientError
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "request errors" do
|
43
|
+
describe "server error" do
|
44
|
+
before { stub_request(:get, url).to_return status: [500, "Internal Server Error"] }
|
45
|
+
|
46
|
+
it "wraps the error http response" do
|
47
|
+
expect { subject.get(url_builder.url) }.to raise_error GoogleDistanceMatrix::RequestError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "timeout" do
|
52
|
+
before { stub_request(:get, url).to_timeout }
|
53
|
+
|
54
|
+
it "wraps the error from Net::HTTP" do
|
55
|
+
expect { subject.get(url_builder.url).body }.to raise_error GoogleDistanceMatrix::RequestError
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "server error" do
|
60
|
+
before { stub_request(:get, url).to_return status: [999, "Unknown"] }
|
61
|
+
|
62
|
+
it "wraps the error http response" do
|
63
|
+
expect { subject.get(url_builder.url) }.to raise_error GoogleDistanceMatrix::RequestError
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GoogleDistanceMatrix::Configuration do
|
4
|
+
subject { described_class.new }
|
5
|
+
|
6
|
+
describe "Validations" do
|
7
|
+
it { should ensure_inclusion_of(:sensor).in_array([true, false]) }
|
8
|
+
|
9
|
+
it { should ensure_inclusion_of(:mode).in_array(["driving", "walking", "bicycling"]) }
|
10
|
+
it { should allow_value(nil).for(:mode) }
|
11
|
+
|
12
|
+
it { should ensure_inclusion_of(:avoid).in_array(["tolls", "highways"]) }
|
13
|
+
it { should allow_value(nil).for(:avoid) }
|
14
|
+
|
15
|
+
it { should ensure_inclusion_of(:units).in_array(["metric", "imperial"]) }
|
16
|
+
it { should allow_value(nil).for(:units) }
|
17
|
+
|
18
|
+
it { should ensure_inclusion_of(:protocol).in_array(["http", "https"]) }
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
describe "defaults" do
|
23
|
+
its(:sensor) { should be_false }
|
24
|
+
its(:mode) { should eq "driving" }
|
25
|
+
its(:avoid) { should be_nil }
|
26
|
+
its(:units) { should eq "metric" }
|
27
|
+
its(:lat_lng_scale) { should eq 5 }
|
28
|
+
its(:protocol) { should eq "http" }
|
29
|
+
|
30
|
+
its(:google_business_api_client_id) { should be_nil }
|
31
|
+
its(:google_business_api_private_key) { should be_nil }
|
32
|
+
|
33
|
+
its(:logger) { should be_nil }
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
describe "#to_param" do
|
38
|
+
described_class::ATTRIBUTES.each do |attr|
|
39
|
+
it "includes #{attr}" do
|
40
|
+
subject[attr] = "foo"
|
41
|
+
expect(subject.to_param[attr]).to eq subject.public_send(attr)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "does not include #{attr} when it is blank" do
|
45
|
+
subject[attr] = nil
|
46
|
+
expect(subject.to_param.with_indifferent_access).to_not have_key attr
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
described_class::API_DEFAULTS.each_pair do |attr, default_value|
|
51
|
+
it "does not include #{attr} when it equals what is default for API" do
|
52
|
+
subject[attr] = default_value
|
53
|
+
|
54
|
+
expect(subject.to_param.with_indifferent_access).to_not have_key attr
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "includes client if google_business_api_client_id has been set" do
|
59
|
+
subject.google_business_api_client_id = "123"
|
60
|
+
expect(subject.to_param['client']).to eq "123"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GoogleDistanceMatrix::Logger do
|
4
|
+
context "without a logger backend" do
|
5
|
+
subject { described_class.new }
|
6
|
+
|
7
|
+
described_class::LEVELS.each do |level|
|
8
|
+
it "logging #{level} does not fail" do
|
9
|
+
subject.public_send level, "log msg"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "with a logger backend" do
|
15
|
+
let(:backend) { mock }
|
16
|
+
|
17
|
+
subject { described_class.new backend }
|
18
|
+
|
19
|
+
described_class::LEVELS.each do |level|
|
20
|
+
describe level do
|
21
|
+
it "sends log message to the backend" do
|
22
|
+
backend.should_receive(level).with("[google_distance_matrix] log msg")
|
23
|
+
subject.public_send level, "log msg"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "supports sending in a tag" do
|
27
|
+
backend.should_receive(level).with("[google_distance_matrix] [client] log msg")
|
28
|
+
subject.public_send level, "log msg", tag: :client
|
29
|
+
end
|
30
|
+
|
31
|
+
it "supports sending in multiple tags" do
|
32
|
+
backend.should_receive(level).with("[google_distance_matrix] [client] [request] log msg")
|
33
|
+
subject.public_send level, "log msg", tag: ['client', 'request']
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GoogleDistanceMatrix::Matrix do
|
4
|
+
let(:origin_1) { GoogleDistanceMatrix::Place.new address: "Karl Johans gate, Oslo" }
|
5
|
+
let(:origin_2) { GoogleDistanceMatrix::Place.new address: "Askerveien 1, Asker" }
|
6
|
+
|
7
|
+
let(:destination_1) { GoogleDistanceMatrix::Place.new address: "Drammensveien 1, Oslo" }
|
8
|
+
let(:destination_2) { GoogleDistanceMatrix::Place.new address: "Skjellestadhagen, Heggedal" }
|
9
|
+
|
10
|
+
let(:url_builder) { GoogleDistanceMatrix::UrlBuilder.new subject }
|
11
|
+
let(:url) { url_builder.url }
|
12
|
+
|
13
|
+
subject do
|
14
|
+
described_class.new(
|
15
|
+
origins: [origin_1, origin_2],
|
16
|
+
destinations: [destination_1, destination_2]
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#initialize" do
|
21
|
+
it "takes a list of origins" do
|
22
|
+
matrix = described_class.new origins: [origin_1, origin_2]
|
23
|
+
expect(matrix.origins).to include origin_1, origin_2
|
24
|
+
end
|
25
|
+
|
26
|
+
it "takes a list of destinations" do
|
27
|
+
matrix = described_class.new destinations: [destination_1, destination_2]
|
28
|
+
expect(matrix.destinations).to include destination_1, destination_2
|
29
|
+
end
|
30
|
+
|
31
|
+
it "has a default configuration" do
|
32
|
+
expect(subject.configuration).to be_present
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#configuration" do
|
37
|
+
it "is by default set from default_configuration" do
|
38
|
+
config = mock
|
39
|
+
config.stub(:dup).and_return config
|
40
|
+
GoogleDistanceMatrix.should_receive(:default_configuration).and_return config
|
41
|
+
|
42
|
+
expect(described_class.new.configuration).to eq config
|
43
|
+
end
|
44
|
+
|
45
|
+
it "has it's own configuration" do
|
46
|
+
expect {
|
47
|
+
subject.configure { |c| c.sensor = !GoogleDistanceMatrix.default_configuration.sensor }
|
48
|
+
}.to_not change(GoogleDistanceMatrix.default_configuration, :sensor)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "has a configurable configuration :-)" do
|
52
|
+
expect {
|
53
|
+
subject.configure { |c| c.sensor = !GoogleDistanceMatrix.default_configuration.sensor }
|
54
|
+
}.to change(subject.configuration, :sensor).to !GoogleDistanceMatrix.default_configuration.sensor
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
%w[origins destinations].each do |attr|
|
59
|
+
let(:place) { GoogleDistanceMatrix::Place.new address: "My street" }
|
60
|
+
|
61
|
+
describe "##{attr}" do
|
62
|
+
it "can receive places" do
|
63
|
+
subject.public_send(attr) << place
|
64
|
+
expect(subject.public_send(attr)).to include place
|
65
|
+
end
|
66
|
+
|
67
|
+
it "does not same place twice" do
|
68
|
+
expect {
|
69
|
+
2.times { subject.public_send(attr) << place }
|
70
|
+
}.to change(subject.public_send(attr), :length).by 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
%w[
|
76
|
+
route_for
|
77
|
+
route_for!
|
78
|
+
routes_for
|
79
|
+
routes_for!
|
80
|
+
shortest_route_by_duration_to
|
81
|
+
shortest_route_by_duration_to!
|
82
|
+
shortest_route_by_distance_to
|
83
|
+
shortest_route_by_distance_to!
|
84
|
+
].each do |method|
|
85
|
+
it "delegates #{method} to routes_finder" do
|
86
|
+
finder = mock
|
87
|
+
result = mock
|
88
|
+
|
89
|
+
subject.stub(:routes_finder).and_return finder
|
90
|
+
|
91
|
+
finder.should_receive(method).and_return result
|
92
|
+
expect(subject.public_send(method)).to eq result
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#data", :request_recordings do
|
97
|
+
context "success" do
|
98
|
+
let!(:api_request_stub) { stub_request(:get, url).to_return body: recorded_request_for(:success) }
|
99
|
+
|
100
|
+
it "loads from Google's API" do
|
101
|
+
subject.data
|
102
|
+
api_request_stub.should have_been_requested
|
103
|
+
end
|
104
|
+
|
105
|
+
it "does not load twice" do
|
106
|
+
2.times { subject.data }
|
107
|
+
api_request_stub.should have_been_requested
|
108
|
+
end
|
109
|
+
|
110
|
+
it "contains one row" do
|
111
|
+
expect(subject.data.length).to eq 2
|
112
|
+
end
|
113
|
+
|
114
|
+
it "contains two columns each row" do
|
115
|
+
expect(subject.data[0].length).to eq 2
|
116
|
+
expect(subject.data[1].length).to eq 2
|
117
|
+
end
|
118
|
+
|
119
|
+
it "assigns correct origin on routes in the data" do
|
120
|
+
expect(subject.data[0][0].origin).to eq origin_1
|
121
|
+
expect(subject.data[0][1].origin).to eq origin_1
|
122
|
+
|
123
|
+
expect(subject.data[1][0].origin).to eq origin_2
|
124
|
+
expect(subject.data[1][1].origin).to eq origin_2
|
125
|
+
end
|
126
|
+
|
127
|
+
it "assigns correct destination on routes in the data" do
|
128
|
+
expect(subject.data[0][0].destination).to eq destination_1
|
129
|
+
expect(subject.data[0][1].destination).to eq destination_2
|
130
|
+
|
131
|
+
expect(subject.data[1][0].destination).to eq destination_1
|
132
|
+
expect(subject.data[1][1].destination).to eq destination_2
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context "some elements is not OK" do
|
137
|
+
let!(:api_request_stub) { stub_request(:get, url).to_return body: recorded_request_for(:zero_results) }
|
138
|
+
|
139
|
+
it "loads from Google's API" do
|
140
|
+
subject.data
|
141
|
+
api_request_stub.should have_been_requested
|
142
|
+
end
|
143
|
+
|
144
|
+
it "as loaded route with errors correctly" do
|
145
|
+
route = subject.data[0][1]
|
146
|
+
|
147
|
+
expect(route.status).to eq "zero_results"
|
148
|
+
expect(route.duration_in_seconds).to be_nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "#reload" do
|
154
|
+
before do
|
155
|
+
subject.stub(:load_matrix).and_return { ['loaded'] }
|
156
|
+
subject.data.clear
|
157
|
+
end
|
158
|
+
|
159
|
+
it "reloads matrix' data from the API" do
|
160
|
+
expect {
|
161
|
+
subject.reload
|
162
|
+
}.to change(subject, :data).from([]).to ['loaded']
|
163
|
+
end
|
164
|
+
|
165
|
+
it "is chainable" do
|
166
|
+
expect(subject.reload.data).to eq ['loaded']
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GoogleDistanceMatrix::Place do
|
4
|
+
let(:address) { "Karl Johans gate, Oslo" }
|
5
|
+
let(:lat) { 1.4 }
|
6
|
+
let(:lng) { 2.2 }
|
7
|
+
|
8
|
+
describe "#initialize" do
|
9
|
+
it "builds with an address" do
|
10
|
+
place = described_class.new(address: address)
|
11
|
+
expect(place.address).to eq address
|
12
|
+
end
|
13
|
+
|
14
|
+
it "builds with lat lng" do
|
15
|
+
place = described_class.new(lat: lat, lng: lng)
|
16
|
+
expect(place.lat).to eq lat
|
17
|
+
expect(place.lng).to eq lng
|
18
|
+
end
|
19
|
+
|
20
|
+
it "builds with an object responding to lat and lng" do
|
21
|
+
point = mock lat: 1, lng: 2
|
22
|
+
place = described_class.new(point)
|
23
|
+
|
24
|
+
expect(place.lat).to eq point.lat
|
25
|
+
expect(place.lng).to eq point.lng
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
it "keeps a record of the object it built itself from" do
|
30
|
+
point = mock lat: 1, lng: 2
|
31
|
+
place = described_class.new(point)
|
32
|
+
|
33
|
+
expect(place.extracted_attributes_from).to eq point
|
34
|
+
end
|
35
|
+
it "builds with an object responding to address" do
|
36
|
+
object = mock address: address
|
37
|
+
place = described_class.new(object)
|
38
|
+
|
39
|
+
expect(place.address).to eq object.address
|
40
|
+
end
|
41
|
+
|
42
|
+
it "builds with an object responding to lat, lng and address" do
|
43
|
+
object = mock lat: 1, lng:2, address: address
|
44
|
+
place = described_class.new(object)
|
45
|
+
|
46
|
+
expect(place.lat).to eq object.lat
|
47
|
+
expect(place.lng).to eq object.lng
|
48
|
+
expect(place.address).to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it "fails if no valid attributes given" do
|
52
|
+
expect { described_class.new }.to raise_error ArgumentError
|
53
|
+
expect { described_class.new(lat: lat) }.to raise_error ArgumentError
|
54
|
+
expect { described_class.new(lng: lng) }.to raise_error ArgumentError
|
55
|
+
end
|
56
|
+
|
57
|
+
it "fails if both address, lat ang lng is given" do
|
58
|
+
expect { described_class.new(address: address, lat: lat, lng: lng) }.to raise_error ArgumentError
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#to_param" do
|
63
|
+
context "with address" do
|
64
|
+
subject { described_class.new address: address }
|
65
|
+
|
66
|
+
its(:to_param) { should eq address }
|
67
|
+
end
|
68
|
+
|
69
|
+
context "with lat lng" do
|
70
|
+
subject { described_class.new lng: lng, lat: lat }
|
71
|
+
|
72
|
+
its(:to_param) { should eq "#{lat},#{lng}" }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#equal?" do
|
77
|
+
it "is considered equal when address is the same" do
|
78
|
+
expect(described_class.new(address: address)).to be_eql described_class.new(address: address)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "is considered equal when lat and lng are the same" do
|
82
|
+
expect(described_class.new(lat: lat, lng: lng)).to be_eql described_class.new(lat: lat, lng: lng)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "is not considered equal when address differs" do
|
86
|
+
expect(described_class.new(address: address)).to_not be_eql described_class.new(address: address + ", Norway")
|
87
|
+
end
|
88
|
+
|
89
|
+
it "is not considered equal when lat or lng differs" do
|
90
|
+
expect(described_class.new(lat: lat, lng: lng)).to_not be_eql described_class.new(lat: lat, lng: lng + 1)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|