diff --git a/.github/workflows/rspec_push.yaml b/.github/workflows/rspec_push.yaml index 23bd043..9bf9360 100644 --- a/.github/workflows/rspec_push.yaml +++ b/.github/workflows/rspec_push.yaml @@ -33,7 +33,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: "3.3.0" + ruby-version: "3.2.0" - name: Install dependencies run: | diff --git a/.github/workflows/rubocop.yaml b/.github/workflows/rubocop.yaml index 9f96c32..87dd025 100644 --- a/.github/workflows/rubocop.yaml +++ b/.github/workflows/rubocop.yaml @@ -12,7 +12,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.3.0 + ruby-version: 3.2.0 - name: Install dependencies run: | diff --git a/.gitignore b/.gitignore index 99df4ae..9dccfb7 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,7 @@ dump.rdb # Ignore application configuration /config/application.yml -/terraform/.terraform/* +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml diff --git a/.rubocop.yml b/.rubocop.yml index d1abbf4..57e8c36 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,7 +3,7 @@ require: - ./lib/rubocop/init_autoloader AllCops: - TargetRubyVersion: 3.3.0 + TargetRubyVersion: 3.2.0 Exclude: - "db/**/*" - "config/**/*" @@ -22,10 +22,16 @@ Layout/ArgumentAlignment: Layout/IndentationWidth: Enabled: true Width: 2 +Layout/DotPosition: + EnforcedStyle: leading Layout/EndAlignment: EnforcedStyleAlignWith: variable Layout/MultilineAssignmentLayout: EnforcedStyle: same_line +Layout/MultilineMethodCallIndentation: + EnforcedStyle: indented +Style/MethodCallWithArgsParentheses: + EnforcedStyle: require_parentheses Layout/SpaceInsideBlockBraces: EnforcedStyle: space Lint/UnusedMethodArgument: diff --git a/.ruby-version b/.ruby-version index 03463f3..944880f 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-3.3.0 +3.2.0 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..c23af94 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.2.0 diff --git a/Dockerfile b/Dockerfile index 9c28fe4..db481fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax = docker/dockerfile:1 # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile -ARG RUBY_VERSION=3.3.0 +ARG RUBY_VERSION=3.2.0 FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base # Rails app lives here @@ -9,9 +9,9 @@ WORKDIR /rails # Set production environment ENV RAILS_ENV="production" \ - BUNDLE_DEPLOYMENT="1" \ - BUNDLE_PATH="/usr/local/bundle" \ - BUNDLE_WITHOUT="development" + BUNDLE_DEPLOYMENT="1" \ + BUNDLE_PATH="/usr/local/bundle" \ + BUNDLE_WITHOUT="development" # Throw-away build stage to reduce size of final image @@ -19,13 +19,13 @@ FROM base as build # Install packages needed to build gems RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config + apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config # Install application gems COPY Gemfile Gemfile.lock ./ RUN bundle install && \ - rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ - bundle exec bootsnap precompile --gemfile + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ + bundle exec bootsnap precompile --gemfile # Copy application code COPY . . @@ -39,8 +39,8 @@ FROM base # Install packages needed for deployment RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y curl libvips postgresql-client && \ - rm -rf /var/lib/apt/lists /var/cache/apt/archives + apt-get install --no-install-recommends -y curl libvips postgresql-client && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives # Copy built artifacts: gems, application COPY --from=build /usr/local/bundle /usr/local/bundle @@ -48,7 +48,7 @@ COPY --from=build /rails /rails # Run and own only the runtime files as a non-root user for security RUN useradd rails --create-home --shell /bin/bash && \ - chown -R rails:rails db log storage tmp + chown -R rails:rails db log storage tmp USER rails:rails # Entrypoint prepares the database. diff --git a/Gemfile b/Gemfile index 483d356..648941b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,13 +1,14 @@ # frozen_string_literal: true source "https://rubygems.org" -ruby "3.3.0" +ruby "3.2.0" gem "bootsnap", require: false gem "database_cleaner" gem "faker" gem "faraday" gem "figaro" gem "geocoder" +gem "jsonapi-serializer" gem "money-rails" gem "pg", "~> 1.1" gem "puma", ">= 5.0" diff --git a/Gemfile.lock b/Gemfile.lock index f015af6..e38e66c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -131,6 +131,8 @@ GEM rdoc (>= 4.0.0) reline (>= 0.4.2) json (2.7.2) + jsonapi-serializer (2.2.0) + activesupport (>= 4.2) language_server-protocol (3.17.0.3) loofah (2.22.0) crass (~> 1.0.2) @@ -323,6 +325,7 @@ PLATFORMS aarch64-linux arm-linux arm64-darwin-22 + arm64-darwin-23 x86-linux x86_64-darwin x86_64-linux @@ -337,6 +340,7 @@ DEPENDENCIES faraday figaro geocoder + jsonapi-serializer money-rails pg (~> 1.1) pry-rails @@ -354,7 +358,7 @@ DEPENDENCIES webmock RUBY VERSION - ruby 3.3.0p0 + ruby 3.2.0p0 BUNDLED WITH 2.5.13 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 13c271f..aadf6ca 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,14 @@ # frozen_string_literal: true class ApplicationController < ActionController::API + private def pagination_params + params.permit(:limit, :offset) + end + private def limit + pagination_params[:limit] || 2 + end + + private def offset + pagination_params[:offset] || 0 + end end diff --git a/app/controllers/drivers/selectable_rides_controller.rb b/app/controllers/drivers/selectable_rides_controller.rb new file mode 100644 index 0000000..623b89a --- /dev/null +++ b/app/controllers/drivers/selectable_rides_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Drivers + class SelectableRidesController < ApplicationController + def index + rides = Rides::Commands::RankRides.call(driver:)[offset, limit] + opts = { include: %i[from_address to_address] } + render json: RidePojoSerializer.new(rides, opts) + end + + private def driver + @driver ||= Driver.find(ride_params[:driver_id]) + end + + private def ride_params + params.permit(:driver_id, :limit, :offset) + end + end +end diff --git a/app/controllers/drivers_controller.rb b/app/controllers/drivers_controller.rb new file mode 100644 index 0000000..a295377 --- /dev/null +++ b/app/controllers/drivers_controller.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class DriversController < ApplicationController + def index + drivers = Driver.limit(limit).offset(offset) + render json: DriverSerializer.new(drivers, is_collection: true) + end + + private def pagination_params + params.permit(:limit, :offset) + end +end diff --git a/app/models/driver.rb b/app/models/driver.rb index 145f47a..631a4b1 100644 --- a/app/models/driver.rb +++ b/app/models/driver.rb @@ -11,6 +11,10 @@ class Driver < ApplicationRecord validates :first_name, :last_name, presence: true + def full_name + [first_name, last_name].join(" ") + end + def origin_place_id current_address.place_id end diff --git a/app/models/ride.rb b/app/models/ride.rb index fda9e66..28ee224 100644 --- a/app/models/ride.rb +++ b/app/models/ride.rb @@ -14,14 +14,26 @@ class Ride < ApplicationRecord } scope :by_address, ->(address_id) { - where(from_address_id: address_id).or(where(to_address_id: address_id)) - } + where(from_address_id: address_id).or(where(to_address_id: address_id)) + } + scope :selectable, -> { includes(:from_address, :to_address) .select(:id, :from_address_id, :to_address_id) .where(driver_id: nil, duration: nil, distance: nil, commute_duration: nil, amount_cents: 0) } + scope :nearby_driver, ->(driver) { + current_driver_address = driver.current_address + # Due to the manner in which Geocoder computes and orders this, we need to first + # get the addresses within the driver's desired radius and _then_ find the rides + # with corresponding from_address_id + addresses = Address.where.not(id: current_driver_address.id) + .near([current_driver_address.latitude, current_driver_address.longitude], driver.max_radius) + + selectable.where(from_address_id: addresses.map(&:id)) + } + def origin_place_id from_address.place_id end diff --git a/app/serializers/address_serializer.rb b/app/serializers/address_serializer.rb new file mode 100644 index 0000000..0764751 --- /dev/null +++ b/app/serializers/address_serializer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddressSerializer + include JSONAPI::Serializer + set_type :address + attributes :id, :line_1, :line_2, :city, :state, :zip_code, :full_address +end diff --git a/app/serializers/driver_serializer.rb b/app/serializers/driver_serializer.rb new file mode 100644 index 0000000..f82a9aa --- /dev/null +++ b/app/serializers/driver_serializer.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class DriverSerializer + include JSONAPI::Serializer + set_type :driver + attributes :id, :full_name + has_one :current_address, serializer: AddressSerializer +end diff --git a/app/serializers/ride_pojo_serializer.rb b/app/serializers/ride_pojo_serializer.rb new file mode 100644 index 0000000..d5c226e --- /dev/null +++ b/app/serializers/ride_pojo_serializer.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class RidePojoSerializer + include JSONAPI::Serializer + extend ActionView::Helpers::DateHelper + extend ActionView::Helpers::NumberHelper + + set_id do |struct, _params| + struct.ride_id + end + + set_type "pre-ride" + + attribute :distance do |struct, _params| + number_to_human((struct.distance_meters / 1609.34).round(2), units: :distance) + end + + attribute :duration do |struct, _params| + distance_of_time_in_words(struct.duration.to_f) + end + + attribute :commute_duration do |struct, _params| + distance_of_time_in_words(struct.commute_duration.to_f) + end + + attribute :ride_earnings do |struct, _params| + struct.ride_amount.format + end + + belongs_to :from_address, serializer: AddressSerializer + belongs_to :to_address, serializer: AddressSerializer +end diff --git a/config/environments/development.rb b/config/environments/development.rb index 2bff298..1bd8ba1 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -8,11 +8,15 @@ # since you don't have to restart the web server when you make code changes. config.enable_reloading = true + # preserve response format with errors + config.debug_exception_response_format = :api + + # Do not eager load code on boot. config.eager_load = false # Show full error reports. - config.consider_all_requests_local = true + config.consider_all_requests_local = false # Enable server timing config.server_timing = true diff --git a/config/environments/test.rb b/config/environments/test.rb index adbb4a6..0aef472 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -24,7 +24,7 @@ } # Show full error reports and disable caching. - config.consider_all_requests_local = true + config.consider_all_requests_local = false config.action_controller.perform_caching = false config.cache_store = :null_store diff --git a/config/initializers/geocoder.rb b/config/initializers/geocoder.rb index 893601a..4e9a09b 100644 --- a/config/initializers/geocoder.rb +++ b/config/initializers/geocoder.rb @@ -10,7 +10,7 @@ api_key: ENV["GOOGLE_API_KEY"], # geocoding service request timeout, in seconds (default 3): - timeout: 5, + timeout: 15, # set default units to kilometers: units: :mi, diff --git a/config/locales/en.yml b/config/locales/en.yml index 6c349ae..46784c1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -29,3 +29,10 @@ en: hello: "Hello world" + distance: + centi: + one: "foot" + other: "feet" + unit: + one: "mile" + other: "miles" diff --git a/config/routes.rb b/config/routes.rb index a125ef0..33ed0d6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,5 +6,10 @@ get "up" => "rails/health#show", as: :rails_health_check # Defines the root path route ("/") - # root "posts#index" + # root "drivers#index" + + resources :drivers, only: %i[index] do + # get "rides", on: :member + resources :selectable_rides, only: :index, module: :drivers + end end diff --git a/db/migrate/20240704202204_add_max_radius_to_driver.rb b/db/migrate/20240704202204_add_max_radius_to_driver.rb new file mode 100644 index 0000000..a91792b --- /dev/null +++ b/db/migrate/20240704202204_add_max_radius_to_driver.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddMaxRadiusToDriver < ActiveRecord::Migration[7.1] + def change + add_column :drivers, :max_radius, :integer, default: 10 + add_index :drivers, :max_radius + end +end diff --git a/db/schema.rb b/db/schema.rb index 4b02b60..526ea56 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_07_02_210854) do +ActiveRecord::Schema[7.1].define(version: 2024_07_04_202204) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -48,6 +48,8 @@ t.string "last_name", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "max_radius", default: 10 + t.index ["max_radius"], name: "index_drivers_on_max_radius" end create_table "rides", force: :cascade do |t| diff --git a/db/seeds.rb b/db/seeds.rb index 15741d2..0126b70 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -17,14 +17,10 @@ 3.times do first_name = Faker::Name.first_name last_name = Faker::Name.last_name - Driver.create!(first_name:,last_name:) - end - - # Create home address for driver - puts "Creating current Addresses for Drivers..." - Driver.find_each.with_index do |driver, index| - address = Address.create(ADDRESSES[index]) - driver.create_current_driver_address(address:, current: true) + driver = Driver.build(first_name:,last_name:) + address = Address.create(ADDRESSES.sample) + driver.driver_addresses.build(address_id: address.id, current: true) + driver.save! end puts "Creating remaining Addresses..." diff --git a/lib/client/request.rb b/lib/client/request.rb index cb4cb72..41099a2 100644 --- a/lib/client/request.rb +++ b/lib/client/request.rb @@ -13,6 +13,10 @@ def post(url, body, headers = nil) connection.post(url, body.to_json, headers) end + def get(url, params = nil, headers = nil) + connection.get(url, params, headers) + end + attr_reader :connection private :connection diff --git a/lib/rides/commands/get_commute_duration.rb b/lib/rides/commands/get_commute_duration.rb index f37ea78..4f55659 100644 --- a/lib/rides/commands/get_commute_duration.rb +++ b/lib/rides/commands/get_commute_duration.rb @@ -8,8 +8,8 @@ module Commands # Returns a list of objects where the origin is the driver's current address(home) class GetCommuteDuration < BaseCommand def call(rides:, driver:) - commute_rides = convert_rides(rides, driver) - GetRoutesData.call(rides: commute_rides) + converted_rides_for_commute = convert_rides(rides, driver) + GetRoutesData.call(rides: converted_rides_for_commute) end # Converts the Driver's current home address and diff --git a/lib/rides/commands/get_routes_data.rb b/lib/rides/commands/get_routes_data.rb index 1f86e1a..659147c 100644 --- a/lib/rides/commands/get_routes_data.rb +++ b/lib/rides/commands/get_routes_data.rb @@ -2,7 +2,11 @@ module Rides module Commands - # Makes a request to the Google API to obtain the route information + # Makes a request to the Google API to obtain the route information for all the + # selectable rides for a given driver. We use the Route Matrix to ensure we are making as few calls + # as possible. The Route Matrix returns all possible routes for the given origins/destinations. + # Even though we only care about one combo per route, it is still more efficient to do it in this manner. + # We then compare the indexes and get the ones that match, which gives us our original desired routes. class GetRoutesData < BaseCommand CACHE = Cache::Store DIRECTIONS_API_URL = "https://routes.googleapis.com/distanceMatrix/v2:computeRouteMatrix" @@ -29,7 +33,19 @@ def call(rides:) data = data.select { _1[:originIndex] == _1[:destinationIndex] } data = transform_keys!(data) - data.map.with_index { OpenStruct.new(ride: rides[_2], **_1) } + combine_routes_data!(data, rides) + end + + # The manner in which jsonapi-serializer serialzies pojos, + # in order to adhere to the json api spec, we need to define + # the id _and_ the object iteself. + private def combine_routes_data!(data, rides) + data.map.with_index do |d, idx| + ride = rides[idx] + OpenStruct.new(ride_id: ride.id, from_address_id: ride.from_address&.id, + from_address: ride.from_address, to_address: ride.to_address, + to_address_id: ride.to_address&.id, **d) + end end private def connection diff --git a/lib/rides/commands/rank_rides.rb b/lib/rides/commands/rank_rides.rb new file mode 100644 index 0000000..e125ef8 --- /dev/null +++ b/lib/rides/commands/rank_rides.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +module Rides + module Commands + class RankRides < BaseCommand + def call(driver:) + rank!(driver) + end + + # How the Sorting Works + # 1. Calculate the Key: For each ride, calculate the earnings per hour using rank_key. + # 2. Negate the Key: By using -rank_key, we convert the highest earnings per hour to the smallest negative + # number, and the smallest earnings per hour to the largest negative number. + # 3. Sort in Ascending Order: Ruby's sort_by method will then sort the rides based on these negative values, + # effectively putting the rides with the highest earnings per hour first. + private def rank!(driver) + rides = combined_ride_data(driver) + return [] if rides.empty? + + rides.sort_by { |ride| -rank_key(ride) } + end + + # (ride earnings) / (commute duration + ride duration) + private def rank_key(ride) + earnings = ride.ride_amount.amount + ride_duration_hours = duration_to_hours(ride.duration) + commute_duration_hours = duration_to_hours(ride.commute_duration) + total_duration_hours = ride_duration_hours + commute_duration_hours + + # Earnings per hour + earnings / total_duration_hours + end + + private def duration_to_hours(duration) + duration.to_i / 3600.0 + end + + # Once we get the data, we want to combine them into one struct + # to more easily compute the ride earnings and assign a rank + private def combined_ride_data(driver) + commutes = commute_durations(driver) + rides = route_data(driver) + + if commutes.count != rides.count + raise RideCountMismatchError, + "The number of rides doesn't match the number of commute rides." \ + "Please check the ride(s) configuration and try again." + end + + combine_rides!(rides, commutes) + end + + # Combines the duration data for the driver to get to the start of the ride + # with that of the ride's data, e.g., duration, distance + private def combine_rides!(rides, commutes) + rides.map.with_index do |ride, idx| + commute = commutes.fetch(idx) + commute_duration = commute.duration + commute_distance = commute.distance_meters + ride_amount = ComputeAmount.call(ride:) + + ride.commute_duration = commute_duration + ride.commute_distance = commute_distance + ride.ride_amount = ride_amount + ride + end + end + + # Get the driving duration to the Ride#from_address, + # to get the duration of the commute to start the ride + private def commute_durations(driver) + rides = selectable_rides_near_driver(driver) + @commute_durations ||= GetCommuteDuration.call(rides:, driver:) + end + + # Get route data for the ride, the duration and the distance between + # the from and to addresses + private def route_data(driver) + rides = selectable_rides_near_driver(driver) + @route_data ||= GetRoutesData.call(rides:) + end + + private def selectable_rides_near_driver(driver) + @selectable_rides_near_driver ||= Ride.selectable.nearby_driver(driver) + end + end + end +end diff --git a/spec/cassettes/nearby_drivers.yml b/spec/cassettes/nearby_drivers.yml new file mode 100644 index 0000000..3e7acb3 --- /dev/null +++ b/spec/cassettes/nearby_drivers.yml @@ -0,0 +1,335 @@ +--- +http_interactions: +- request: + method: get + uri: https://maps.googleapis.com/maps/api/geocode/json?address=4705%20Weitzel%20Street,%20Timnath,%20CO,%2080547&key=&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Sat, 06 Jul 2024 18:25:27 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '2811' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=102 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: "{\n \"results\" : \n [\n {\n \"address_components\" + : \n [\n {\n \"long_name\" : \"4705\",\n + \ \"short_name\" : \"4705\",\n \"types\" : \n [\n + \ \"street_number\"\n ]\n },\n {\n + \ \"long_name\" : \"Weitzel Street\",\n \"short_name\" + : \"Weitzel Street\",\n \"types\" : \n [\n \"route\"\n + \ ]\n },\n {\n \"long_name\" + : \"Fort Collins\",\n \"short_name\" : \"Fort Collins\",\n \"types\" + : \n [\n \"locality\",\n \"political\"\n + \ ]\n },\n {\n \"long_name\" + : \"Larimer County\",\n \"short_name\" : \"Larimer County\",\n + \ \"types\" : \n [\n \"administrative_area_level_2\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Colorado\",\n \"short_name\" + : \"CO\",\n \"types\" : \n [\n \"administrative_area_level_1\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"United States\",\n \"short_name\" + : \"US\",\n \"types\" : \n [\n \"country\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"80528\",\n \"short_name\" : + \"80528\",\n \"types\" : \n [\n \"postal_code\"\n + \ ]\n }\n ],\n \"formatted_address\" + : \"4705 Weitzel Street, Fort Collins, CO 80528, USA\",\n \"geometry\" + : \n {\n \"bounds\" : \n {\n \"northeast\" + : \n {\n \"lat\" : 40.5226661,\n \"lng\" + : -104.9882057\n },\n \"southwest\" : \n {\n + \ \"lat\" : 40.5193081,\n \"lng\" : -104.9906206\n + \ }\n },\n \"location\" : \n {\n + \ \"lat\" : 40.5210627,\n \"lng\" : -104.9892691\n + \ },\n \"location_type\" : \"GEOMETRIC_CENTER\",\n \"viewport\" + : \n {\n \"northeast\" : \n {\n \"lat\" + : 40.5226661,\n \"lng\" : -104.9880641697085\n },\n + \ \"southwest\" : \n {\n \"lat\" + : 40.5193081,\n \"lng\" : -104.9907621302915\n }\n + \ }\n },\n \"place_id\" : \"ChIJ0zbP73SzbocR7mXVIY-QdBM\",\n + \ \"types\" : \n [\n \"premise\"\n ]\n }\n + \ ],\n \"status\" : \"OK\"\n}" + recorded_at: Sat, 06 Jul 2024 18:25:27 GMT +- request: + method: get + uri: https://maps.googleapis.com/maps/api/geocode/json?address=151%20N%20College%20Ave,%20Fort%20Collins,%20CO,%2080524&key=&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Sat, 06 Jul 2024 18:25:27 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '3265' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=75 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: "{\n \"results\" : \n [\n {\n \"address_components\" + : \n [\n {\n \"long_name\" : \"151\",\n \"short_name\" + : \"151\",\n \"types\" : \n [\n \"street_number\"\n + \ ]\n },\n {\n \"long_name\" + : \"North College Avenue\",\n \"short_name\" : \"N College Ave\",\n + \ \"types\" : \n [\n \"route\"\n + \ ]\n },\n {\n \"long_name\" + : \"Old Town West\",\n \"short_name\" : \"Old Town West\",\n + \ \"types\" : \n [\n \"neighborhood\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Fort Collins\",\n \"short_name\" + : \"Fort Collins\",\n \"types\" : \n [\n \"locality\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Larimer County\",\n \"short_name\" + : \"Larimer County\",\n \"types\" : \n [\n \"administrative_area_level_2\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Colorado\",\n \"short_name\" + : \"CO\",\n \"types\" : \n [\n \"administrative_area_level_1\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"United States\",\n \"short_name\" + : \"US\",\n \"types\" : \n [\n \"country\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"80524\",\n \"short_name\" : + \"80524\",\n \"types\" : \n [\n \"postal_code\"\n + \ ]\n },\n {\n \"long_name\" + : \"2443\",\n \"short_name\" : \"2443\",\n \"types\" + : \n [\n \"postal_code_suffix\"\n ]\n + \ }\n ],\n \"formatted_address\" : \"151 N College + Ave, Fort Collins, CO 80524, USA\",\n \"geometry\" : \n {\n + \ \"bounds\" : \n {\n \"northeast\" : \n + \ {\n \"lat\" : 40.5881931,\n \"lng\" + : -105.0772479\n },\n \"southwest\" : \n {\n + \ \"lat\" : 40.5881176,\n \"lng\" : -105.0779597\n + \ }\n },\n \"location\" : \n {\n + \ \"lat\" : 40.5881554,\n \"lng\" : -105.0776036\n + \ },\n \"location_type\" : \"ROOFTOP\",\n \"viewport\" + : \n {\n \"northeast\" : \n {\n \"lat\" + : 40.58950433029149,\n \"lng\" : -105.0761845197085\n },\n + \ \"southwest\" : \n {\n \"lat\" + : 40.5868063697085,\n \"lng\" : -105.0788824802915\n }\n + \ }\n },\n \"place_id\" : \"ChIJlRJnwIpKaYcRYzG0fYZOwpY\",\n + \ \"types\" : \n [\n \"premise\"\n ]\n }\n + \ ],\n \"status\" : \"OK\"\n}" + recorded_at: Sat, 06 Jul 2024 18:25:27 GMT +- request: + method: get + uri: https://maps.googleapis.com/maps/api/geocode/json?address=%20216%20N%20College%20Ave%20%23110,%20Fort%20Collins,%20CO,%2080524&key=&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Sat, 06 Jul 2024 18:25:27 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '2884' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=25 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: "{\n \"results\" : \n [\n {\n \"address_components\" + : \n [\n {\n \"long_name\" : \"#110\",\n + \ \"short_name\" : \"#110\",\n \"types\" : \n [\n + \ \"subpremise\"\n ]\n },\n {\n + \ \"long_name\" : \"216\",\n \"short_name\" : \"216\",\n + \ \"types\" : \n [\n \"street_number\"\n + \ ]\n },\n {\n \"long_name\" + : \"North College Avenue\",\n \"short_name\" : \"N College Ave\",\n + \ \"types\" : \n [\n \"route\"\n + \ ]\n },\n {\n \"long_name\" + : \"Fort Collins\",\n \"short_name\" : \"Fort Collins\",\n \"types\" + : \n [\n \"locality\",\n \"political\"\n + \ ]\n },\n {\n \"long_name\" + : \"Larimer County\",\n \"short_name\" : \"Larimer County\",\n + \ \"types\" : \n [\n \"administrative_area_level_2\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Colorado\",\n \"short_name\" + : \"CO\",\n \"types\" : \n [\n \"administrative_area_level_1\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"United States\",\n \"short_name\" + : \"US\",\n \"types\" : \n [\n \"country\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"80524\",\n \"short_name\" : + \"80524\",\n \"types\" : \n [\n \"postal_code\"\n + \ ]\n },\n {\n \"long_name\" + : \"2405\",\n \"short_name\" : \"2405\",\n \"types\" + : \n [\n \"postal_code_suffix\"\n ]\n + \ }\n ],\n \"formatted_address\" : \"216 N College + Ave #110, Fort Collins, CO 80524, USA\",\n \"geometry\" : \n {\n + \ \"location\" : \n {\n \"lat\" : 40.589498,\n + \ \"lng\" : -105.076696\n },\n \"location_type\" + : \"ROOFTOP\",\n \"viewport\" : \n {\n \"northeast\" + : \n {\n \"lat\" : 40.59075128029149,\n \"lng\" + : -105.0751790197085\n },\n \"southwest\" : \n + \ {\n \"lat\" : 40.5880533197085,\n \"lng\" + : -105.0778769802915\n }\n }\n },\n \"place_id\" + : \"ChIJZ2jj-IpKaYcRZz9wlp78T6g\",\n \"types\" : \n [\n \"subpremise\"\n + \ ]\n }\n ],\n \"status\" : \"OK\"\n}" + recorded_at: Sat, 06 Jul 2024 18:25:27 GMT +- request: + method: get + uri: https://maps.googleapis.com/maps/api/geocode/json?address=501%20W%2020th%20St,%20Greeley,%20CO,%2080639&key=&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Sat, 06 Jul 2024 18:25:27 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '2597' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=55 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: "{\n \"results\" : \n [\n {\n \"address_components\" + : \n [\n {\n \"long_name\" : \"501\",\n \"short_name\" + : \"501\",\n \"types\" : \n [\n \"street_number\"\n + \ ]\n },\n {\n \"long_name\" + : \"West 20th Street\",\n \"short_name\" : \"W 20th St\",\n + \ \"types\" : \n [\n \"route\"\n + \ ]\n },\n {\n \"long_name\" + : \"Greeley\",\n \"short_name\" : \"Greeley\",\n \"types\" + : \n [\n \"locality\",\n \"political\"\n + \ ]\n },\n {\n \"long_name\" + : \"Weld County\",\n \"short_name\" : \"Weld County\",\n \"types\" + : \n [\n \"administrative_area_level_2\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Colorado\",\n \"short_name\" + : \"CO\",\n \"types\" : \n [\n \"administrative_area_level_1\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"United States\",\n \"short_name\" + : \"US\",\n \"types\" : \n [\n \"country\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"80639\",\n \"short_name\" : + \"80639\",\n \"types\" : \n [\n \"postal_code\"\n + \ ]\n }\n ],\n \"formatted_address\" + : \"501 W 20th St, Greeley, CO 80639, USA\",\n \"geometry\" : \n {\n + \ \"location\" : \n {\n \"lat\" : 40.4064511,\n + \ \"lng\" : -104.6857479\n },\n \"location_type\" + : \"ROOFTOP\",\n \"viewport\" : \n {\n \"northeast\" + : \n {\n \"lat\" : 40.40766078029149,\n \"lng\" + : -104.6842588697085\n },\n \"southwest\" : \n + \ {\n \"lat\" : 40.40496281970849,\n \"lng\" + : -104.6869568302915\n }\n }\n },\n \"place_id\" + : \"ChIJQdPK3ZChbocR-Igukn6r7Rs\",\n \"plus_code\" : \n {\n + \ \"compound_code\" : \"C847+HP Greeley, CO\",\n \"global_code\" + : \"85GQC847+HP\"\n },\n \"types\" : \n [\n \"street_address\"\n + \ ]\n }\n ],\n \"status\" : \"OK\"\n}" + recorded_at: Sat, 06 Jul 2024 18:25:27 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/cassettes/ranked_rides.yml b/spec/cassettes/ranked_rides.yml new file mode 100644 index 0000000..ad2bc59 --- /dev/null +++ b/spec/cassettes/ranked_rides.yml @@ -0,0 +1,838 @@ +--- +http_interactions: +- request: + method: get + uri: https://maps.googleapis.com/maps/api/geocode/json?address=1221%20E%20Elizabeth%20St,%20Fort%20Collins,%20CO,%2080524&key=&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Sun, 07 Jul 2024 00:57:46 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '3024' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=121 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: "{\n \"results\" : \n [\n {\n \"address_components\" + : \n [\n {\n \"long_name\" : \"1221\",\n + \ \"short_name\" : \"1221\",\n \"types\" : \n [\n + \ \"street_number\"\n ]\n },\n {\n + \ \"long_name\" : \"East Elizabeth Street\",\n \"short_name\" + : \"E Elizabeth St\",\n \"types\" : \n [\n \"route\"\n + \ ]\n },\n {\n \"long_name\" + : \"Fort Collins\",\n \"short_name\" : \"Fort Collins\",\n \"types\" + : \n [\n \"locality\",\n \"political\"\n + \ ]\n },\n {\n \"long_name\" + : \"Larimer County\",\n \"short_name\" : \"Larimer County\",\n + \ \"types\" : \n [\n \"administrative_area_level_2\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Colorado\",\n \"short_name\" + : \"CO\",\n \"types\" : \n [\n \"administrative_area_level_1\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"United States\",\n \"short_name\" + : \"US\",\n \"types\" : \n [\n \"country\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"80524\",\n \"short_name\" : + \"80524\",\n \"types\" : \n [\n \"postal_code\"\n + \ ]\n },\n {\n \"long_name\" + : \"4066\",\n \"short_name\" : \"4066\",\n \"types\" + : \n [\n \"postal_code_suffix\"\n ]\n + \ }\n ],\n \"formatted_address\" : \"1221 E Elizabeth + St, Fort Collins, CO 80524, USA\",\n \"geometry\" : \n {\n + \ \"bounds\" : \n {\n \"northeast\" : \n + \ {\n \"lat\" : 40.5740995,\n \"lng\" + : -105.0547518\n },\n \"southwest\" : \n {\n + \ \"lat\" : 40.5739237,\n \"lng\" : -105.0555168\n + \ }\n },\n \"location\" : \n {\n + \ \"lat\" : 40.5740149,\n \"lng\" : -105.0551343\n + \ },\n \"location_type\" : \"ROOFTOP\",\n \"viewport\" + : \n {\n \"northeast\" : \n {\n \"lat\" + : 40.5753192302915,\n \"lng\" : -105.0537853197085\n },\n + \ \"southwest\" : \n {\n \"lat\" + : 40.5726212697085,\n \"lng\" : -105.0564832802915\n }\n + \ }\n },\n \"place_id\" : \"ChIJK-pLGN5KaYcR3uWeXBYE6ts\",\n + \ \"types\" : \n [\n \"premise\"\n ]\n }\n + \ ],\n \"status\" : \"OK\"\n}" + recorded_at: Sun, 07 Jul 2024 00:57:46 GMT +- request: + method: get + uri: https://maps.googleapis.com/maps/api/geocode/json?address=2121%20E%20Harmony%20Rd%20%23%20180,%20Fort%20Collins,%20CO,%2080528&key=&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Sun, 07 Jul 2024 00:57:46 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '2887' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=111 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: "{\n \"results\" : \n [\n {\n \"address_components\" + : \n [\n {\n \"long_name\" : \"# 180\",\n + \ \"short_name\" : \"# 180\",\n \"types\" : \n + \ [\n \"subpremise\"\n ]\n },\n + \ {\n \"long_name\" : \"2121\",\n \"short_name\" + : \"2121\",\n \"types\" : \n [\n \"street_number\"\n + \ ]\n },\n {\n \"long_name\" + : \"East Harmony Road\",\n \"short_name\" : \"E Harmony Rd\",\n + \ \"types\" : \n [\n \"route\"\n + \ ]\n },\n {\n \"long_name\" + : \"Fort Collins\",\n \"short_name\" : \"Fort Collins\",\n \"types\" + : \n [\n \"locality\",\n \"political\"\n + \ ]\n },\n {\n \"long_name\" + : \"Larimer County\",\n \"short_name\" : \"Larimer County\",\n + \ \"types\" : \n [\n \"administrative_area_level_2\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Colorado\",\n \"short_name\" + : \"CO\",\n \"types\" : \n [\n \"administrative_area_level_1\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"United States\",\n \"short_name\" + : \"US\",\n \"types\" : \n [\n \"country\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"80528\",\n \"short_name\" : + \"80528\",\n \"types\" : \n [\n \"postal_code\"\n + \ ]\n },\n {\n \"long_name\" + : \"3401\",\n \"short_name\" : \"3401\",\n \"types\" + : \n [\n \"postal_code_suffix\"\n ]\n + \ }\n ],\n \"formatted_address\" : \"2121 E Harmony + Rd # 180, Fort Collins, CO 80528, USA\",\n \"geometry\" : \n {\n + \ \"location\" : \n {\n \"lat\" : 40.5221078,\n + \ \"lng\" : -105.0368968\n },\n \"location_type\" + : \"ROOFTOP\",\n \"viewport\" : \n {\n \"northeast\" + : \n {\n \"lat\" : 40.52349463029149,\n \"lng\" + : -105.0356535697085\n },\n \"southwest\" : \n + \ {\n \"lat\" : 40.5207966697085,\n \"lng\" + : -105.0383515302915\n }\n }\n },\n \"place_id\" + : \"ChIJS_rwErJMaYcRp8Q9z60tJjQ\",\n \"types\" : \n [\n \"subpremise\"\n + \ ]\n }\n ],\n \"status\" : \"OK\"\n}" + recorded_at: Sun, 07 Jul 2024 00:57:46 GMT +- request: + method: get + uri: https://maps.googleapis.com/maps/api/geocode/json?address=1024%20S%20Lemay%20Ave,%20Fort%20Collins,%20CO,%2080524&key=&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Sun, 07 Jul 2024 00:57:46 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '3015' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=109 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: "{\n \"results\" : \n [\n {\n \"address_components\" + : \n [\n {\n \"long_name\" : \"1024\",\n + \ \"short_name\" : \"1024\",\n \"types\" : \n [\n + \ \"street_number\"\n ]\n },\n {\n + \ \"long_name\" : \"South Lemay Avenue\",\n \"short_name\" + : \"S Lemay Ave\",\n \"types\" : \n [\n \"route\"\n + \ ]\n },\n {\n \"long_name\" + : \"Fort Collins\",\n \"short_name\" : \"Fort Collins\",\n \"types\" + : \n [\n \"locality\",\n \"political\"\n + \ ]\n },\n {\n \"long_name\" + : \"Larimer County\",\n \"short_name\" : \"Larimer County\",\n + \ \"types\" : \n [\n \"administrative_area_level_2\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Colorado\",\n \"short_name\" + : \"CO\",\n \"types\" : \n [\n \"administrative_area_level_1\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"United States\",\n \"short_name\" + : \"US\",\n \"types\" : \n [\n \"country\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"80524\",\n \"short_name\" : + \"80524\",\n \"types\" : \n [\n \"postal_code\"\n + \ ]\n },\n {\n \"long_name\" + : \"3929\",\n \"short_name\" : \"3929\",\n \"types\" + : \n [\n \"postal_code_suffix\"\n ]\n + \ }\n ],\n \"formatted_address\" : \"1024 S Lemay + Ave, Fort Collins, CO 80524, USA\",\n \"geometry\" : \n {\n + \ \"bounds\" : \n {\n \"northeast\" : \n + \ {\n \"lat\" : 40.5728058,\n \"lng\" + : -105.0556003\n },\n \"southwest\" : \n {\n + \ \"lat\" : 40.5710308,\n \"lng\" : -105.0576701\n + \ }\n },\n \"location\" : \n {\n + \ \"lat\" : 40.5716484,\n \"lng\" : -105.0566547\n + \ },\n \"location_type\" : \"ROOFTOP\",\n \"viewport\" + : \n {\n \"northeast\" : \n {\n \"lat\" + : 40.5732687302915,\n \"lng\" : -105.0552862197085\n },\n + \ \"southwest\" : \n {\n \"lat\" + : 40.5705707697085,\n \"lng\" : -105.0579841802915\n }\n + \ }\n },\n \"place_id\" : \"ChIJRbgIZeBKaYcRRG_TIgGYoIw\",\n + \ \"types\" : \n [\n \"premise\"\n ]\n }\n + \ ],\n \"status\" : \"OK\"\n}" + recorded_at: Sun, 07 Jul 2024 00:57:46 GMT +- request: + method: get + uri: https://maps.googleapis.com/maps/api/geocode/json?address=1106%20E%20Prospect%20Rd,%20Fort%20Collins,%20CO,%2080525&key=&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Sun, 07 Jul 2024 00:57:47 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '3284' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=95 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: "{\n \"results\" : \n [\n {\n \"address_components\" + : \n [\n {\n \"long_name\" : \"1106\",\n + \ \"short_name\" : \"1106\",\n \"types\" : \n [\n + \ \"street_number\"\n ]\n },\n {\n + \ \"long_name\" : \"East Prospect Road\",\n \"short_name\" + : \"E Prospect Rd\",\n \"types\" : \n [\n \"route\"\n + \ ]\n },\n {\n \"long_name\" + : \"Highlander Heights\",\n \"short_name\" : \"Highlander Heights\",\n + \ \"types\" : \n [\n \"neighborhood\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Fort Collins\",\n \"short_name\" + : \"Fort Collins\",\n \"types\" : \n [\n \"locality\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Larimer County\",\n \"short_name\" + : \"Larimer County\",\n \"types\" : \n [\n \"administrative_area_level_2\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Colorado\",\n \"short_name\" + : \"CO\",\n \"types\" : \n [\n \"administrative_area_level_1\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"United States\",\n \"short_name\" + : \"US\",\n \"types\" : \n [\n \"country\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"80525\",\n \"short_name\" : + \"80525\",\n \"types\" : \n [\n \"postal_code\"\n + \ ]\n },\n {\n \"long_name\" + : \"5304\",\n \"short_name\" : \"5304\",\n \"types\" + : \n [\n \"postal_code_suffix\"\n ]\n + \ }\n ],\n \"formatted_address\" : \"1106 E Prospect + Rd, Fort Collins, CO 80525, USA\",\n \"geometry\" : \n {\n + \ \"bounds\" : \n {\n \"northeast\" : \n + \ {\n \"lat\" : 40.56795959999999,\n \"lng\" + : -105.0571725\n },\n \"southwest\" : \n {\n + \ \"lat\" : 40.5676192,\n \"lng\" : -105.0576284\n + \ }\n },\n \"location\" : \n {\n + \ \"lat\" : 40.5677337,\n \"lng\" : -105.0574767\n + \ },\n \"location_type\" : \"ROOFTOP\",\n \"viewport\" + : \n {\n \"northeast\" : \n {\n \"lat\" + : 40.56913788029149,\n \"lng\" : -105.0560383697085\n },\n + \ \"southwest\" : \n {\n \"lat\" + : 40.56643991970849,\n \"lng\" : -105.0587363302915\n }\n + \ }\n },\n \"place_id\" : \"ChIJ4U-oox9LaYcRrCvlPHIAuKI\",\n + \ \"types\" : \n [\n \"premise\"\n ]\n }\n + \ ],\n \"status\" : \"OK\"\n}" + recorded_at: Sun, 07 Jul 2024 00:57:46 GMT +- request: + method: get + uri: https://maps.googleapis.com/maps/api/geocode/json?address=1939%20Wilmington%20Dr,%20Fort%20Collins,%20CO,%2080528&key=&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Sun, 07 Jul 2024 00:57:47 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '3016' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=104 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: "{\n \"results\" : \n [\n {\n \"address_components\" + : \n [\n {\n \"long_name\" : \"1939\",\n + \ \"short_name\" : \"1939\",\n \"types\" : \n [\n + \ \"street_number\"\n ]\n },\n {\n + \ \"long_name\" : \"Wilmington Drive\",\n \"short_name\" + : \"Wilmington Dr\",\n \"types\" : \n [\n \"route\"\n + \ ]\n },\n {\n \"long_name\" + : \"Fort Collins\",\n \"short_name\" : \"Fort Collins\",\n \"types\" + : \n [\n \"locality\",\n \"political\"\n + \ ]\n },\n {\n \"long_name\" + : \"Larimer County\",\n \"short_name\" : \"Larimer County\",\n + \ \"types\" : \n [\n \"administrative_area_level_2\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Colorado\",\n \"short_name\" + : \"CO\",\n \"types\" : \n [\n \"administrative_area_level_1\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"United States\",\n \"short_name\" + : \"US\",\n \"types\" : \n [\n \"country\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"80528\",\n \"short_name\" : + \"80528\",\n \"types\" : \n [\n \"postal_code\"\n + \ ]\n },\n {\n \"long_name\" + : \"6104\",\n \"short_name\" : \"6104\",\n \"types\" + : \n [\n \"postal_code_suffix\"\n ]\n + \ }\n ],\n \"formatted_address\" : \"1939 Wilmington + Dr, Fort Collins, CO 80528, USA\",\n \"geometry\" : \n {\n + \ \"bounds\" : \n {\n \"northeast\" : \n + \ {\n \"lat\" : 40.5196871,\n \"lng\" + : -105.0418817\n },\n \"southwest\" : \n {\n + \ \"lat\" : 40.519378,\n \"lng\" : -105.0422273\n + \ }\n },\n \"location\" : \n {\n + \ \"lat\" : 40.5195298,\n \"lng\" : -105.0420545\n + \ },\n \"location_type\" : \"ROOFTOP\",\n \"viewport\" + : \n {\n \"northeast\" : \n {\n \"lat\" + : 40.5208085302915,\n \"lng\" : -105.0407055197085\n },\n + \ \"southwest\" : \n {\n \"lat\" + : 40.5181105697085,\n \"lng\" : -105.0434034802915\n }\n + \ }\n },\n \"place_id\" : \"ChIJvWJU9rBMaYcRtZ5wHymbrUU\",\n + \ \"types\" : \n [\n \"premise\"\n ]\n }\n + \ ],\n \"status\" : \"OK\"\n}" + recorded_at: Sun, 07 Jul 2024 00:57:47 GMT +- request: + method: get + uri: https://maps.googleapis.com/maps/api/geocode/json?address=1107%20S%20Lemay%20Ave,%20Suite%20240,%20Fort%20Collins,%20CO,%2080524&key=&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Sun, 07 Jul 2024 00:57:47 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '2929' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=105 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: "{\n \"results\" : \n [\n {\n \"address_components\" + : \n [\n {\n \"long_name\" : \"240\",\n \"short_name\" + : \"240\",\n \"types\" : \n [\n \"subpremise\"\n + \ ]\n },\n {\n \"long_name\" + : \"1107\",\n \"short_name\" : \"1107\",\n \"types\" + : \n [\n \"street_number\"\n ]\n + \ },\n {\n \"long_name\" : \"South Lemay + Avenue\",\n \"short_name\" : \"S Lemay Ave\",\n \"types\" + : \n [\n \"route\"\n ]\n },\n + \ {\n \"long_name\" : \"University Acres\",\n \"short_name\" + : \"University Acres\",\n \"types\" : \n [\n \"neighborhood\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Fort Collins\",\n \"short_name\" + : \"Fort Collins\",\n \"types\" : \n [\n \"locality\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Larimer County\",\n \"short_name\" + : \"Larimer County\",\n \"types\" : \n [\n \"administrative_area_level_2\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Colorado\",\n \"short_name\" + : \"CO\",\n \"types\" : \n [\n \"administrative_area_level_1\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"United States\",\n \"short_name\" + : \"US\",\n \"types\" : \n [\n \"country\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"80524\",\n \"short_name\" : + \"80524\",\n \"types\" : \n [\n \"postal_code\"\n + \ ]\n }\n ],\n \"formatted_address\" + : \"1107 S Lemay Ave #240, Fort Collins, CO 80524, USA\",\n \"geometry\" + : \n {\n \"location\" : \n {\n \"lat\" + : 40.5720018,\n \"lng\" : -105.0584233\n },\n \"location_type\" + : \"ROOFTOP\",\n \"viewport\" : \n {\n \"northeast\" + : \n {\n \"lat\" : 40.5732694302915,\n \"lng\" + : -105.0570756197085\n },\n \"southwest\" : \n + \ {\n \"lat\" : 40.5705714697085,\n \"lng\" + : -105.0597735802915\n }\n }\n },\n \"place_id\" + : \"ChIJ2QtPVOBKaYcR33T96L5lGSA\",\n \"types\" : \n [\n \"subpremise\"\n + \ ]\n }\n ],\n \"status\" : \"OK\"\n}" + recorded_at: Sun, 07 Jul 2024 00:57:47 GMT +- request: + method: get + uri: https://maps.googleapis.com/maps/api/geocode/json?address=4601%20Corbett%20Dr,%20Fort%20Collins,%20CO,%2080528&key=&language=en&sensor=false + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Sun, 07 Jul 2024 00:57:47 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '3008' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=101 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: "{\n \"results\" : \n [\n {\n \"address_components\" + : \n [\n {\n \"long_name\" : \"4601\",\n + \ \"short_name\" : \"4601\",\n \"types\" : \n [\n + \ \"street_number\"\n ]\n },\n {\n + \ \"long_name\" : \"Corbett Drive\",\n \"short_name\" + : \"Corbett Dr\",\n \"types\" : \n [\n \"route\"\n + \ ]\n },\n {\n \"long_name\" + : \"Fort Collins\",\n \"short_name\" : \"Fort Collins\",\n \"types\" + : \n [\n \"locality\",\n \"political\"\n + \ ]\n },\n {\n \"long_name\" + : \"Larimer County\",\n \"short_name\" : \"Larimer County\",\n + \ \"types\" : \n [\n \"administrative_area_level_2\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"Colorado\",\n \"short_name\" + : \"CO\",\n \"types\" : \n [\n \"administrative_area_level_1\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"United States\",\n \"short_name\" + : \"US\",\n \"types\" : \n [\n \"country\",\n + \ \"political\"\n ]\n },\n {\n + \ \"long_name\" : \"80528\",\n \"short_name\" : + \"80528\",\n \"types\" : \n [\n \"postal_code\"\n + \ ]\n },\n {\n \"long_name\" + : \"9579\",\n \"short_name\" : \"9579\",\n \"types\" + : \n [\n \"postal_code_suffix\"\n ]\n + \ }\n ],\n \"formatted_address\" : \"4601 Corbett + Dr, Fort Collins, CO 80528, USA\",\n \"geometry\" : \n {\n + \ \"bounds\" : \n {\n \"northeast\" : \n + \ {\n \"lat\" : 40.5224051,\n \"lng\" + : -105.0277394\n },\n \"southwest\" : \n {\n + \ \"lat\" : 40.5216682,\n \"lng\" : -105.0288448\n + \ }\n },\n \"location\" : \n {\n + \ \"lat\" : 40.5220128,\n \"lng\" : -105.0284598\n + \ },\n \"location_type\" : \"ROOFTOP\",\n \"viewport\" + : \n {\n \"northeast\" : \n {\n \"lat\" + : 40.5233856302915,\n \"lng\" : -105.0268814697085\n },\n + \ \"southwest\" : \n {\n \"lat\" + : 40.5206876697085,\n \"lng\" : -105.0295794302915\n }\n + \ }\n },\n \"place_id\" : \"ChIJWxLXuU2zbocRgJWHLDl5uNU\",\n + \ \"types\" : \n [\n \"premise\"\n ]\n }\n + \ ],\n \"status\" : \"OK\"\n}" + recorded_at: Sun, 07 Jul 2024 00:57:47 GMT +- request: + method: post + uri: https://routes.googleapis.com/distanceMatrix/v2:computeRouteMatrix + body: + encoding: UTF-8 + string: '{"origins":[{"waypoint":{"placeId":"ChIJK-pLGN5KaYcR3uWeXBYE6ts"}},{"waypoint":{"placeId":"ChIJK-pLGN5KaYcR3uWeXBYE6ts"}},{"waypoint":{"placeId":"ChIJK-pLGN5KaYcR3uWeXBYE6ts"}},{"waypoint":{"placeId":"ChIJK-pLGN5KaYcR3uWeXBYE6ts"}},{"waypoint":{"placeId":"ChIJK-pLGN5KaYcR3uWeXBYE6ts"}},{"waypoint":{"placeId":"ChIJK-pLGN5KaYcR3uWeXBYE6ts"}}],"destinations":[{"waypoint":{"placeId":"ChIJS_rwErJMaYcRp8Q9z60tJjQ"}},{"waypoint":{"placeId":"ChIJRbgIZeBKaYcRRG_TIgGYoIw"}},{"waypoint":{"placeId":"ChIJ4U-oox9LaYcRrCvlPHIAuKI"}},{"waypoint":{"placeId":"ChIJvWJU9rBMaYcRtZ5wHymbrUU"}},{"waypoint":{"placeId":"ChIJ2QtPVOBKaYcR33T96L5lGSA"}},{"waypoint":{"placeId":"ChIJWxLXuU2zbocRgJWHLDl5uNU"}}],"routingPreference":"TRAFFIC_AWARE","travelMode":"DRIVE"}' + headers: + X-Goog-Fieldmask: + - originIndex,destinationIndex,status,condition,distanceMeters,duration + X-Goog-Api-Key: + - "" + Content-Type: + - application/json + User-Agent: + - Faraday v2.9.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Sun, 07 Jul 2024 00:57:47 GMT + Server: + - scaffolding on HTTPServer2 + Cache-Control: + - private + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: "[{\n \"originIndex\": 1,\n \"destinationIndex\": 1,\n \"status\": + {},\n \"distanceMeters\": 205,\n \"duration\": \"55s\",\n \"condition\": + \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 3,\n \"destinationIndex\": + 1,\n \"status\": {},\n \"distanceMeters\": 205,\n \"duration\": \"55s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 5,\n \"destinationIndex\": + 1,\n \"status\": {},\n \"distanceMeters\": 205,\n \"duration\": \"55s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 1,\n \"status\": {},\n \"distanceMeters\": 205,\n \"duration\": \"55s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 1,\n \"status\": {},\n \"distanceMeters\": 205,\n \"duration\": \"55s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 4,\n \"status\": {},\n \"distanceMeters\": 710,\n \"duration\": \"178s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 1,\n \"status\": {},\n \"distanceMeters\": 205,\n \"duration\": \"55s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 3,\n \"destinationIndex\": + 4,\n \"status\": {},\n \"distanceMeters\": 710,\n \"duration\": \"178s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 3,\n \"destinationIndex\": + 2,\n \"status\": {},\n \"distanceMeters\": 1290,\n \"duration\": \"243s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 2,\n \"status\": {},\n \"distanceMeters\": 1290,\n \"duration\": \"243s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 5,\n \"destinationIndex\": + 4,\n \"status\": {},\n \"distanceMeters\": 710,\n \"duration\": \"178s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 5,\n \"destinationIndex\": + 5,\n \"status\": {},\n \"distanceMeters\": 8728,\n \"duration\": \"691s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 2,\n \"status\": {},\n \"distanceMeters\": 1290,\n \"duration\": \"243s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 2,\n \"status\": {},\n \"distanceMeters\": 1290,\n \"duration\": \"243s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 1,\n \"destinationIndex\": + 4,\n \"status\": {},\n \"distanceMeters\": 710,\n \"duration\": \"178s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 5,\n \"destinationIndex\": + 2,\n \"status\": {},\n \"distanceMeters\": 1290,\n \"duration\": \"243s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 5,\n \"status\": {},\n \"distanceMeters\": 8728,\n \"duration\": \"691s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 1,\n \"destinationIndex\": + 2,\n \"status\": {},\n \"distanceMeters\": 1290,\n \"duration\": \"243s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 3,\n \"destinationIndex\": + 5,\n \"status\": {},\n \"distanceMeters\": 8728,\n \"duration\": \"691s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 0,\n \"status\": {},\n \"distanceMeters\": 8071,\n \"duration\": \"680s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 4,\n \"status\": {},\n \"distanceMeters\": 710,\n \"duration\": \"178s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 4,\n \"status\": {},\n \"distanceMeters\": 710,\n \"duration\": \"178s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 5,\n \"status\": {},\n \"distanceMeters\": 8728,\n \"duration\": \"691s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 0,\n \"status\": {},\n \"distanceMeters\": 8071,\n \"duration\": \"680s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 1,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"distanceMeters\": 8122,\n \"duration\": \"701s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 5,\n \"destinationIndex\": + 0,\n \"status\": {},\n \"distanceMeters\": 8071,\n \"duration\": \"680s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"distanceMeters\": 8122,\n \"duration\": \"701s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 3,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"distanceMeters\": 8122,\n \"duration\": \"701s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 5,\n \"status\": {},\n \"distanceMeters\": 8728,\n \"duration\": \"691s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 5,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"distanceMeters\": 8122,\n \"duration\": \"701s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 3,\n \"destinationIndex\": + 0,\n \"status\": {},\n \"distanceMeters\": 8071,\n \"duration\": \"680s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 1,\n \"destinationIndex\": + 0,\n \"status\": {},\n \"distanceMeters\": 8071,\n \"duration\": \"680s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 0,\n \"status\": {},\n \"distanceMeters\": 8071,\n \"duration\": \"680s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"distanceMeters\": 8122,\n \"duration\": \"701s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 1,\n \"destinationIndex\": + 5,\n \"status\": {},\n \"distanceMeters\": 8728,\n \"duration\": \"691s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"distanceMeters\": 8122,\n \"duration\": \"701s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n]" + recorded_at: Sun, 07 Jul 2024 00:57:47 GMT +- request: + method: post + uri: https://routes.googleapis.com/distanceMatrix/v2:computeRouteMatrix + body: + encoding: UTF-8 + string: '{"origins":[{"waypoint":{"placeId":"ChIJS_rwErJMaYcRp8Q9z60tJjQ"}},{"waypoint":{"placeId":"ChIJRbgIZeBKaYcRRG_TIgGYoIw"}},{"waypoint":{"placeId":"ChIJ4U-oox9LaYcRrCvlPHIAuKI"}},{"waypoint":{"placeId":"ChIJvWJU9rBMaYcRtZ5wHymbrUU"}},{"waypoint":{"placeId":"ChIJ2QtPVOBKaYcR33T96L5lGSA"}},{"waypoint":{"placeId":"ChIJWxLXuU2zbocRgJWHLDl5uNU"}}],"destinations":[{"waypoint":{"placeId":"ChIJvWJU9rBMaYcRtZ5wHymbrUU"}},{"waypoint":{"placeId":"ChIJWxLXuU2zbocRgJWHLDl5uNU"}},{"waypoint":{"placeId":"ChIJWxLXuU2zbocRgJWHLDl5uNU"}},{"waypoint":{"placeId":"ChIJ4U-oox9LaYcRrCvlPHIAuKI"}},{"waypoint":{"placeId":"ChIJWxLXuU2zbocRgJWHLDl5uNU"}},{"waypoint":{"placeId":"ChIJRbgIZeBKaYcRRG_TIgGYoIw"}}],"routingPreference":"TRAFFIC_AWARE","travelMode":"DRIVE"}' + headers: + X-Goog-Fieldmask: + - originIndex,destinationIndex,status,condition,distanceMeters,duration + X-Goog-Api-Key: + - "" + Content-Type: + - application/json + User-Agent: + - Faraday v2.9.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Sun, 07 Jul 2024 00:57:48 GMT + Server: + - scaffolding on HTTPServer2 + Cache-Control: + - private + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: "[{\n \"originIndex\": 5,\n \"destinationIndex\": 1,\n \"status\": + {},\n \"duration\": \"0s\",\n \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n + \ \"originIndex\": 5,\n \"destinationIndex\": 4,\n \"status\": {},\n \"duration\": + \"0s\",\n \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": + 3,\n \"destinationIndex\": 0,\n \"status\": {},\n \"duration\": \"0s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"duration\": \"0s\",\n \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n + \ \"originIndex\": 5,\n \"destinationIndex\": 2,\n \"status\": {},\n \"duration\": + \"0s\",\n \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": + 1,\n \"destinationIndex\": 5,\n \"status\": {},\n \"duration\": \"0s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 5,\n \"status\": {},\n \"distanceMeters\": 427,\n \"duration\": \"117s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 3,\n \"destinationIndex\": + 4,\n \"status\": {},\n \"distanceMeters\": 1881,\n \"duration\": \"253s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 3,\n \"destinationIndex\": + 1,\n \"status\": {},\n \"distanceMeters\": 1881,\n \"duration\": \"253s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 5,\n \"destinationIndex\": + 0,\n \"status\": {},\n \"distanceMeters\": 1969,\n \"duration\": \"294s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 0,\n \"status\": {},\n \"distanceMeters\": 689,\n \"duration\": \"202s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"distanceMeters\": 1067,\n \"duration\": \"230s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 3,\n \"destinationIndex\": + 2,\n \"status\": {},\n \"distanceMeters\": 1881,\n \"duration\": \"253s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 2,\n \"status\": {},\n \"distanceMeters\": 1301,\n \"duration\": \"184s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 1,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"distanceMeters\": 1007,\n \"duration\": \"153s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 5,\n \"destinationIndex\": + 5,\n \"status\": {},\n \"distanceMeters\": 8503,\n \"duration\": \"674s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 5,\n \"status\": {},\n \"distanceMeters\": 1007,\n \"duration\": \"143s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 1,\n \"status\": {},\n \"distanceMeters\": 1301,\n \"duration\": \"184s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 1,\n \"destinationIndex\": + 4,\n \"status\": {},\n \"distanceMeters\": 8445,\n \"duration\": \"651s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 2,\n \"status\": {},\n \"distanceMeters\": 7923,\n \"duration\": \"635s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 4,\n \"status\": {},\n \"distanceMeters\": 1301,\n \"duration\": \"184s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 2,\n \"status\": {},\n \"distanceMeters\": 8505,\n \"duration\": \"686s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 1,\n \"status\": {},\n \"distanceMeters\": 8505,\n \"duration\": \"686s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 1,\n \"destinationIndex\": + 1,\n \"status\": {},\n \"distanceMeters\": 8445,\n \"duration\": \"651s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 1,\n \"status\": {},\n \"distanceMeters\": 7923,\n \"duration\": \"635s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 3,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"distanceMeters\": 7124,\n \"duration\": \"621s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 4,\n \"status\": {},\n \"distanceMeters\": 7923,\n \"duration\": \"635s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 1,\n \"destinationIndex\": + 2,\n \"status\": {},\n \"distanceMeters\": 8445,\n \"duration\": \"651s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 3,\n \"destinationIndex\": + 5,\n \"status\": {},\n \"distanceMeters\": 7888,\n \"duration\": \"674s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 5,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"distanceMeters\": 7738,\n \"duration\": \"624s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 1,\n \"destinationIndex\": + 0,\n \"status\": {},\n \"distanceMeters\": 7839,\n \"duration\": \"662s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 5,\n \"status\": {},\n \"distanceMeters\": 7781,\n \"duration\": \"644s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 2,\n \"destinationIndex\": + 0,\n \"status\": {},\n \"distanceMeters\": 7317,\n \"duration\": \"640s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 0,\n \"status\": {},\n \"distanceMeters\": 7899,\n \"duration\": \"698s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 0,\n \"destinationIndex\": + 3,\n \"status\": {},\n \"distanceMeters\": 7017,\n \"duration\": \"595s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n,\r\n{\n \"originIndex\": 4,\n \"destinationIndex\": + 4,\n \"status\": {},\n \"distanceMeters\": 8505,\n \"duration\": \"686s\",\n + \ \"condition\": \"ROUTE_EXISTS\"\n}\n]" + recorded_at: Sun, 07 Jul 2024 00:57:48 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/factories/addresses.rb b/spec/factories/addresses.rb index b3a179d..acbed8f 100644 --- a/spec/factories/addresses.rb +++ b/spec/factories/addresses.rb @@ -11,8 +11,10 @@ latitude { Faker::Address.latitude } longitude { Faker::Address.longitude } - trait :with_out_place_id do + trait :with_out_location_data do place_id { nil } + latitude { nil } + longitude { nil } end end end diff --git a/spec/lib/rides/commands/get_commute_duration_spec.rb b/spec/lib/rides/commands/get_commute_duration_spec.rb index 5e26dfb..ffe29f2 100644 --- a/spec/lib/rides/commands/get_commute_duration_spec.rb +++ b/spec/lib/rides/commands/get_commute_duration_spec.rb @@ -4,11 +4,11 @@ it "gets data for an address" do VCR.use_cassette("first_commute") do from_address = create( - :address, :with_out_place_id, line_1: "4705 Weitzel Street", city: "Timnath", state: "CO", + :address, :with_out_location_data, line_1: "4705 Weitzel Street", city: "Timnath", state: "CO", zip_code: "80547" ) to_address = create( - :address, :with_out_place_id, line_1: "151 N College Ave", city: "Fort Collins", state: "CO", + :address, :with_out_location_data, line_1: "151 N College Ave", city: "Fort Collins", state: "CO", zip_code: "80524" ) create_list(:ride, 2, from_address:, to_address:) diff --git a/spec/lib/rides/commands/get_routes_data_spec.rb b/spec/lib/rides/commands/get_routes_data_spec.rb index 4982dc7..d3bf600 100644 --- a/spec/lib/rides/commands/get_routes_data_spec.rb +++ b/spec/lib/rides/commands/get_routes_data_spec.rb @@ -3,8 +3,12 @@ RSpec.describe Rides::Commands::GetRoutesData do let(:rides) do [ - double(:ride, origin_place_id: "origin1", destination_place_id: "dest1"), - double(:ride, origin_place_id: "origin2", destination_place_id: "dest2") + double(:ride, id: 1, origin_place_id: "origin1", destination_place_id: "dest1", from_address_id: 1, + from_address: double(:address, id: 1), to_address_id: 1, + to_address: double(:address, id: 1)), + double(:ride, id: 2, origin_place_id: "origin2", destination_place_id: "dest2", from_address_id: 2, + from_address: double(:address, id: 2), to_address_id: 1, + to_address: double(:address, id: 1)) ] end diff --git a/spec/models/address_spec.rb b/spec/models/address_spec.rb index b07a2e0..e794f6b 100644 --- a/spec/models/address_spec.rb +++ b/spec/models/address_spec.rb @@ -28,7 +28,7 @@ it "#full_address" do VCR.use_cassette("initial_geocode") do - address = create(:address, :with_out_place_id, line_1: "711 Oval Drive", city: "Fort Collins", state: "CO", + address = create(:address, :with_out_location_data, line_1: "711 Oval Drive", city: "Fort Collins", state: "CO", zip_code: "80521") expect(address.full_address).to eq("711 Oval Drive, Fort Collins, CO, 80521") expect(address.latitude).to eq(40.577655) diff --git a/spec/models/ride_spec.rb b/spec/models/ride_spec.rb index 7d42a82..6343e43 100644 --- a/spec/models/ride_spec.rb +++ b/spec/models/ride_spec.rb @@ -2,16 +2,76 @@ require "rails_helper" -RSpec.describe Ride, :skip_geocode, type: :model do +RSpec.describe Ride, type: :model do subject { create(:ride) } - describe "associations" do + describe "associations", :skip_geocode do it { is_expected.to belong_to(:driver).optional(true) } it { is_expected.to belong_to(:from_address).class_name("Address") } it { is_expected.to belong_to(:to_address).class_name("Address") } end - describe "attributes" do + describe "attributes", :skip_geocode do it { is_expected.to monetize(:amount_cents).as(:amount) } it { is_expected.to validate_numericality_of(:amount_cents) } end + + describe "scopes" do + describe ".nearby_drivers" do + it "gets rides within the driver's default max_radius (defaults to 10mi.)" do + VCR.use_cassette("nearby_drivers") do + from_address_1 = create( + :address, :with_out_location_data, line_1: "4705 Weitzel Street", city: "Timnath", state: "CO", + zip_code: "80547" + ) + to_address_1 = create( + :address, :with_out_location_data, line_1: "151 N College Ave", city: "Fort Collins", state: "CO", + zip_code: "80524" + ) + create(:ride, from_address: from_address_1, to_address: to_address_1) + from_address_2 = create( + :address, :with_out_location_data, line_1: " 216 N College Ave #110", city: "Fort Collins", state: "CO", + zip_code: "80524" + ) + to_address_2 = create( + :address, :with_out_location_data, line_1: "501 W 20th St", city: "Greeley", state: "CO", + zip_code: "80639" + ) + create(:ride, from_address: from_address_2, to_address: to_address_2) + driver = create(:driver, current_address: to_address_1) + rides = Ride.selectable.nearby_driver(driver) + + expect(rides.length).to eq(2) + end + end + + it "gets rides near drivers with different max_radius" do + VCR.use_cassette("nearby_drivers") do + from_address_1 = create( + :address, :with_out_location_data, line_1: "4705 Weitzel Street", city: "Timnath", state: "CO", + zip_code: "80547" + ) + to_address_1 = create( + :address, :with_out_location_data, line_1: "151 N College Ave", city: "Fort Collins", state: "CO", + zip_code: "80524" + ) + create(:ride, from_address: from_address_1, to_address: to_address_1) + from_address_2 = create( + :address, :with_out_location_data, line_1: " 216 N College Ave #110", city: "Fort Collins", state: "CO", + zip_code: "80524" + ) + to_address_2 = create( + :address, :with_out_location_data, line_1: "501 W 20th St", city: "Greeley", state: "CO", + zip_code: "80639" + ) + create(:ride, from_address: from_address_2, to_address: to_address_2) + driver = create(:driver, current_address: from_address_2, max_radius: 5) + rides = Ride.selectable.nearby_driver(driver) + + # Since the driver's current home address is in Greeley, + # The Timnath ride(from_address) is too far away as well as the Fort Collins ride (from_address_2) + expect(rides.length).to eq(0) + end + end + end + end end diff --git a/spec/requests/drivers/drivers_spec.rb b/spec/requests/drivers/drivers_spec.rb new file mode 100644 index 0000000..90a8e06 --- /dev/null +++ b/spec/requests/drivers/drivers_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "Drivers::Drivers", :skip_geocode, type: :request do + let!(:drivers) { create_list(:driver, 10) } + describe "GET /index" do + it "returns list of drivers" do + get "/drivers?limit=10" + expect(response.status).to eq(200) + result = JSON.parse(response.body, symbolize_names: true) + + expect(result[:data].count).to eq(10) + obj = result.dig(:data, 0) + expect(obj[:type]).to eq("driver") + expect(obj.dig(:relationships, :current_address, :data, :type)).to eq("address") + end + + it "can paginate list of drivers" do + get "/drivers?limit=2&offset=0" + expect(response.status).to eq(200) + result = JSON.parse(response.body, symbolize_names: true) + first_result = result[:data] + + expect(first_result.count).to eq(2) + + get "/drivers?limit=2&offset=2" + expect(response.status).to eq(200) + result = JSON.parse(response.body, symbolize_names: true) + second_result = result[:data] + + expect(first_result.map { _1[:id] }).to_not eq(second_result.map { _1[:id] }) + end + end +end diff --git a/spec/requests/drivers/selectable_rides_spec.rb b/spec/requests/drivers/selectable_rides_spec.rb new file mode 100644 index 0000000..260d22d --- /dev/null +++ b/spec/requests/drivers/selectable_rides_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "Drivers::Rides", type: :request do + describe "GET /drivers/:driver_id/rides" do + it "returns ranked rides" do + VCR.use_cassette("ranked_rides") do + attrs = [ + { line_1: "1221 E Elizabeth St", line_2: nil, city: "Fort Collins", state: "CO", zip_code: "80524", + latitude: 40.5740149, longitude: -105.0551343, place_id: "ChIJK-pLGN5KaYcR3uWeXBYE6ts", id: nil }, + { line_1: "2121 E Harmony Rd # 180", line_2: nil, city: "Fort Collins", state: "CO", zip_code: "80528", + latitude: 40.5221078, longitude: -105.0368968, place_id: "ChIJS_rwErJMaYcRp8Q9z60tJjQ", id: nil }, + { line_1: "1024 S Lemay Ave", line_2: nil, city: "Fort Collins", state: "CO", zip_code: "80524", + latitude: 40.5716484, longitude: -105.0566547, place_id: "ChIJRbgIZeBKaYcRRG_TIgGYoIw", id: nil }, + { line_1: "1106 E Prospect Rd", line_2: nil, city: "Fort Collins", state: "CO", zip_code: "80525", + latitude: 40.5677337, longitude: -105.0574767, place_id: "ChIJ4U-oox9LaYcRrCvlPHIAuKI", id: nil }, + { line_1: "1939 Wilmington Dr", line_2: nil, city: "Fort Collins", state: "CO", zip_code: "80528", + latitude: 40.5195298, longitude: -105.0420545, place_id: "ChIJvWJU9rBMaYcRtZ5wHymbrUU", id: nil }, + { line_1: "1107 S Lemay Ave", line_2: "Suite 240", city: "Fort Collins", state: "CO", zip_code: "80524", + latitude: 40.5720018, longitude: -105.0584233, place_id: "ChIJ2QtPVOBKaYcR33T96L5lGSA", id: nil }, + { line_1: "4601 Corbett Dr", line_2: nil, city: "Fort Collins", state: "CO", zip_code: "80528", + latitude: 40.5220128, longitude: -105.0284598, place_id: "ChIJWxLXuU2zbocRgJWHLDl5uNU", id: nil } + ] + addresses = attrs.map { create(:address, **_1) } + driver = create(:driver, current_address: addresses[0]) + addresses.each do |from_address| + to_address = Address.where.not(id: [from_address.id, + driver.current_address_id]).order("RANDOM()").limit(1).first + create(:ride, from_address:, to_address:) + end + + get "/drivers/#{driver.id}/selectable_rides" + expect(response.status).to eq(200) + + result = JSON.parse(response.body, symbolize_names: true) + data = result.dig(:data, 0) + + expected_keys = %i[distance duration commute_duration ride_earnings] + attributes = data[:attributes] + expect(attributes[:ride_earnings]).to eq("$12.00") + expect(attributes[:duration].end_with?("minutes")).to be_truthy + actual_keys = attributes.keys + expect(expected_keys).to eq(actual_keys) + + expected_relationships = %i[from_address to_address] + actual_relationships = data[:relationships].keys + expect(expected_relationships).to eq(actual_relationships) + + included = result[:included] + expect(included.all? { _1[:type] == "address" }).to be_truthy + end + end + end +end diff --git a/spec/requests/drivers_spec.rb b/spec/requests/drivers_spec.rb new file mode 100644 index 0000000..852ec0f --- /dev/null +++ b/spec/requests/drivers_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "Drivers", :skip_geocode, type: :request do + let!(:drivers) { create_list(:driver, 10) } + describe "GET /index" do + it "returns list of drivers" do + get "/drivers?limit=10" + expect(response.status).to eq(200) + result = JSON.parse(response.body, symbolize_names: true) + + expect(result[:data].count).to eq(10) + obj = result.dig(:data, 0) + expect(obj[:type]).to eq("driver") + expect(obj.dig(:relationships, :current_address, :data, :type)).to eq("address") + end + + it "can paginate list of drivers" do + get "/drivers?limit=2&offset=0" + expect(response.status).to eq(200) + result = JSON.parse(response.body, symbolize_names: true) + first_result = result[:data] + + expect(first_result.count).to eq(2) + + get "/drivers?limit=2&offset=2" + expect(response.status).to eq(200) + result = JSON.parse(response.body, symbolize_names: true) + second_result = result[:data] + + expect(first_result.map { _1[:id] }).to_not eq(second_result.map { _1[:id] }) + end + end +end