google_distance_matrix 0.4.0 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +16 -0
  3. data/.rubocop.yml +6 -0
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +4 -6
  6. data/CHANGELOG.md +40 -0
  7. data/Gemfile +2 -0
  8. data/README.md +0 -3
  9. data/Rakefile +9 -4
  10. data/google_distance_matrix.gemspec +21 -19
  11. data/lib/google_distance_matrix.rb +25 -23
  12. data/lib/google_distance_matrix/client.rb +32 -18
  13. data/lib/google_distance_matrix/client_cache.rb +9 -3
  14. data/lib/google_distance_matrix/configuration.rb +39 -24
  15. data/lib/google_distance_matrix/errors.rb +6 -3
  16. data/lib/google_distance_matrix/log_subscriber.rb +14 -14
  17. data/lib/google_distance_matrix/logger.rb +7 -5
  18. data/lib/google_distance_matrix/matrix.rb +45 -22
  19. data/lib/google_distance_matrix/place.rb +37 -28
  20. data/lib/google_distance_matrix/places.rb +5 -4
  21. data/lib/google_distance_matrix/polyline_encoder.rb +2 -2
  22. data/lib/google_distance_matrix/polyline_encoder/delta.rb +4 -2
  23. data/lib/google_distance_matrix/polyline_encoder/value_encoder.rb +13 -5
  24. data/lib/google_distance_matrix/railtie.rb +4 -1
  25. data/lib/google_distance_matrix/route.rb +22 -15
  26. data/lib/google_distance_matrix/routes_finder.rb +27 -29
  27. data/lib/google_distance_matrix/url_builder.rb +44 -16
  28. data/lib/google_distance_matrix/url_builder/polyline_encoder_buffer.rb +3 -0
  29. data/lib/google_distance_matrix/version.rb +3 -1
  30. data/spec/lib/google_distance_matrix/client_cache_spec.rb +27 -11
  31. data/spec/lib/google_distance_matrix/client_spec.rb +40 -30
  32. data/spec/lib/google_distance_matrix/configuration_spec.rb +36 -24
  33. data/spec/lib/google_distance_matrix/log_subscriber_spec.rb +13 -44
  34. data/spec/lib/google_distance_matrix/logger_spec.rb +16 -13
  35. data/spec/lib/google_distance_matrix/matrix_spec.rb +90 -57
  36. data/spec/lib/google_distance_matrix/place_spec.rb +38 -25
  37. data/spec/lib/google_distance_matrix/places_spec.rb +29 -28
  38. data/spec/lib/google_distance_matrix/polyline_encoder/delta_spec.rb +5 -3
  39. data/spec/lib/google_distance_matrix/polyline_encoder_spec.rb +7 -2
  40. data/spec/lib/google_distance_matrix/route_spec.rb +11 -9
  41. data/spec/lib/google_distance_matrix/routes_finder_spec.rb +124 -81
  42. data/spec/lib/google_distance_matrix/url_builder_spec.rb +97 -48
  43. data/spec/spec_helper.rb +3 -1
  44. metadata +46 -18
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GoogleDistanceMatrix
2
4
  # Public: Error class for lib.
3
5
  class Error < StandardError
@@ -67,7 +69,8 @@ module GoogleDistanceMatrix
67
69
  end
68
70
 
69
71
  def to_s
70
- "GoogleDistanceMatrix::ClientError - #{[response, status_read_from_api_response].compact.join('. ')}."
72
+ "GoogleDistanceMatrix::ClientError - \
73
+ #{[response, status_read_from_api_response].compact.join('. ')}."
71
74
  end
72
75
  end
73
76
 
@@ -75,7 +78,8 @@ module GoogleDistanceMatrix
75
78
  #
76
79
  # See https://developers.google.com/maps/documentation/distancematrix/#Limits, which states:
77
80
  # "Distance Matrix API URLs are restricted to 2048 characters, before URL encoding."
78
- # "As some Distance Matrix API service URLs may involve many locations, be aware of this limit when constructing your URLs."
81
+ # "As some Distance Matrix API service URLs may involve many locations,
82
+ # be aware of this limit when constructing your URLs."
79
83
  #
80
84
  class MatrixUrlTooLong < ClientError
81
85
  attr_reader :url, :max_url_size
@@ -91,5 +95,4 @@ module GoogleDistanceMatrix
91
95
  "Matrix API URL max size is: #{max_url_size}. Built URL was: #{url.length}. URL: '#{url}'."
92
96
  end
93
97
  end
94
-
95
98
  end
@@ -1,8 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GoogleDistanceMatrix
4
+ # LogSubscriber logs to GoogleDistanceMatrix.logger by subscribing to gem's intstrumentation.
5
+ #
6
+ # NOTE: This log subscruber uses the default_configuration as it's configuration.
7
+ # This is relevant for example for the filter_parameters_in_logged_url configuration
2
8
  class LogSubscriber < ActiveSupport::LogSubscriber
3
9
  attr_reader :logger, :config
4
10
 
5
- def initialize(logger: GoogleDistanceMatrix.logger, config: GoogleDistanceMatrix.default_configuration)
11
+ def initialize(
12
+ logger: GoogleDistanceMatrix.logger,
13
+ config: GoogleDistanceMatrix.default_configuration
14
+ )
6
15
  super()
7
16
 
8
17
  @logger = logger
@@ -10,20 +19,11 @@ module GoogleDistanceMatrix
10
19
  end
11
20
 
12
21
  def client_request_matrix_data(event)
13
- url = filter_url! event.payload[:url]
14
- logger.info "(#{event.duration}ms) (elements: #{event.payload[:elements]}) GET #{url}", tag: :client
15
- end
16
-
17
- private
18
-
19
- def filter_url!(url)
20
- config.filter_parameters_in_logged_url.each do |param|
21
- url.gsub! %r{(#{param})=.*?(&|$)}, '\1=[FILTERED]\2'
22
- end
23
-
24
- url
22
+ url = event.payload[:filtered_url]
23
+ logger.info "(#{event.duration}ms) (elements: #{event.payload[:elements]}) GET #{url}",
24
+ tag: :client
25
25
  end
26
26
  end
27
27
  end
28
28
 
29
- GoogleDistanceMatrix::LogSubscriber.attach_to "google_distance_matrix"
29
+ GoogleDistanceMatrix::LogSubscriber.attach_to 'google_distance_matrix'
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GoogleDistanceMatrix
4
+ # Logger class for Google Distance Matrix
2
5
  class Logger
3
- PREFIXES = %w[google_distance_matrix]
4
- LEVELS = %w[fatal error warn info debug]
6
+ PREFIXES = %w[google_distance_matrix].freeze
7
+ LEVELS = %w[fatal error warn info debug].freeze
5
8
 
6
9
  attr_reader :backend
7
10
 
@@ -16,17 +19,16 @@ module GoogleDistanceMatrix
16
19
  msg = args.first
17
20
  tags = PREFIXES.dup.concat Array.wrap(options[:tag])
18
21
 
19
- backend.public_send level, tag_msg(msg, tags) if backend
22
+ backend&.public_send level, tag_msg(msg, tags)
20
23
  end
21
24
  end
22
25
 
23
-
24
26
  private
25
27
 
26
28
  def tag_msg(msg, tags)
27
29
  msg_buffer = tags.map { |tag| "[#{tag}]" }
28
30
  msg_buffer << msg
29
- msg_buffer.join " "
31
+ msg_buffer.join ' '
30
32
  end
31
33
  end
32
34
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GoogleDistanceMatrix
2
4
  # Public: Represents a distance matrix.
3
5
  #
@@ -25,23 +27,25 @@ module GoogleDistanceMatrix
25
27
  # config.mode = "walking"
26
28
  # end
27
29
  #
28
- # You can set default configuration by doing: GoogleDistanceMatrix.configure_defaults { |c| c.sensor = true }
30
+ # You can set default configuration by doing:
31
+ # GoogleDistanceMatrix.configure_defaults { |c| c.sensor = true }
29
32
  #
30
33
  #
31
34
  # Query API and get the matrix back
32
35
  #
33
36
  # matrix.data # Returns a two dimensional array.
34
37
  # # Rows are ordered according to the values in the origins.
35
- # # Each row corresponds to an origin, and each element within that row corresponds to
38
+ # # Each row corresponds to an origin, and each element
39
+ # # within that row corresponds to
36
40
  # # a pairing of the origin with a destination.
37
41
  #
38
42
  #
39
43
  class Matrix
40
44
  include ActiveModel::Validations
41
45
 
42
- validates :origins, length: {minimum: 1, too_short: "must have at least one origin"}
43
- validates :destinations, length: {minimum: 1, too_short: "must have at least one destination"}
44
- validate { errors.add(:configuration, "is invalid") if configuration.invalid? }
46
+ validates :origins, length: { minimum: 1, too_short: 'must have at least one origin' }
47
+ validates :destinations, length: { minimum: 1, too_short: 'must have at least one destination' }
48
+ validate { errors.add(:configuration, 'is invalid') if configuration.invalid? }
45
49
 
46
50
  attr_reader :origins, :destinations, :configuration
47
51
 
@@ -53,6 +57,7 @@ module GoogleDistanceMatrix
53
57
  @configuration = attributes[:configuration] || GoogleDistanceMatrix.default_configuration.dup
54
58
  end
55
59
 
60
+ delegate :sensitive_url, :filtered_url, to: :url_builder
56
61
 
57
62
  delegate :route_for, :routes_for, to: :routes_finder
58
63
  delegate :route_for!, :routes_for!, to: :routes_finder
@@ -61,7 +66,6 @@ module GoogleDistanceMatrix
61
66
  delegate :shortest_route_by_duration_in_traffic_to, to: :routes_finder
62
67
  delegate :shortest_route_by_duration_in_traffic_to!, to: :routes_finder
63
68
 
64
-
65
69
  # Public: The data for this matrix.
66
70
  #
67
71
  # Returns a two dimensional array, the matrix's data
@@ -83,40 +87,59 @@ module GoogleDistanceMatrix
83
87
  @data.present?
84
88
  end
85
89
 
86
-
87
-
88
90
  def configure
89
91
  yield configuration
90
92
  end
91
93
 
92
- def url
93
- UrlBuilder.new(self).url
94
- end
95
-
96
-
97
94
  def inspect
98
95
  attributes = %w[origins destinations]
99
- attributes << "data" if loaded?
96
+ attributes << 'data' if loaded?
100
97
  inspection = attributes.map { |a| "#{a}: #{public_send(a).inspect}" }.join ', '
101
98
 
102
99
  "#<#{self.class} #{inspection}>"
103
100
  end
104
101
 
105
-
106
102
  private
107
103
 
104
+ def url_builder
105
+ # We do not keep url builder as an instance variable as origins and destinations
106
+ # may be added after URL is being built for the first time. We should either
107
+ # make our matrix immutable or reset if origins/destinations are added after data (and
108
+ # the url) first being built and data fetched.
109
+ UrlBuilder.new self
110
+ end
111
+
108
112
  def routes_finder
109
113
  @routes_finder ||= RoutesFinder.new self
110
114
  end
111
115
 
112
116
  def load_matrix
113
- parsed = JSON.parse client.get(url, instrumentation: {elements: origins.length * destinations.length}).body
117
+ body = client.get(
118
+ sensitive_url,
119
+ instrumentation: instrumentation_for_api_request,
120
+ configuration: configuration
121
+ ).body
122
+
123
+ parsed = JSON.parse(body)
124
+ create_route_objects_for_parsed_data parsed
125
+ end
126
+
127
+ def instrumentation_for_api_request
128
+ {
129
+ elements: origins.length * destinations.length,
130
+ sensitive_url: sensitive_url,
131
+ filtered_url: filtered_url
132
+ }
133
+ end
114
134
 
115
- parsed["rows"].each_with_index.map do |row, origin_index|
135
+ def create_route_objects_for_parsed_data(parsed)
136
+ parsed['rows'].each_with_index.map do |row, origin_index|
116
137
  origin = origins[origin_index]
117
138
 
118
- row["elements"].each_with_index.map do |element, destination_index|
119
- route_attributes = element.merge(origin: origin, destination: destinations[destination_index])
139
+ row['elements'].each_with_index.map do |element, destination_index|
140
+ route_attributes = element.merge(
141
+ origin: origin, destination: destinations[destination_index]
142
+ )
120
143
  Route.new route_attributes
121
144
  end
122
145
  end
@@ -133,9 +156,9 @@ module GoogleDistanceMatrix
133
156
  end
134
157
 
135
158
  def clear_from_cache!
136
- if configuration.cache
137
- configuration.cache.delete ClientCache.key(url)
138
- end
159
+ return if configuration.cache.nil?
160
+
161
+ configuration.cache.delete ClientCache.key(sensitive_url, configuration)
139
162
  end
140
163
  end
141
164
  end
@@ -1,28 +1,31 @@
1
- # Public: Represents a place and knows how to convert it to param.
2
- #
3
- # Examples
4
- #
5
- # GoogleDistanceMatrix::Place.new address: "My address"
6
- # GoogleDistanceMatrix::Place.new lat: 1, lng: 3
7
- #
8
- # You may also build places by other objects responding to lat and lng or address.
9
- # If your object responds to all of the attributes we'll use lat and lng as data
10
- # for the Place.
11
- #
12
- # GoogleDistanceMatrix::Place.new object
1
+ # frozen_string_literal: true
2
+
13
3
  module GoogleDistanceMatrix
4
+ # Public: Represents a place and knows how to convert it to param.
5
+ #
6
+ # Examples
7
+ #
8
+ # GoogleDistanceMatrix::Place.new address: "My address"
9
+ # GoogleDistanceMatrix::Place.new lat: 1, lng: 3
10
+ #
11
+ # You may also build places by other objects responding to lat and lng or address.
12
+ # If your object responds to all of the attributes we'll use lat and lng as data
13
+ # for the Place.
14
+ #
15
+ # GoogleDistanceMatrix::Place.new object
14
16
  class Place
15
- ATTRIBUTES = %w[address lat lng]
17
+ ATTRIBUTES = %w[address lat lng].freeze
16
18
 
17
- attr_reader *ATTRIBUTES, :extracted_attributes_from
19
+ attr_reader(*ATTRIBUTES, :extracted_attributes_from)
18
20
 
19
21
  def initialize(attributes_or_object)
20
22
  if respond_to_needed_attributes? attributes_or_object
21
23
  extract_and_assign_attributes_from_object attributes_or_object
22
24
  elsif attributes_or_object.is_a? Hash
25
+ @extracted_attributes_from = attributes_or_object.with_indifferent_access
23
26
  assign_attributes attributes_or_object
24
27
  else
25
- fail ArgumentError, "Must be either hash or object responding to lat, lng or address. "
28
+ raise ArgumentError, 'Must be either hash or object responding to lat, lng or address. '
26
29
  end
27
30
 
28
31
  validate_attributes
@@ -33,14 +36,16 @@ module GoogleDistanceMatrix
33
36
  address.present? ? address : lat_lng(options[:lat_lng_scale]).join(',')
34
37
  end
35
38
 
36
-
37
39
  def eql?(other)
40
+ return false unless other.is_a? self.class
41
+
38
42
  if address.present?
39
43
  address == other.address
40
44
  else
41
45
  lat_lng == other.lat_lng
42
46
  end
43
47
  end
48
+ alias == eql?
44
49
 
45
50
  def lat_lng?
46
51
  lat.present? && lng.present?
@@ -58,7 +63,9 @@ module GoogleDistanceMatrix
58
63
  end
59
64
 
60
65
  def inspect
61
- inspection = (ATTRIBUTES | [:extracted_attributes_from]).reject { |a| public_send(a).blank? }.map { |a| "#{a}: #{public_send(a).inspect}" }.join ', '
66
+ inspection = (ATTRIBUTES | [:extracted_attributes_from])
67
+ .reject { |a| public_send(a).blank? }
68
+ .map { |a| "#{a}: #{public_send(a).inspect}" }.join ', '
62
69
 
63
70
  "#<#{self.class} #{inspection}>"
64
71
  end
@@ -66,19 +73,15 @@ module GoogleDistanceMatrix
66
73
  private
67
74
 
68
75
  def respond_to_needed_attributes?(object)
69
- (object.respond_to?(:lat) && object.respond_to?(:lng)) || object.respond_to?(:address)
76
+ (object.respond_to?(:lat) && object.respond_to?(:lng)) || object.respond_to?(:address)
70
77
  end
71
78
 
72
79
  def extract_and_assign_attributes_from_object(object)
73
- attrs = Hash[ATTRIBUTES.map do |attr_name|
74
- if object.respond_to? attr_name
75
- [attr_name, object.public_send(attr_name)]
76
- end
80
+ attrs = Hash[ATTRIBUTES.map do |attr_name|
81
+ [attr_name, object.public_send(attr_name)] if object.respond_to? attr_name
77
82
  end.compact]
78
83
 
79
- if attrs.has_key?('lat') || attrs.has_key?('lng')
80
- attrs.delete 'address'
81
- end
84
+ attrs.delete 'address' if attrs.key?('lat') || attrs.key?('lng')
82
85
 
83
86
  @extracted_attributes_from = object
84
87
  assign_attributes attrs
@@ -92,14 +95,20 @@ module GoogleDistanceMatrix
92
95
  @lng = attributes[:lng]
93
96
  end
94
97
 
98
+ # rubocop:disable Metrics/AbcSize
99
+ # rubocop:disable Metrics/CyclomaticComplexity
100
+ # rubocop:disable Style/GuardClause
95
101
  def validate_attributes
96
- unless address.present? || (lat.present? && lng.present?)
97
- fail ArgumentError, "Must provide an address, or lat and lng."
102
+ unless address.present? || (lat.present? && lng.present?)
103
+ raise ArgumentError, 'Must provide an address, or lat and lng.'
98
104
  end
99
105
 
100
106
  if address.present? && lat.present? && lng.present?
101
- fail ArgumentError, "Cannot provide address, lat and lng."
107
+ raise ArgumentError, 'Cannot provide address, lat and lng.'
102
108
  end
103
109
  end
110
+ # rubocop:enable Metrics/AbcSize
111
+ # rubocop:enable Metrics/CyclomaticComplexity
112
+ # rubocop:enable Style/GuardClause
104
113
  end
105
114
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GoogleDistanceMatrix
4
+ # Represents a collection of places
2
5
  class Places
3
6
  include Enumerable
4
7
 
@@ -7,10 +10,9 @@ module GoogleDistanceMatrix
7
10
  concat Array.wrap(places)
8
11
  end
9
12
 
10
-
11
13
  delegate :each, :[], :length, :index, :pop, :shift, :delete_at, :compact, :inspect, to: :places
12
14
 
13
- [:<<, :push, :unshift].each do |method|
15
+ %i[<< push unshift].each do |method|
14
16
  define_method method do |*args|
15
17
  args = ensure_args_are_places args
16
18
 
@@ -22,10 +24,9 @@ module GoogleDistanceMatrix
22
24
  end
23
25
 
24
26
  def concat(other)
25
- push *other
27
+ push(*other)
26
28
  end
27
29
 
28
-
29
30
  private
30
31
 
31
32
  attr_reader :places
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'polyline_encoder/delta'
2
4
  require_relative 'polyline_encoder/value_encoder'
3
5
 
@@ -7,7 +9,6 @@ module GoogleDistanceMatrix
7
9
  #
8
10
  # See https://developers.google.com/maps/documentation/utilities/polylinealgorithm
9
11
  class PolylineEncoder
10
-
11
12
  # Encodes a set of lat/lng pairs
12
13
  #
13
14
  # Example
@@ -16,7 +17,6 @@ module GoogleDistanceMatrix
16
17
  new(array_of_lat_lng_pairs).encode
17
18
  end
18
19
 
19
-
20
20
  # Initialize a new encoder
21
21
  #
22
22
  # Arguments
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GoogleDistanceMatrix
2
4
  class PolylineEncoder
3
5
  # Calculates deltas between lat_lng values, internal helper class for PolylineEncoder.
@@ -20,7 +22,6 @@ module GoogleDistanceMatrix
20
22
  calculate_deltas rounded
21
23
  end
22
24
 
23
-
24
25
  private
25
26
 
26
27
  def round_to_precision(array_of_lat_lng_pairs)
@@ -42,7 +43,8 @@ module GoogleDistanceMatrix
42
43
  deltas << lat - delta_lat
43
44
  deltas << lng - delta_lng
44
45
 
45
- delta_lat, delta_lng = lat, lng
46
+ delta_lat = lat
47
+ delta_lng = lng
46
48
  end
47
49
 
48
50
  deltas
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/NumericPredicate
1
4
  module GoogleDistanceMatrix
2
5
  class PolylineEncoder
3
6
  # Encodes a single value, like 17998321, in to encoded polyline value,
@@ -9,6 +12,8 @@ module GoogleDistanceMatrix
9
12
  #
10
13
  # @see GoogleDistanceMatrix::PolylineEncoder
11
14
  class ValueEncoder
15
+ # rubocop:disable Metrics/MethodLength
16
+ # rubocop:disable Metrics/AbcSize
12
17
  def encode(value)
13
18
  negative = value < 0
14
19
  value = value.abs
@@ -27,7 +32,7 @@ module GoogleDistanceMatrix
27
32
  # Right shift bits to get rid of the ones we just put on the array.
28
33
  # Bits will end up in reverse order.
29
34
  chunks_of_5_bits = []
30
- while value > 0 do
35
+ while value > 0
31
36
  chunks_of_5_bits.push(value & 0x1f)
32
37
  value >>= 5
33
38
  end
@@ -44,6 +49,8 @@ module GoogleDistanceMatrix
44
49
  # step 11: Convert to ASCII
45
50
  chunks_of_5_bits.map(&:chr)
46
51
  end
52
+ # rubocop:enable Metrics/MethodLength
53
+ # rubocop:enable Metrics/AbcSize
47
54
 
48
55
  private
49
56
 
@@ -51,11 +58,12 @@ module GoogleDistanceMatrix
51
58
  #
52
59
  # Example of usage
53
60
  # p d 17998321 # => "00000001 00010010 10100001 11110001"
54
- def d(v, bits = 32, chunk_size = 8)
55
- (bits - 1).downto(0).
56
- map { |n| v[n] }.
57
- each_slice(chunk_size).map { |chunk| chunk.join }.join ' '
61
+ def d(val, bits = 32, chunk_size = 8)
62
+ (bits - 1).downto(0)
63
+ .map { |n| val[n] }
64
+ .each_slice(chunk_size).map(&:join).join ' '
58
65
  end
59
66
  end
60
67
  end
61
68
  end
69
+ # rubocop:enable Style/NumericPredicate