google_distance_matrix 0.4.0 → 0.6.3

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.
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