On August 15, Ruby on Rails released its version 6, making even more features available to developers. The 13-year-old framework gives you a plethora of tools out of the box, which allows building complete applications in a very short time. With adapters for almost all database systems available, you don’t need much else.
If, however, you need to add more functionality to your services, you’ll eventually need to make use of an API. For example, upon signing up users, you could use Twilio’s Verify Phone Number API to validate their contact details. You could also use an IP Geolocation service to find out where users are, and provide a more personalized experience. The possibilities are endless. You can be sure most of the services you use on a day-to-day basis use some third-party API to add value to their products.
What is an API?
An API, shorthand for Application Programming Interface, is a broad term that refers to one form or another of allowing components to communicate with each other. In the context of this article, you can think of it as a set of URLs you can request from your Rails app to get information about something, trigger some action, process a payment, etc.
Why would you want to use Rails to consume an API? Ruby’s nature makes it quite easy to use external services. In comparison to Node.js, for example, there’s no need to use promises or callbacks, so your code ends up being more readable and maintainable. Plus, RapidAPI provides ready to go examples to get started.
RESTful APIs
If you’ve heard about APIs, by now you’ve probably also heard the term “RESTful API”. REST refers to a set of rules that an API should follow to call itself RESTful. Among others, these are:
- Client-Server Architecture: there should be clear differences or boundaries between the client and the server. This refers mostly to the separation of concerns, where the client is in charge of displaying data, where the server is in charge of storing it, in simple terms.
- Statelessness: this simply means the server does not store any information about the client, and every request must contain all the necessary details to complete. This doesn’t mean RESTful APIs can’t allow authentication, it only means that whatever information is needed for this should be handled by the client and submitted every time.
In REST, there’s this concept of collections and members. Members belong to a collection, and each has endpoints that represent them. For instance, if you think of a Blog, a Post would be a member of the Posts collection:
Collection | Member | |
Name | Posts | Post |
Endpoint | example.org/posts | example.org/posts/1 |
Notice how the member endpoint has the post id (1
in this case).
Interacting with an API
For both collections and members, you can perform actions on them. By actions we mean, for example, creating, reading, updating or deleting (the acronym CRUD might ring a bell). Actions are identified using different HTTP request methods. You’ve probably already heard about at least some of them. More often than not, RESTful APIs make use of only a couple of them:
- GET: requests the server to return either a collection or a single item of a collection, depending on whether this action was performed on a collection endpoint or a single item endpoint.
- POST: when POSTing to a collection endpoint, you’re requesting the API to create a new resource using the payload you’re submitting. In general, this action is only performed on collections and not on items.
- PUT: this action is regularly used to update a resource, that is, to change some or all values of it.
- DELETE: like the name suggests, this action instructs the server to delete a resource or a collection.
There are other actions less commonly used, like PATCH. Strictly speaking, the PUT action should be used to update a whole resource, while PATCH only to update parts of it. However, in a lot of cases, APIs tend to either only use PUT or just use both indistinctively, and it’s better to read the documentation rather than to make assumptions.
Prerequisites
Before we get started, you’ll need:
- Ruby
- Ruby on Rails
- A RapidAPI account
Installing Ruby and Ruby on Rails depends slightly on which platform you’re using. Please refer to the official documentation for installing them. For Ruby, you can use this guide on their website, whereas for Rails you can use their official installation guide. In order for you to understand this guide, you’ll need some basic knowledge of Rails and HTML. We’ll be using Ruby on Rails version 6, but version 5 will do just fine for this tutorial.
You will also need a RapidAPI account. In order to sign up, head over to rapidapi.com and click the sign up button, or just log in if this isn’t your first time around!
How to Connect to an API with Ruby?
Before getting started, let’s go through some initial steps on how to connect and use an API. Using RapidAPI, we can find and test different APIs easily and quickly. The great thing about this is that it allows you to play around with the different endpoints without having to set up anything. This can quickly give you an idea of how the API works, and what you can get from it.
For example, let’s take the REST Countries API. This simple service provides you with lots of information about a specific country. Plus, you can use it to search for countries (think, an autocomplete box), or get a country by its currency, language or calling code. Just as an example, let’s try finding a country by language. Select the Search by language endpoint in the list to the left:
Then, input es
(or the two-letter code of the language you’d like to search for). Now, just click on the Test Endpoint button. On the right, you’ll see the results.
You’ll notice you get a great deal of data about every country, from its currency to a list of countries it borders with. Feel free to play around with the other endpoints, as we’ll be using this API in the application we’ll be building.
Building a Simple Travel Planner
To get a good idea of how you can use one or more APIs in your Rails applications, we’ll be building a very simple travel planner. This app will allow the user to enter a country of destination and will show them some key details about it (the currency and timezone, for example), plus some weather information.
1. Create a New App
To get started, create a new application using rails new
:
rails new travel-planner --skip-active-record
Notice we’re skipping ActiveRecord, since our application won’t be using a database. If you intend on building something from this tutorial, you may skip that modifier. We’ll be adding Bootstrap to our Gemfile to make our UI look nice. Follow the instructions on their website to get started. Adding Bootstrap is obviously not required, so skip this step if you’re just in this for the code.
gem 'bootstrap', '~> 4.3.1'
For calling the APIs, we’ll be using the excon
gem, but feel free to use any networking gem you’re most comfortable with. Add this to your Gemfile as well:
gem 'excon'
Now run bundle install
to fetch them.
2. Add the First View
The first thing our users will see is going to be a form where they can enter a country they want to visit. Using this, we’ll find out what the capital of that country is and show them some information about that. Let’s start by creating the controller and the view for it:
# app/controllers/travel_controller.rb class TravelController < ApplicationController def index end end
A simple controller which will just render this view:
<!-- app/views/travel/index.html.erb --> <div class="container"> <div class="row"> <div class="col-lg-12 mt-5"> <div class="mx-auto mt-5" style="width: 400px"> <%= form_with(url: search_path, method: 'get', local: true) do %> <div class="form-group"> <%= label_tag :country, 'Search for a country '%> <%= text_field_tag :country, nil, placeholder: 'Eg. Germany', class: 'form-control' %> </div> <%= button_tag 'Search', class: 'btn btn-success btn-block' %> <% end %> </div> </div> </div> </div>
There’s a lot more here! Most of it is just Bootstrap styles, so don’t panic. Let’s go through it. We’re basically just creating a form that will send the user to our search_path
with the country they’ve typed. local: true
denotes we don’t want to use AJAX for this form (which is the default). Then we just specify a label and a text field for the user to type in. Lastly, we add a button to submit the form.
Before we can continue, we’ll need to add some routes to our routes file:
# config/routes.rb Rails.application.routes.draw do root to: 'travel#index' get '/search' => 'travel#search' end
We’re adding a root route (the default route) pointing to our TravelController’s index
method, plus an extra route where we’ll be doing our API calls and showing the user the results. This is the search_path
route I mentioned above. Now, when the user goes to our website, they will be presented with the form we just created.
3. Fetching Data from the APIs
Once a user sends the form, we’ll have to search for the country (and check if it exists). After that, we can use the country data to fetch the weather in the capital and display that to the user. In case we can’t find any results, we’ll just send the user back and show an alert. Let’s handle the latter first:
# app/controllers/travel_controller.rb # ... def search countries = find_country(params[:country]) unless countries flash[:alert] = 'Country not found' return render action: :index end # ... end
In this action, we’re finding the country using the country
parameter the user submits via the form. If we get no results from the API call, we’ll set an alert and render our index
action to show the form again. This requires us to tweak the form a bit so that the alert displays for the user. Add this to the form:
<!-- app/views/travel/index.html.erb --> <!-- ... --> <div class="col-lg-12 mt-5"> <% if flash[:alert] %> <div class="alert alert-warning"><%= flash[:alert] %></div> <% end %> <div class="mx-auto mt-5" style="width: 400px"> <!-- ... -->
This way, the user will see something like this when they enter a country we can’t find:
Also, you’ll probably have noticed that we’re calling some find_country
method in our action. Let’s define that:
# app/controllers/travel_controller.rb private def request_api(url) response = Excon.get( url, headers: { 'X-RapidAPI-Host' => URI.parse(url).host, 'X-RapidAPI-Key' => ENV.fetch('RAPIDAPI_API_KEY') } ) return nil if response.status != 200 JSON.parse(response.body) end def find_country(name) request_api( "https://restcountries-v1.p.rapidapi.com/name/#{URI.encode(name)}" ) end
Lots going on here! First, we define a private and generic method which we can use to fetch data from our APIs. Both calls we’ll define are very similar, so it’s good practice to extract all the similar code to a single method for easier maintenance.
This method performs a GET request to the url
we specify and sends our RapidAPI Key (which is grabbed from our environment) and the host of the URL (which RapidAPI uses to identify you as a developer). After that, the method parses the response and converts it to a ruby object. In case the API returns something other than a status 200, we’ll just return nil
.
Our find_country
method is fairly simple: it just uses the REST Countries API to get data about a country. We need to URL-encode the query before sending it, otherwise, we don’t get any meaningful results.
4. Adding Weather and Putting it all Together
Our search
action is not yet complete. We still need to fetch the weather info for our user. Let’s add some more to the action, so it ends up looking like this:
# app/controllers/travel_controller.rb def search countries = find_country(params[:country]) unless countries flash[:alert] = 'Country not found' return render action: :index end @country = countries.first @weather = find_weather(@country['capital'], @country['alpha2Code']) end
And let’s define the find_weather
method as well:
# app/controllers/travel_controller.rb def find_weather(city, country_code) query = URI.encode("#{city},#{country_code}") request_api( "https://community-open-weather-map.p.rapidapi.com/forecast?q=#{query}" ) end
In order to fetch weather information, we’re using the Open Weather Map API. This API has a forecast endpoint which required the city and country. We can get this from the country data we fetched from the other API. Now, to put this all together, we’ll need a view:
<!-- app/views/travel/search.html.erb --> <div class="container"> <div class="row mt-5"> <div class="col-lg-6"> <h4>Country info</h4> <dl> <dt>Country</dt> <dd><%= @country['name'] %></dd> <dt>Capital</dt> <dd><%= @country['capital'] %></dd> <dt>Currency</dt> <dd><%= @country['currencies'].join(', ') %></dd> <dt>Timezone(s)</dt> <dd><%= @country['timezones'].join(', ') %></dd> </dl> </div> <div class="col-lg-6"> <h4>Weather Info</h4> <dl> <% @weather['list'].each do |weather| %> <dt><%= weather['dt_txt'] %></dt> <dd> <strong>Min</strong> <%= to_fahrenheit weather['main']['temp_min'] %> <strong>Max</strong> <%= to_fahrenheit weather['main']['temp_max'] %> </dd> <% end %> </dl> </div> </div> </div>
This view looks like a lot, but it’s not that complex after all. We’re grabbing some key information about the country and displaying that on the left, and, on the right, we’re simply going through the weather forecast. We created a to_fahrenheit
helper, which is needed to convert the temperature we fetched from Kelvin to Fahrenheit:
# app/helpers/temperature_helper.rb module TemperatureHelper def to_fahrenheit(temp) # Kelvin * 9/5 - 459.67 fahrenheit = (temp.to_f * 9.fdiv(5) - 459.67).round(2) "#{fahrenheit}ºF" end end
This saves us some code in our view and is considered good practice: you want to avoid too much application logic in your templates.
5. Testing your App
Related: How to test your API with Ruby
Give it a go! Run this command on your terminal to start your server up:
RAPIDAPI_API_KEY=your_api_key_here rails server
Remember to insert your RapidAPI Key in the command. You should now be able to go to https://localhost:3000, enter a country name and get some data about it plus the weather forecast:
Conclusion
This tutorial should’ve given you a basic overview of what a REST API is and how you can leverage it in your Ruby on Rails apps. You should now have some basic knowledge of how to fetch data from a web service. We recommend you play around with different APIs offered at RapidAPI.
Very helpful… Thanks
I got these error :
Showing /var/www/travel-planner/app/views/travel/search.html.erb where line #20 raised:
undefined method `[]’ for nil:NilClass
Please help me
Hi,
what does line 20 have? The error indicates the value you’re trying to print is nil. You can try and print that value directly to see what the contents are.
Let me know how that goes.
I’m actually getting the same error as the user above. Line 20 indicates:
So looking at the response example the link below it looks like even when a country exists weather isn’t always a param that’s returned. If I run the code with this snippet removed then it works great.
https://rapidapi.com/apilayernet/api/rest-countries-v1?endpoint=53aa5a09e4b051a76d241368
”
Min
Max
“
I’ve tried this with a different API wand got rid of the weather part but i still get the nil[] error
I tried this example with a different API and I’m still getting that nil[] error any suggestions?
Hi Gavin, what API are you using?
Hello to all
In this enigmatical forthwith, I honey you all
Rise your one’s nearest and friends
Hi! Thank You for that.waw!… Like a Mojohead Label =)
Glad you liked it!
Where would you put the API key if you wanted it to work without having to add the API key during the terminal server startup?
Hi Jeff, I would suggest looking into “Rails Encrypted Credentials” for that. Should be what should need.
Hey …
does anybody this error
TravelController#index is missing a template for request formats: text/html
I follow every step … but could not reach the example running
I am using Rails 6.0.3.1
and got a suspect about whitespaces in folder names but … even handling this … still did not work,
Hey! Awesome tutorial. I cam across a few issues when trying this out.
1. Instead of putting the key on startup, I preferred using the key in my travel controller instead.
Instead of using the fetch function
‘X-RapidAPI-Key’ => ENV.fetch(‘RAPIDAPI_API_KEY’)
I found it easier to hardcode the key
‘X-RapidAPI-Key’ => ‘3f5c0e8747msha687f45b66a7728p13eeedjsne26a41a665a2′
2. Probably not an issue on your end but I noticed putting in US will result in Australia. The get request actually searches for the first result with a matching text string.
3. @weather is not getting defined. I unfortunately can’t confirm the API response on rapidAPI since the weather API is “Freemium” (I might check this in postman) but I get this error when trying to call the @weather variable in the search route.
undefined method `[]’ for nil:NilClass
I had a similar issue with @weather not being defined. It turned out that I needed to register for the weather API. Once my account registered it, it worked. It’s free but I think it limits you to something like 500 API calls per month. It should be enough to get you through the tutorial, at least!
Thanks @J
Please add that you need to subscribe to the open weather map api service by visiting: https://rapidapi.com/community/api/open-weather-map/pricing