Building a Rails app backbone

Table of Contents

1. Introduction

Maps are a useful weapon for any developer to have in their arsenal and, with the combined dev-friendly approach to Rails and massive community built around React, now is the perfect time to use these two technologies together to add another layer to your project.

2. Background

This tutorial was largely inspired by my time at Le Wagon in Tokyo. Over the course of 9 weeks we dived into a lot of core concepts of full-stack web development using Ruby and the Rails framework. I'd fully recommend it if you're interested in fast-tracking your development learning progress or looking to get into the world of Ruby development.

We touched on JavaScript during the course and used it pretty extensively in our final project, Safe Spaces, but with the main focus being on Rails we didn't have any time to explore some of the popular JS frameworks.

After the end of Le Wagon I wanted to get some hands-on experience with React and ended up working on a small side project that involved integrating Google Maps into a Rails project with the @react-google-maps/api package. I decided to create this tutorial to share my experience in building a simple starter using these tools.

3. Prerequisites

To follow along with this article you'll need at least a basic understanding of Ruby on Rails, MVC (Model View Controller), JavaScript, and React. You'll also need a Google account in order to create a Google Maps API key and a code editor of your choice (e.g. Visual Studio Code).

This tutorial also requires you to have the following installed:

This tutorial was tested on a fresh Ubuntu 20.04 install following the installation instructions provided by the docs in each prerequisite. If you have issues at any point feel free to drop a comment or reach out via DM.

If you get stuck at any point, feel free to reference the source code or clone the project and follow along.

4. Setting up your app

We're going to be building our project using a Rails backbone connected to a PostgreSQL database. If you have all the prerequisites installed, generate a new app with the following command:

rails new rails-react-google-maps --database=postgresql

This will create a new folder in your current directory with the same name as your app. Rails uses SQLite3 as its default database so by passing the --database=postgresql option in the setup we tell the app to use PostgreSQL instead to save some time.

PostgreSQL database management in Rails

Now that we have our basic structure set up we can get started with creating the database we'll use to hold all of our Place data. Rails makes database management easy with a few simple commands. The ones we'll be using in this tutorial are:

rails db:create
rails db:migrate
rails db:seed

rails db:create does what it says on the box - creates our database. This is an important step as without it we'll get an error when we try to run our server.

rails db:migrate is something we'll touch on soon but it's a part of that "rails magic" that makes database management simple. I'd recommend reading the Rails Guide on Active Record Migrations to learn more about how they work.

In simple terms, migrations allow us to manipulate the tables in our database - whether they're adding or removing columns, changing data types, or renaming tables themselves - which can all be done from the command line.

rails db:seed will mainly be used in the development phase of our project to create starter data we'll use to populate our app. We'll explore this more when we start creating instances of our Place model.

To learn more about what other commands you have at your disposal, check out the Running Migrations section in the previously linked Rails Guide.

5. Building our database

To get started, run the following command in your terminal:

rails db:create

You should see this output:

Created database 'rails_react_google_maps_development'
Created database 'rails_react_google_maps_test'

We now have our database set up and ready to store our data. To make sure everything is working, type rails server in your terminal to start up your app. You should see the basic Rails welcome page.

6. Creating our Place model

Now that our app and database are running we can start creating our Place model that will give us the data we need to display on Google Maps. Let's generate the model in the terminal:

rails generate model Place name:string address:string latitude:float longitude:float

You should have something in your terminal that looks like this:

Running via Spring preloader in process 387002
      invoke  active_record
      create    db/migrate/20211104052039_create_places.rb
      create    app/models/place.rb
      invoke    test_unit
      create      test/models/place_test.rb
      create      test/fixtures/places.yml

This means we've got our Place model up and running! But we're not quite done yet because it's not technically connected to our database. To do this we'll be using the file generated by this line:

create    db/migrate/20211104052039_create_places.rb

Remember the migrations we spoke about earlier? Well this is where they come into play. To connect our new model to our database all you have to do is run a migration in your terminal and you should get some feedback:

rails db:migrate
== 20211104052039 CreatePlaces: migrating =====================================
-- create_table(:places)
   -> 0.0106s
== 20211104052039 CreatePlaces: migrated (0.0107s) ============================

There you have it, we've now created our database, generated a model to create new data, and connected that model to the database.

Sidenote about Models and MVC

Rails is built around an MVC (Model View Control) pattern. It's a deeper topic than this tutorial but it's worth looking into if you're interested in exploring what Rails (or any other MVC-based framework) is capable of. This Medium article does a good job of explaining how it relates to Rails directly, while the Mozilla docs on the topic give a more holistic overview.

7. Geocoder gem

Before we start making new places we need to install geocoder, the gem that will help us get the coordinates of a place from the address we give it. If you're not sure what a gem is, this Medium article gives a pretty good explanation.

The geocoder gem takes an address and converts it into latitude and longitude - important data that we'll need for Google Maps. We could technically hard code the coordinates when we create a Place but if you're working with more than a handful of places this can get pretty tedious.

To install geocoder, navigate to your project's root directory (/rails-react-google-maps) and open the Gemfile. Adding a gem to this file will make it available to anyone who clones your app with one easy command. To find out more about how the Gemfile works, check out the documentation.

Add gem 'geocoder' to your Gemfile anywhere above the development and test groups and save the file. Next, in your terminal run:

bundle install

You should get a long list of gems with a confirmation message at the bottom. If you scroll through the list you should be able to find Using geocoder 1.6.7. To create some necessary files for setup, execute the following command:

rails generate geocoder:config

You should see the following:

Running via Spring preloader in process 388633
      create  config/initializers/geocoder.rb

If we navigate to that geocoder.rb file we can make any changes to the configuration that we want. For example, I use the metric system so instead of the default miles, I can change units to km by uncommenting the units option and changing it:

Geocoder.configure(
  ...
  # Calculation options
  units: :km,
  ...
)

The final step for setting up our geocoder gem is to point it to the address we want to extract coordinates from. In our project directory, go to the /rails-react-google-maps/app/models folder and open place.rb and add the following lines inside the class:

geocoded_by :address
  after_validation :geocode

The first line tells geocoder which part of the model it needs to listen to, in this case the address data we declared when generating the model. This means that whenever we create a place, geocoder will take the address and automatically assign the coordinates to the latitude and longitude without us having to hard code it ourselves.

To test this out let's open a rails console and create an example:

rails console

In the console:

Place.create(address: '1600 Pennsylvania Avenue NW, Washington, DC 20500, United States', name: 'The White House')

If it's successful, you should see a lot of information that looks something like this:

TRANSACTION (0.3ms)  BEGIN
  Place Create (0.8ms)  INSERT INTO "places" ("name", "address", "latitude", "longitude", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id"  [["name", "The White House"], ["address", "1600 Pennsylvania Avenue NW, Washington, DC 20500, United States"], ["latitude", 38.897699700000004], ["longitude", -77.03655315], ["created_at", "2021-11-04 08:22:20.077379"], ["updated_at", "2021-11-04 08:22:20.077379"]]
  TRANSACTION (1.0ms)  COMMIT
 => #<Place id: 3, name: "The White House", address: "1600 Pennsylvania Avenue NW, Washington, DC 20500,...", latitude: 38.897699700000004, longitude: -77.03655315, created_at: "2021-11-04 08:22:20.077379000 +0000", updated_at: "2021-11-04 08:22:20.077379000 +0000">

If you look carefully you'll see that we've got ["latitude", 38.897699700000004] and ["longitude", -77.03655315] which means everything is working!

8. Seeding our database

We briefly touched on seeding when we discussed database management in Rails. We've already populated our database with one place (The White House) so now it's time to create a few more places to give us some content to work with.

To make life easier I've provided a template for our seeds file. Feel free to use these or substitute the addresses for your own. I grabbed these places and addresses off Google Maps so I recommend you do the same and test them in the console if you're unsure.

Navigate to rails-react-google-maps/db/ and paste the following into your seeds.rb file:

places = [
  {
    name: 'The White House',
    address: '1600 Pennsylvania Avenue NW, Washington, DC 20500, United States'
  },
  {
    name: 'Washington Monument',
    address: '2 15th St NW, Washington, DC 20024, United States'
  },
  {
    name: 'Lincoln Memorial',
    address: '2 Lincoln Memorial Cir NW, Washington, DC 20002, United States'
  },
  {
    name: 'Washington National Cathedral',
    address: '3101 Wisconsin Ave NW, Washington, DC 20016, United States'
  },
  {
    name: 'Ronald Reagan Washington National Airport',
    address: '2401 Smith Blvd, Arlington, VA 22202, United States'
  }
]

puts 'Clearing seeds...'

Place.destroy_all

puts 'Seeds cleared.'

puts 'Seeding the database'

places.each do |place|
  Place.create!(
    name: place[:name],
    address: place[:address]
  )
end

puts "Created #{Place.all.count} places."

If you're not too sure what's happening here, don't worry. All you need to do from here is go back to your terminal and run:

rails db:seed

This will create an instance for each place, with some feedback in your terminal that should look like this:

Clearing seeds...
Seeds cleared.
Seeding the database
Created 5 places.

Just to double check, let's revisit the rails console to make sure our places are all set up:

rails c

Then let's check that all 5 were generated:

Running via Spring preloader in process 415433
Loading development environment (Rails 6.1.4.1)
2.6.6 :001 > Place.count
   (0.7ms)  SELECT COUNT(*) FROM "places"
 => 5

Finally, let's make sure the first place matches the first seed we created (The White House). You should see this output:

irb(main):002:0> Place.first
  Place Load (0.3ms)  SELECT "places".* FROM "places" ORDER BY "places"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> 
#<Place:0x000056403376b848
 id: 2,
 name: "The White House",
 address: "1600 Pennsylvania Avenue NW, Washington, DC 20500, United States",
 latitude: 38.897699700000004,
 longitude: -77.03655315,
 created_at: Fri, 05 Nov 2021 06:25:00.618439000 UTC +00:00,
 updated_at: Fri, 05 Nov 2021 06:25:00.618439000 UTC +00:00>

9. Generating our views and controllers

The next step is for us to generate the views and controllers for our Place model. The views are what our users will see in the browser, and it's where we'll be rendering our React components (i.e. the map). The controller is responsible for handling the logic of our app - managing what happens between HTTP requests, routing, and actions. This is a core concept for Rails and I'd strongly suggest studying up on it. Check out the official routing docs to dive deeper into it.

We'll use the rails generate command again to create our controller and views. When generating a controller you can pass it the name of the methods you want to create inside it, which in turn creates the associated views. To get started, head back to your terminal and enter:

rails generate controller places index
Running via Spring preloader in process 420964
      create  app/controllers/places_controller.rb
       route  get 'places/index'
      invoke  erb
      create    app/views/places
      create    app/views/places/index.html.erb
      invoke  test_unit
      create    test/controllers/places_controller_test.rb
      invoke  helper
      create    app/helpers/places_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/places.scss

There's a lot to take in so let's break down the important bits of what we just did:

Controller & Routes

  • create app/controllers/places_controller.rb
  • route get 'places/index'

Here we've created our controller and, because we signaled that we wanted to include an index action, the generator created the route for us. If we check the places_controller.rb you should see an empty method for our index action that will be revisited once we start rendering in our views:

class PlacesController < ApplicationController
  def index
  end
end

Views

  • invoke erb
  • create app/views/places
  • create app/views/places/index.html.erb

The final piece of the MVC puzzle - this is where all our back end goodness comes to life on the page. Again, thanks to the options we passed in the generate controller command, Rails automatically created two pages for us in the embedded ruby (erb) format.

The .erb format is highly flexible and allows us to run Ruby code in our view, making the process of connecting back to front really easy. We'll be using it in-depth later to connect our controller logic to our React components.

10. Routing

The last step before we move onto React and Google Maps is to configure our routes. If you're not sure about routing I fully recommend revisiting the routing documentation linked above.

For now we'll only be using the index route, which returns all instances of a model. If you navigate to rails-react-google-maps/config/routes.rb you'll see that the controller generator already gave us the route but we'll be cleaning it up using Rails resources and specifying what we want to have access to. At the same time we'll reroute the root (homepage) of our app to the index page:

Rails.application.routes.draw do
  root to: 'places#index'
  resources :places, only: %i[index]
end

This just moves the routing logic to one line which we can add to or remove from to adjust our routes, as well as making our places index page our new homepage when we start the app.

Let's test it out. If you're running a server in your terminal, press Ctrl+C to shut it down and restart - if your server isn't running:

rails server

Open localhost:3000 in your browser and you should have a mostly empty page that looks like this:

It's not looking so great at the moment but it means that our front end is connected to our back end!

Congratulations, you now have a working Rails app that has a working model, a view that's displaying in our browser, and a controller that we'll soon fill with lots of logic goodness to bring our app to life.

Carry on to Part Two to implement React and use it to build out our map feature in Google Maps!

33