33
Building a Rails app backbone
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.
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.
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.
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.
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.
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.
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.
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.
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!
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>
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:
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
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.
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