Star Wars API — Part 3: Rails & Localhost

Let’s Make A (Local) Website

Robert Hopkins
12 min readJun 10, 2016

In Part 2 we attached all of the information from the Star Wars API to a database using ActiveRecord and several has_and_belongs_to_many relationships. In this part we rebuild the ActiveRecord associations using has_many relationships and then put all the information in the database onto a local server so we can browse all of the information by visiting http://localhost:3000.

I am going under the assumption that you have rails installed and have a little bit of an idea of what it is capable of. If you do not have it installed or you are incredibly new to programming in Ruby I suggest finding smaller scale projects (like building a CLI app to access the API or just using ActiveRecord to create a database). Also a little bit of ERB and HTML knowledge will be needed as well. If you aren’t familiar with either, we are going to be using very basic code so it shouldn’t be too difficult to understand.

By only changing the associations we can reuse a large portion of the code written in part 2, but there is a considerable amount of changes that need to be made in order to get the Rails app fully functioning. Let’s get started.

Setup

Create A New Rails Application

Enter the directory of where you want your new web-app to live and run the rails new command followed by the name of your new app:

rails new swapi

This will generate the Rails framework for our new app.

Congratulations if this is your first Rails Application! You will soon be overwhelmed with the Rails magic.

Generate Models and Controllers

We then need to run the next 15 generators in order to handle all the models and associations we will need:

rails g resource character
rails g resource film
rails g resource planet
rails g resource vehicle
rails g resource starship
rails g resource specie
rails g model character_film
rails g model character_specie
rails g model character_starship
rails g model character_vehicle
rails g model film_planet
rails g model film_specie
rails g model film_starship
rails g model film_vehicle
rails g model api_communicator

After running these 15 lines we should now have:

  • 15 Model Files
  • 6 Controller Files
  • 6 Routes in config/routes.rb

Since we have no plans on writing tests for this part we can add a flag to avoid generating 21+ sets of test files if you so choose:

rails g resource character --no-test-framework

The reason we generated a resource for the first 6 and a model for the last 9 is because eventually we will need a few other things that come with generating a resource. When you generate a resource you actually generate a model, controller, views, assets, and a migration file. All of those will be required for our main 6 classes. Since we only need model files and migration files for the other 9 classes we can use generate model.

Filling in the Migration Files

For the 6 models from Part 2, you can copy the code inside the #change function and paste it inside its respective migration file in your new Rails app.

Watch out for a typo I made in Part 2, you can either ignore it or correct it everywhere. For both vehicles and starships I misspelled “max_atmosphering_speed” and created a working product that instead has a “max_atmoshpering_speed.”

For the other 8 migration files (all of your join tables) you need to add 2 lines in the change function. Here are 2 examples, all 8 follow the same convention (the number in your file name will not match mine, but the name will definitely be the same. The number is based on a timestamp of when the generator was run):

#db/migrate/20160601222343_create_character_starships.rbclass CreateCharacterStarships < ActiveRecord::Migration
def change
create_table :character_starships do |t|
t.integer :character_id
t.integer :starship_id
t.timestamps null: false
end
end
end
#db/migrate/20160601222513_create_film_species.rbclass CreateFilmSpecies < ActiveRecord::Migration
def change
create_table :film_species do |t|
t.integer :film_id
t.integer :specie_id
t.timestamps null: false
end
end
end

Models

Setting Up Associations

We now need to add the logic for associations into 14 of the 15 models we just created. For each pairing of models that have a many-to-many relationship it requires 2 lines of associations and a join table in order to get the desired functionality. Here is a short example of what is required just to connect Characters to Films

#character.rb
has_many :character_films
has_many :films, through: :character_films
#film.rb
has_many :character_films
has_many :characters, through: :character_films
#character_film.rb
belongs_to :film
belongs_to :character

The self.primary_key = ‘id’ line is required in every model since, if you remember from Part 2, we are assigning our own values for primary keys for each model instead of using auto increment.

Here is a code block of everything required in the models:

#character.rb
has_many :character_films
has_many :films, through: :character_films
has_many :character_starships
has_many :starships, through: :character_starships
has_many :character_vehicles
has_many :vehicles, through: :character_vehicles
has_many :character_species
has_many :species, through: :character_species
belongs_to :planet
self.primary_key = 'id'
#film.rb
has_many :film_planets
has_many :planets, through: :film_planets
has_many :film_species
has_many :species, through: :film_species
has_many :film_vehicles
has_many :vehicles, through: :film_vehicles
has_many :film_starships
has_many :starships, through: :film_starships
has_many :character_films
has_many :characters, through: :character_films
self.primary_key = 'id'
#planet.rb
has_many :film_planets
has_many :films, through: :film_planets
has_many :characters
has_many :species, through: :characters
self.primary_key = 'id'
#starship.rb
has_many :character_starships
has_many :characters, through: :character_starships
has_many :film_starships
has_many :films, through: :film_starships
self.primary_key = 'id'
#vehicle.rb
has_many :character_vehicles
has_many :characters, through: :character_vehicles
has_many :film_vehicles
has_many :films, through: :film_vehicles
self.primary_key = 'id'
#specie.rb
has_many :character_species
has_many :characters, through: :character_species
has_many :planets, through: :characters
self.primary_key = 'id'

And here are the lines required in the association models:

#character_film.rb
belongs_to :film
belongs_to :character
#character_specie.rb
belongs_to :specie
belongs_to :character
#character_vehicle.rb
belongs_to :vehicle
belongs_to :character
#character_starship.rb
belongs_to :starship
belongs_to :character
#film_planet.rb
belongs_to :film
belongs_to :planet
#film_starship.rb
belongs_to :film
belongs_to :starship
#film_specie.rb
belongs_to :film
belongs_to :specie
#film_vehicle.rb
belongs_to :film
belongs_to :vehicle

Recognize the difference in the 2 sections. In all of the models we are using has_many relationships all of our symbols are plural. In all of the join table files we use the singular version of the word since we are defining belongs_to relationships.

Gemfile

In addition to the many gems that come included with Rails, we are going to add one more.

gem 'pry-rails'

This allows us to pry into any bugs and gives us a much easier to understand console/terminal. Once you have added this gem, don’t forget to run ‘bundle install’ from your terminal.

The plural of “specie” is “species”, The singular of “species” is “specy”?

Here is an example of some “Rails Magic” that comes back to bite us. Rails is an incredibly intelligent framework, but since it is so far abstracted that sometimes it can make mistakes when you introduce the nuance of your own application. When I was building this originally I was running into serious errors when trying to run Character.first.species or Planet.last.species that read something along the lines of “Error cannot find constant Character::Specy.” You can explicitly tell Rails how you want to interpret some of the stranger words of the english language (like species which is the same word both singular and plural). Since we need a singular and plural version we need to implement some logic to accomplish this.

In the inflections.rb file the following loop needs to be added and all of our pluralization problems will be solved:

#app/config/initializers/inflections.rbActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'specie', 'species'
end

Moving Logic Over from Part 2

We can explicitly copy over our logic for the ApiCommunicator from Part 2 into our new Rails app. I copied and pasted the entire file word-for-word.

The files that are contained inside bin.rb (the file we executed to populate the database in Part 2) can be removed from a function and explicitly pasted into db/seeds.rb so our rake command “rake db:seed” will fill our new Rails database.

Once you think everything is set up the way you wanted attempt running the following rake commands:

rake db:create
rake db:migrate
rake db:seed

If you see no errors then everything should be okay. You can test with a few commands to either verify your database is working or to narrow down where your current problem is:

rails c

This will drop you into your Rails console. Since we have installed the gem ‘pry-rails’ we are actually in an instance of pry that has access to our current developmental database. Now that you are in your console try running some of these commands. If you are not getting errors that means we have successfully set up the database and all of our associations:

###if you have no errors###c = Character.first
c.species
c.planet #this is singular, not plural!
c.vehicles
c.starships
c.films
f = Film.last
f.characters
f.planets
f.species
f.starships
f.vehicles

If you have errors, then you can create your own Character or Film object and run the rest of the lines above to see what exactly is giving you your error:

##instead of c = Character.first or f=Film.last##c = Character.create(name: "Robert")
f = Film.create(title: "Episode 9001")

If you have ever need to go back to a fresh seed of the database you can run rake db:reset, but if you made any changes to migration files (don’t do that) or you added any new migration files then you’ll need to run the full set of commands:

rake db:drop
rake db:create
rake db:migrate
rake db:seed
ORrake db:reset #this is all 4 of the above commands, it assumes there have been no changes to your migrate folder

Controllers & Views

The Homepage!

Now that we have fully set up the database in Rails we can now start building the website.

Disclaimer: your website will be black text and blue links on a white background running locally if you follow from here on out. In Part 4 (link here) I will write about implementing Bootstrap (which makes it pretty) and deploying onto Heroku (which means you’ll be live on the internet).

First we are missing a controller and a route to handle the homepage. In console run the following:

rails g controller home --no-test-framework

In you’re home controller you only need to add one empty method:

class HomeController < ApplicationController
def index
end
end

Then we have to go into our routes.rb file and add a route that will direct people to hit this controller action when they visit the root website. Think google.com, but for us it will be localhost:3000.

#config/routes.rb should now look like thisRails.application.routes.draw do 
resources :characters
resources :vehicles
resources :starships
resources :species
resources :planets
resources :films
root 'home#index'
end

In the above block “resources” sets up a controller with the default routes for 7 different controller actions: index, new, create, show, edit, update, and destroy. We will only be using index and show so we could append logic to each route to specify that, but in the case of our app it isn’t necessary. Example:

 resources :characters, only: [:index, :show]

Now we must build the view or what code is sent to the browser when you make a web request. Let’s start with just a Hello World! to make sure it works.

#app/views/home/index.html.erb<h1>Hello World!</h1>

In your console type: rails s

This will start a local server on port 3000. In your browser visit http://localhost:3000. You should see this:

A living website!

We can now add some real logic. Let’s build links to an index page for our 6 categories. Replace the above code with the code below:

<h1> Welcome to My Website</h1>
<br>
<a href=’/characters’>Characters</a><br>
<a href=’/films’>Films</a><br>
<a href=’/planets’>Planets</a><br>
<a href=’/starships’>Starships</a><br>
<a href=’/vehicles’>Vehicles</a><br>
<a href=’/species’>Species</a><br>

Save it and refresh the webpage. This should yield:

Real links that don’t lead anywhere, yet.

This currently leads to 6 dead links, but we can set them up relatively easily.

Congratulations on building a homepage! Let’s make it a website now.

Index Pages

Just like we created the index.html.erb file in the home folder of views we need to replicate this and create it in each of the 6 other view folders. All 6 files will be named exactly the same. This is because the index action in each controller is looking for a file of the same name in views by default.

I will explain the logic behind building a single index page (character). The logic is repeated 5 more times for the other pages.

First we want to set up our controller so we have access to our Ruby objects:

#app/controllers/characters_controller.rbclass CharactersController < ApplicationController
def index
@characters = Character.all
end
end

Now we can use ERB and we’ll have access to an array of all characters in our database from the view file.

#app/views/characters/index.html.erb<h2> Character Index Page </h2>
<a href=’/’><p>home</p></a>
<% @characters.all.each do |c|%>
<a href=”/characters/<%=c.id%>”><%= c.name %></a><br>
<%end%>

This will create a page with a title, a link back to the homepage, and a series of links to individual character pages. The default logic for the link to a showpage is along the lines of ‘/characters/:id’ where :id is read as a parameter. That means if you visit localhost:3000/characters/ANYTHING it will interpret ANYTHING as an :id.

As far as the code that contain % signs inside of what looks like HTML brackets, this is ERB. Anything with just <% rubycode%> will be interpreted/read. Anything between <%= rubycode %> will be printed to the view.

Now your character index page should look something like this:

The links go on for a while. I cut them off in the screenshot.

Now we’ve created another page with a long series of dead links. Next we need to create a show page. This will interpret the same code for each individual character so the code for those pages will be slightly more abstract.

Disclaimer: Before moving on to the Show Pages section you should create the other 5 index pages.

Show Pages

Now we need to build the 6 show pages. Again, I will build the character show page, but the logic can be used to create show pages for the other 5.

Remember that when you create a show page the logic needs to be able to work for every single instance of that object in your database. That means Luke Skywalker and Darth Vader will be treated the same by your view file.

Let’s start by setting up the controller. Before I stated that the default link to a show page is in the form ‘/characters/:id’. That means when we visit http://localhost:3000/characters/1 we want to visit the page for Character.find(1). So we have access to the parameter :id in the show method in the character controller.

#app/controllers/character_controller.rbclass CharacterController < ApplicationController
def index
@characters = Character.all
end
def show
@character = Character.find(params[:id])
end
end

So now on the show pages we pass in a single instance of the Character class instead of passing in an array of all instances of the object class like we do on the index page.

Let’s start by just displaying a title, two links (one to the character index page and another back to the root) and the attributes of each instance before we get into displaying some of the associated objects

#app/views/characters/show.html.erb<h3> Character Show Page! </h3>
<a href=’/’>home</a><br>
<a href=’/characters’>characters</a><br>
<hr>
Name: <%= @character.name %><br>
Height: <%= @character.height %>cm tall <br>
Mass: <%= @character.mass %>kg <br>
Hair Color: <%= @character.hair_color %><br>
Skin Color: <%= @character.skin_color %><br>
Eye Color: <%= @character.eye_color %><br>
Gender: <%= @character.gender %><br>
Birthyear: <%= @character.birth_year %><br>

We use <%= rubycode %> because we want to print the string value every single time.

If you load up http://localhost:3000/characters/1 you should now see:

If any of these attributes return ‘nil’ then we will just get a blank instead of an error so blank attributes are no cause for concern. Next, when we import associations, we will be trying to iterate over arrays and if the arrays are empty we will get an error for trying to call .each on an empty array so we will need to work in logic to prevent pages with incomplete information from breaking.

We also want to work in a link to the show page of the associated films, starships, etc. Using ERB we can build a list of links for each attribute. Here is an example of how to build species on the character page:

Specie:
<% if @character.species.empty?%>
No Species Listed
<% else %>
<a href=’/species/<%= @character.species[0].id %>’><%= @character.species[0].name %></a>
<% end %>

The result creates a link to the “human” species page:

We can apply the same logic to the other 4 associations:

<hr>
Home Planet: <a href=”/planets/<%= @character.planet.id %>”><%= @character.planet.name %></a><br><br>
Vehicles
<ul>
<% if !@character.vehicles.empty? %>
<% @character.vehicles.each do |v| %>
<li><a href=”/vehicles/<%=v.id%>”><%=v.name%></a></li>
<%end%>
<% else %>
<li>No Vehicles</li>
<% end %>
</ul><br>
Starships
<ul>
<% if !@character.starships.empty? %>
<% @character.starships.each do |s| %>
<li><a href=”/starships/<%=s.id%>”><%=s.name%></a></li>
<%end%>
<% else %>
<li>No Starships</li>
<% end %>
</ul><br>
Films
<ul>
<% @character.films.each do |f| %>
<li><a href=”/films/<%=f.id%>”><%=f.title%></a></li>
<%end%>
</ul><br>

The final result is:

If everything is built correctly, then you can apply the same logic to the other 5 show pages and the end result should be a fully functioning website on your local computer. Congrats on building your first rails application!

-Robert Hopkins | robert.hopkins@flatironschool.com

--

--