Location, Location, Location

Jerry Brown
5 min readDec 14, 2020

Drive My Car is a Ruby-based application built with Sinatra and ActiveRecord to connect users who have cars that need to be driven long distances with users who are willing to complete that trip. For a wide variety of reasons, drivers are sometimes separated from their vehicles and are in need of someone to deliver their vehicle to them. Conversely, others might be planning a long-distance trip but lack a mode of transportation. Drive My Car aims to connect these users through localized posts.

Connections via Location

Location is very often used as a means of curating information for users online. The ability to display information to a user that is ‘nearby’ offers a clear link between an otherwise unlinked connection.

Drive My Car relies on location data to match users to posts with cars that are currently near them. The goal is to have a user provide a location linked to their account, and then display to that user all previously-uploaded posts that are relevant to them in terms of location. While doing something like connecting to a location-centered API is the obvious solve, it was outside the scope of this project and a different solution was required.

Setting up the relationships

These ActiveRecord relationships between Users, Posts, and Locations took the following shape within my project:

class User < ActiveRecord::Base
has_secure_password
has_many :posts
belongs_to :location

The User class has straightforward relationships with both Posts and Locations. Throughout the application, a user can have many posts and belong to only one location at a time.

class Post < ActiveRecord::Base
belongs_to :user
belongs_to :origin, class_name: "Location"
belongs_to :destination, class_name: "Location"

Things get a bit more interesting when defining the Post class. Of course, each post will only ever belong to one user. However, the post also needs to have two different types of locations: a starting point for the car (origin) and an ending point (destination). To get there, we need to tell ActiveRecord that a post will have both an origin and a destination, but that it should pull the foreign key from the Location class. In order words, when ActiveRecord is looking to assign an origin_id or destination_id to a Post, it should use the primary key ID in the Location table. This leads us to a Location class that looks like this:

class Location < ActiveRecord::Base
has_many :users
has_many :origins, foreign_key: "origin_id", class_name: "Post"
has_many :destinations, foreign_key: "destination_id", class_name: "Post"

To wrap up the relationship, we’ll need to run a DB Migration to let our database know that we want to start storing an origin_id and destination_id inside our posts table.

def change
add_column :posts, :origin_id, :integer
add_column :posts, :destination_id, :integer
end

Major Cities Array

Now that we’re all set up with our Location relationships, we want to be sure that we can determine if a user’s location is the same location as the origin or destination of a post. The main concern at this point was ensuring that the program knew inputs like “LA” and “Los Angeles” were actually the same location. Rather than forcing a user to pick from a drop-down, I thought a better UX would be to keep the input type as a text field but offer suggestions to pick from as the user typed.

It turns out that this type of autocomplete magic usually done with Javascript, which my program wasn’t using. To make it work with Ruby, my implementation uses “seed” cities — a list of around 1,000 of the most densely populated cities in the country. After a quick google search, I created an array of those city/state names stored locally inside the Location class:

class Location < ActiveRecord::BaseMAJOR_LOCATIONS = [
‘New York City, New York’,
‘Los Angeles, California’,
‘Chicago, Illinois’,
‘Houston, Texas’,
...
'Charleston, West Virginia'
]

Enter <datalist>’s

At this point, I needed to make sure that the options from the “Major Locations” array was able to appear on the forms for signing up and creating a new post. HTML has a <datalist> tag that can be used, in conjunction with a text input, to display suggestions as the user types. Here’s the setup for my registration form:

<label for="location" class="form-label">Location</label>
<input list="locations" type="text" name="location" class="form-control" id="location">
<datalist id="locations">
<% location_options.each do |l| %>
<option value='<%= l %>'>
<% end %>
</datalist>

In this User/New view we’re taking all of the Major Location names, turning them into datalist <option>’s, and making them available once the user begins typing into the field. This is perfect for ensuring that a user who types in “LA” will see “Los Angeles, California” appear on the list and select that location. The same functionality is used on the Post New/Edit forms for users when they set the car’s origin and destination.

Once we capture that location, the application splits the string at the “, “ to determine the difference between the City portion of the string, and the State portion. If this specific location isn’t already saved to the Database, the program creates a new location, saves it, and assigns it to the respective User or Post accordingly.

Localized Homepage

At this point, we’ve captured the user’s location, the car’s starting point for any given post, and its end point. In order to display nearby posts, all we need to do is define what is considered ‘nearby’. For the purposes of this project, ‘nearby’ was defined as posts with car origins that are within the same state, with priority given to cars that are within the same exact city as the user.

Where to go from here

There are a few obvious expansions for this location functionality. The first would be to allow users to filter/search for posts based on location, which didn’t make the cut for “MVP” features of this project.

Another expansion is to more accurately define what ‘nearby’ means to a user. Of course, just because a car is within the same state as someone doesn’t necessarily mean that it’s close. With this current implementation, a car in San Francisco would be considered “closer” to a user in LA than car in Vegas. Calculating distances would require a zip/postal code data point tied to each location instance, as well as a function to determine the distance between two zip codes.

Feel free to check out Drive My Car on Github and let me know what you think!

--

--