Ruby is often thought of as a web language, due to the popularity of web frameworks like Ruby on Rails. Building websites and web backends using Ruby is certainly easy and the ecosystem is huge, making it ideal. But Ruby can of course be used for other purposes. In this article, we’d like to show you how to use a Twitter API and an example of how you can build a simple CLI tool for your terminal. This should be very simple for you to extend with other APIs!
How to get Twitter API Data
For this article, we’ll be using an API by PeerReach, which provides some interesting information about Twitter users, like the subjects they’re associated with, which groups they belong to, and which ranks they have in these groups. To get started with this API, head to rapidapi.com, and create an account if you haven’t already. Once you’re done, you’ll need to subscribe to the PeerReach API here. You can just press the “Subscribe to Test” button to begin.
Once you’ve signed up, we can play around with it. First, try it out straight from your browser. In the middle column, you can see the endpoint and the parameters you can pass (only the username in this case). Enter any username (perhaps yours?) and click on the “Test Endpoint” button on the top.
On the right of the page, you’ll see the API’s reply nicely formatted. Another way we can play around with the API is using curl. You most likely have this installed already on your computer. You’ll just need to copy your API key from RapidAPI’s site (you can easily copy it from one of the code snippets on the right). Then, you’d just run this command on your terminal:
curl -H "x-rapidapi-key: YOUR_API_KEY" 'https://peerreach-peerreach-subscription.p.rapidapi.com/user/lookup.json?screen_name=codinghorror'
The URL we’re querying can also be grabbed from one of the snippets by the way. Run this and you should quickly get the same response you saw when testing on the website.
The third way of testing is just copying the snippets provided for you! Look on the right and you’ll find a list of different languages and libraries to use. Let’s grab the “Ruby (Net::HTTP)” one:
You can straight up copy and paste this into a new twitter.rb
file anywhere in your system, then run it using ruby from your terminal with ruby twitter.rb
. You should see something like this:
$ ruby test.rb {"screen_name":"codinghorror","user_id":"5637652","lastupdate":"2020-07-31 15:20:03","followers":"275846","friends":"223","country":null,"gender":"Male","interests":[],"profiles":["webtech","blogger","games","humor","developer"],"peergroups":[{"topic":"developer","region":"ww","score":"4618","rank":"8"},{"topic":"webtech","region":"ww","score":"9990","rank":"308"},{"topic":"blogger","region":"ww","score":"2351","rank":"1822"},{"topic":"games","region":"ww","score":"3450","rank":"4047"}],"subjects":[{"name":"uber","subject_id":"603641","score":"2","assign_date":"2015-06-18 01:07:33"},{"name":"dnc","subject_id":"885429","score":"1","assign_date":"2016-11-16 01:01:10"},{"name":"nintendo","subject_id":"22330","score":"1","assign_date":"2016-11-12 01:01:15"},{"name":"products","subject_id":"722189","score":"1","assign_date":"2016-04-15 01:01:18"},{"name":"halloween","subject_id":"601068","score":"1","assign_date":"2015-09-22 01:01:17"},{"name":"storage","subject_id":"1016169","score":"1","assign_date":"2015-08-13 01:07:07"},{"name":"nimoy","subject_id":"1434142","score":"1","assign_date":"2015-02-28 01:17:19"},{"name":"windows","subject_id":"601162","score":"1","assign_date":"2014-10-02 01:02:50"},{"name":"windows10","subject_id":"1182952","score":"1","assign_date":"2014-10-02 01:02:49"},{"name":"watson","subject_id":"635196","score":"1","assign_date":"2014-09-23 01:02:55"},{"name":"minecraft","subject_id":"22088","score":"1","assign_date":"2014-09-10 01:03:11"},{"name":"potato","subject_id":"708353","score":"1","assign_date":"2014-07-08 01:02:59"},{"name":"swift","subject_id":"602033","score":"1","assign_date":"2014-06-03 01:09:37"},{"name":"wall","subject_id":"600118","score":"1","assign_date":"2014-05-20 01:08:22"},{"name":"bridge","subject_id":"657510","score":"1","assign_date":"2014-05-01 01:08:06"},{"name":"fools","subject_id":"704967","score":"1","assign_date":"2014-04-01 01:07:49"},{"name":"horror","subject_id":"21302","score":"1","assign_date":"2014-03-01 01:06:26"},{"name":"norway","subject_id":"22353","score":"1","assign_date":"2014-02-19 01:07:48"},{"name":"oregon","subject_id":"22426","score":"1","assign_date":"2014-02-16 01:05:55"},{"name":"mac","subject_id":"602285","score":"1","assign_date":"2014-01-26 01:05:11"}]}
The format is obviously not formatted since it comes compressed from the server. We can fix that with a few changes:
require 'uri' require 'net/http' require 'openssl' require 'json' url = URI("https://peerreach-peerreach-subscription.p.rapidapi.com/user/lookup.json?screen_name=codinghorror") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Get.new(url) request["x-rapidapi-host"] = 'peerreach-peerreach-subscription.p.rapidapi.com' request["x-rapidapi-key"] = '6b9ae6ac49mshd298c37aadd8671p1b1cc2jsn2a002937834e' response = http.request(request) json = JSON.parse(response.read_body) puts JSON.pretty_generate(json)
Notice the highlighted lines. We require the JSON library, parse the response, and then use JSON.pretty_generate
to generate a pretty print of the object.
Example: Twitter API using Ruby and Thor
How To Create A CLI Tool for Twitter with Ruby
We’re going to be building a CLI utility using the mentioned API and Ruby. Since we’re using Ruby, you’ll of course need that. You most likely already have it installed, otherwise, refer to the official documentation to get started. We’ll also be using bundler
and other gems. Install bundler by running gem install bundler
(which, again, you might already have installed).
Since we’re going to be building a CLI tool that depends on an API, we’re also going to need access to that.
There, you can just select the basic plan, which is free up to 2400 requests a day (more than enough for this article). You might be required to enter your credit card information, but you won’t be charged for this.
Starting Small
Let’s start by building the basic skeleton for our CLI app. We could perfectly well build the whole app from scratch, but this would be like building an entire website using Ruby from scratch (why not use Ruby on Rails?). Thus, we’ll be using a gem called Thor, which is built precisely for this purpose: to build CLI programs. It provides all the basic tools to get input, options, and other goodies. Since we’re also going to need another gem, let’s just use bundler to manage them. Create a new directory called twitter-cli
, then run bundle init
in there. This will create a basic Gemfile
for you. Edit that file and make sure it looks like this:
source 'https://rubygems.org' gem 'excon' gem 'thor'
Run bundle install
to install both our gems. We already explained what Thor is all about, but what about Excon? Excon is just a simple HTTP library, which will simplify the use of our API a bunch.
Next, create a new cli.rb
file in the same directory, and add this:
require 'bundler/setup' Bundler.require(:default) class Twitter < Thor def self.exit_on_failure? true end desc 'username', 'Fetch data from a Twitter user' def username(username) puts "Hi #{username}" end end Twitter.start
The first two lines leverage our Gemfile to load our gems. This way, if we were to add more and more gems, we only need to edit our Gemfile and run bundle install
again, and that’s it. Next, we create a new class which inherits from Thor
. This gives our class CLI capabilities. The first method we define might be a bit confusing. Thor’s default behavior is to return exit code zero when it doesn’t find an option or method. This is not really what we’d want, as an error should return a non-zero exit code. This fixes that. Below that, we define a new method called username
, which takes one argument and just prints a string. Above it, we define some short documentation for it. Finally, we “start” our CLI program.
We can now run it to see what happens. If you go to your terminal and just run ruby cli.rb
you should get this:
$ ruby cli.rb Commands: cli.rb help [COMMAND] # Describe available commands or one specific command cli.rb username # Fetch data from a Twitter user
Pretty cool huh? We got all of this documentation for free. You can even try ruby cli.rb help username
to get:
$ ruby cli.rb help username Usage: cli.rb username Fetch data from a Twitter user
Again, all of this for free. If you now run ruby cli.rb username rapidapi
you should get the string we defined:
$ ruby cli.rb username rapidapi Hi rapidapi
Fetch some data
Now we’re ready to fetch some actual data. At RapidAPI, you should be able to see your API key. Take note of this, as you’ll need it now. You might not be able to directly copy it from the center column, but you can quickly grab it from the snippets on the right:
Add some extra code above your class in cli.rb
:
require 'json' def fetch(username) encoded_username = URI.encode_www_form_component(username) response = Excon .get( 'https://peerreach-peerreach-subscription.p.rapidapi.com/user/lookup.json?screen_name=' + encoded_username, headers: { 'x-rapidapi-key' => 'YOUR_API_KEY' } ) JSON.parse(response.body) end
This is just a simple method that fetches the endpoint you see on RapidAPI (check that snippet above) and converts the response from JSON to a Ruby Hash. If you’re planning on sharing this (or any other) utility, make sure you don’t hard-code your API key in there!
The rest of the work is pretty simple. We need to modify our original username
method to fetch the user’s data and display it:
def username(username) data = fetch(username) puts "Screen name: #{data['screen_name']} (twitter.com/#{data['screen_name']})" puts "Last Update: #{data['lastupdate']}" puts "Followers: #{data['followers']}" puts "Following: #{data['friends']}" puts puts "#{data['profiles'].count} profiles" data['profiles'].each do |profile| puts " - #{profile}" end puts puts "#{data['peergroups'].count} peer groups" data['peergroups'].each do |peergroup| puts " ##{peergroup['rank']} #{peergroup['topic']}" end puts puts "#{data['subjects'].count} subjects" data['subjects'].each do |subject| assign_year = Date.parse(subject['assign_date']).strftime('%Y') puts " #{subject['name']} assigned in #{assign_year}" end end
That is a lot of puts
. No way around it, as we’re displaying a bunch of information at the same time (although there are some options, like using templates with ERB). You can get some help understanding the API’s response format using RapidAPI’s tester. Now, you should be able to run ruby cli.rb username codinghorror
(or whatever username you like!) and see something like this:
$ ruby cli.rb username codinghorror Screen name: codinghorror (twitter.com/codinghorror) Last Update: 2020-07-31 15:20:03 Followers: 275846 Following: 223 5 profiles - webtech - blogger - games - humor - developer 4 peer groups #8 developer #304 webtech #1822 blogger #4038 games 20 subjects uber assigned in 2015 dnc assigned in 2016 nintendo assigned in 2016 products assigned in 2016 halloween assigned in 2015 storage assigned in 2015 nimoy assigned in 2015 [...]
Feel free to adjust this output to your liking. What we’d like to suggest is to align the output so that it’s easier to read. See how the peer groups and subjects are difficult to see due to the alignment? This is a simple fix. All we need to do for the peer groups, for example, calculate the maximum length of the “rank”, so that we can pad the others. Let’s do that:
puts "#{data['peergroups'].count} peer groups" length = data['peergroups'].map { |d| d['rank'] }.map(&:length).max data['peergroups'].each do |peergroup| puts " ##{peergroup['rank'].ljust(length)} #{peergroup['topic']}" end
Notice the highlighted lines. We’ve added a calculation of the maximum length of the rank attribute, then we use the ljust
method on a string, which pads the string to the length we specify. Running our command again yields this result:
4 peer groups #8 developer #304 webtech #1822 blogger #4038 games
Much better! Let’s do the same for the subjects:
puts "#{data['subjects'].count} subjects" length = data['subjects'].map { |d| d['name'] }.map(&:length).max data['subjects'].each do |subject| assign_year = Date.parse(subject['assign_date']).strftime('%Y') puts " #{subject['name'].ljust(length)} assigned in #{assign_year}" end
More or less the same as before. Run your command now. You should see a much nicer and easier to read output.
JSON as Output
Being a CLI utility, we can’t avoid the power user features. What if we just need the JSON output? Sure, we could directly call the API using curl or something like that, but we can also add an option to our program easily, using Thor. Right before our username
method, add this:
desc 'username', 'Fetch data from a Twitter user' method_option :json, desc: 'Return JSON' def username(username)
The method_option
allows you to add modifiers to the command. These are, obviously, optional, as the name suggests, and they magically appear in the documentation:
Usage: cli.rb username Options: [--json=JSON] # Return JSON Fetch data from a Twitter user
Pretty nice for such little effort. Now we need to use this modifier in our code, let’s add some extra lines:
def username(username) data = fetch(username) if options[:json] print data.to_json return end # ...
We’ll fetch the data as normal, then check if the json
modifier is set. If it is, then just print the data as JSON immediately, then return to exit the program. You can try this by running ruby cli.rb username codinghorror --json
. You’ll get the straight JSON response, which you could potentially pipe into utilities like jq.
Handling Edge Cases
What if the username we search for doesn’t exist? Right now we’ll just get an error because our code doesn’t handle this. Let’s do that. The API returns an empty array []
when the user isn’t found. Let’s handle this case in our method:
if data.empty? puts 'User not found' exit 1 end puts "Screen name: #{data['screen_name']} (twitter.com/#{data['screen_name']})" puts "Last Update: #{data['lastupdate']}" puts "Followers: #{data['followers']}" puts "Following: #{data['friends']}"
If the data
variable is empty, we’ll just output a message and exit with code 1 (as a program should in an error!). Note that the output should better go to the STDERR output device if it’s an error, but we’ll leave that as an exercise to the reader.
Extra Credit
Right now we need to call ruby cli.rb
to even start our program. Ideally, we should just be able to call twitter-cli
straight from our terminal. We can achieve this very simply. First, we need to rename our program. Just rename the file directly, removing the .rb
extension and all. Then, add a header to our file to tell the OS how to run it:
#!/usr/bin/env ruby require 'bundler/setup' Bundler.require(:default) # ...
Lastly, we need to make the file executable: chmod +x twitter-cli
. Now, we can just call ./twitter-cli
to invoke our program. If you want to avoid the ./
prefix, you’d need to put the working directory in your PATH
environment variable. You can do this for your current terminal like so:
export PATH="$PATH:`pwd`"
Now you can run the command twitter-cli
directly!
Conclusion
We hope this article gave you a useful introduction to using Ruby for CLI programs. Ruby is a powerful language and it can allow you to write any type of program. Its ecosystem also makes it very easy for you to get started. We’d like to recommend trying out the paint gem to output colors and bold text. Use this as an exercise to make the output of your program even easier to read! You can also extend your program using more APIs from RapidAPI, and you don’t necessarily need to use Twitter APIs. You can perhaps combine multiple different ones (using a single API key) to make a very useful
Leave a Reply