Add Geolocation to Search with Rails and Stimulus.js
Tags: ruby, rails
Published: 2024-03-05 19:38:00

---
title: Add Geolocation to Search with Rails and Stimulus.js
published: true
date: 2024-02-19 18:33:52 UTC
tags: ruby, rails
canonical_url: https://webcrunch.com/posts/geolocation-rails-stimulus
---

{% youtube GkqZoIK1HhY %}

In this tutorial, I'll show you how to add Geolocation support to a search form using Ruby on Rails and Stimulus.js

By integrating geolocation capabilities, users can search for locations based on their current position, which saves time and effort.

## New up a Rails application

To kick things off, let’s create a new app I'll call `geodemo`. I prefer Tailwind for my CSS and esbuild for JavaScript. We'll leverage Stimulus.js for this tutorial so you can choose whatever JavaScript engine you like as it adapts to each gracefully.

```bash
rails new geodemo -c tailwind -j esbuild
```

### Add Dependencies

Let’s install some dependencies. A lovely gem called [Geocoder](https://github.com/alexreisner/geocoder) will help us find locations based on an address or set of latitude and longitude lookups. It’s super powerful!

```bash
bundle add geocoder
```

### Add a Search Controller

For this tutorial, we’ll make a global search rails controller. I won’t get into the weeds of _what_ we would search but you could use something like [pg\_search](https://github.com/Casecommons/pg_search) to build a query from a model you need to return results of.

```bash
rails g controller search index
```

I’ll put a basic form in `app/views/search/index.html.erb` coming up.

### Add a SearchLocationController

We need a view and request cycle to make this all work so let’s generate a `SearchLocation` controller with Rails to handle the `POST` request. For the sake of the guide, I'll add a route for the index action to display the form we'll leverage coming up.

```bash
rails g controller search_location create
```

This should generate the controller in `app/controllers` a couple of views and some default routes. Let's amend the routes.

```ruby
Rails.application.routes.draw do
  get 'search/index'
  post 'search_location', to: "search_location#create"

  get "up" => "rails/health#show", as: :rails_health_check

  root "search#index"
end
```

Now you can boot up your server and hopefully see the new root path active at `localhost:3000`

### Add the search form

Inside `app/views/search/index.html.erb` I've added this erb code:

```erb
<div class="max-w-xl mx-auto my-16">
  <h1 class="font-bold text-3xl mb-4">Search by location</h1>
  <div data-controller="geolocation">
    <%= form_tag url: search_index_url, method: :get do %>
      <div class="relative">
        <%= text_field_tag :query, nil, class: "px-6 py-3 rounded border border-gray-300/80 focus:ring-4 focus:ring-gray-200/60 focus:outline-none w-full rounded-full text-lg", placeholder: "Search state or city", data: { geolocation_target: 'input' } %>

        <button type="submit" class="absolute top-1.5 right-2 bg-white rounded-full px-5 py-2 border border-gray-300/80 shadow-sm bg-gray-50/50 hover:bg-gray-50">Search</button>
      </div>
    <% end %>

    <button type="button" data-action="click->geolocation#handleClick" class="underline font-medium mt-2">
      <span>Find my location</span>
    </button>
  </div>
</div>
```

This code scaffolds a basic search form that would perform a GET request to our search controller index action.

If we had real search records in our app, we could return real data in some fashion after submission.

I’ll leave that for you to sort out since this tutorial is more about geolocation.

If you look closely, you’ll see some data attributes on our HTML code.

These will be how Stimulus.js knows about our form fields and actions.

The button “Find my location” has an action that our Stimulus.js controller I’ll call `geolocation_controller.js` will work with. Let’s make that next.

### Set up the Geolocation Stimulus.js Controller

Let's create a Stimulus controller to handle geolocation functionality.

Create a new file named `geolocation_controller.js` in the `app/javascript/controllers` directory.

You can also run this handy rails command to generate one fast.

```bash
rails g stimulus geolocation
```

Inside the new file, I’ve added the following code:

```javascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["input"]

  handleClick() {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        this.handleSuccess.bind(this),
        this.handleError.bind(this)
      )
    } else {
      console.error("Geolocation is not supported by this browser.")
    }
  }

  handleSuccess(position) {
    const { latitude, longitude } = position.coords
    this.sendLocationToRails(latitude, longitude)
  }

  handleError(error) {
    console.error(`Geolocation error: ${error.message}`)
  }

  sendLocationToRails(latitude, longitude) {
    const csrfToken = document.querySelector('meta[name="csrf-token"]').content

    fetch("/search_location", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-CSRF-Token": csrfToken,
      },
      body: JSON.stringify({ latitude, longitude }),
    })
      .then((response) => {
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`)
        }
        return response.json()
      })
      .then((data) => {
        if (data.address.state) {
          this.inputTarget.value = data.address.state
        } else if (data.address.city) {
          this.inputTarget.value = data.address.city
        }
      })
      .catch((error) => {
        console.error("Error communicating with Rails:", error)
      })
  }
}
```

Here’s a quick outline of what this code is responsible for:

1. **Targets** : It declares a static property `targets`, specifying the targets that the controller will interact with. In this case, it specifies an input target.
2. **handleClick Method** : This method is triggered when a user clicks on an element associated with this controller. It checks if the browser supports geolocation. If it does, it calls `getCurrentPosition` method of the `navigator.geolocation` object, passing two callback functions for success and error handling.
3. **handleSuccess Method** : This method is invoked upon successfully retrieving the user's geolocation. It extracts the latitude and longitude from the position object and calls `sendLocationToRails` method with these coordinates.
4. **handleError Method** : This method handles errors encountered during geolocation retrieval by logging an error message to the console.
5. **sendLocationToRails Method** : This method sends the user's location (latitude and longitude) to a Rails backend endpoint using a fetch request. It retrieves the CSRF token from the meta tags, includes it in the request headers, and sends the location data as JSON in the request body. It then processes the response, updating the input field value based on the data received (either a zip code or a city name). Any errors during this process are caught and logged to the console.

### Handle Geolocation Response in Rails Controller

In your Rails controller (`search_controller.rb`), implement the logic to handle the geolocation data received from the frontend:

```ruby
class SearchLocationController < ApplicationController
  def create
    # Access latitude and longitude parameters sent from frontend
    latitude = params[:latitude]
    longitude = params[:longitude]

    # Implement your logic to determine a location based on coordinates
    location_data = Geocoder.search([latitude, longitude])

    render json: location_data.first.data
  end
end
```

This code leverages the Geocoder gem we added before and returns JSON based on the values passed from the front end.

### Testing it out

If all goes well, we should be able to click the “Find my location” link and propagate the input field with either a postal code or city, depending on what returns. This is a _one-click_ option to make it easier on your users who would instead give their location instead of typing one in the form manually.

We extract the latitude and longitude coordinates from the `navigator.geolocation` browser API. Those coordinates get submit via JavaScript as a POST request to our `search_location#create` method in the `SearchLocationController` , which then performs the query to decode into a US state. Finally, that response returns to the front end and dynamically sets the input's value.

### Happy dance, it works!

In this tutorial, we've successfully integrated geolocation support into a search form in a Rails application using Stimulus.js.

The lazy users (we all know we are one) can now conveniently search for locations based on their current position in one click.

Now there is plenty of room for improvement here, but I hope I’ve guided you in a good direction to build something useful.

Experiment with additional features, like error handling and showing user feedback such as loading states and more.

Happy coding!