Ruby Social Gems: Github
This is the fourth installment in my series about Ruby Social Gems. Today, I am focusing on the Octokit gem for Github.
In the previous parts of this series, we covered a social gems such as LinkedIn and Facebook, creating a simple application that covers the authentication and APIs queries. We are going to build a similar application with the Octokit gem for the same purposes.
Project Setup
We’ll use rails-composer to setup the project quickly with the required gems and configuration.
If you don’t know rails-composer, it’s simply a couple of Rails project templates that you can build interactively when initiating your project.
Project Initialization
You can download the full source code of this tutorial from Github here
Run the following line to create the project:
rails new rsg_github_gem -m https://raw.github.com/RailsApps/rails-composer/master/composer.rb
You’ll be asked a few questions from the composer about the project template. You can choose between different options as you like. I’ll list the important options that I selected to build this project:
- Build a RailsApps example application
- rails-omniauth
- ERB
- GitHub
After creating the project, create an application on Github to get the Client ID
and Client Secret
. These values go in the secrets.yml
file in your Rails app. Don’t worry to much about that now, we’ll get back to this step later.
Create Github application
Go to (your Github settings)[https://github.com/settings/applications] and click on the Register new application button, then fill out the form. The only value that matters for our purposes is the “Authorization callback URL” field, which you should fill in with the exact callback defined in your routes.
rails-composer created this route as http://localhost:3000/auth/github/callback
.
Looking in the rsggithubgem/config/routes.rb file on line 4 you’ll see get '/auth/:provider/callback' => 'sessions#create'
. This is our callback.r :provider
here is github
because we’re using the OmniAuth gem for authentication.
If you are unfamiliar with OmniAuth, here’s a quote from their Github repository:
OmniAuth is a library that standardizes multi-provider authentication for web applications. It was created to be powerful, flexible, and do as little as possible. Any developer can create strategies for OmniAuth that can authenticate users via disparate systems. OmniAuth strategies have been created for everything from Facebook to LDAP.
We’ll cover OmniAuth in details in a future tutorial.
OmniAuth Provider key and secret
After you have created the application in Github copy the “Client ID” to the omniauth_provider_key
and the “Client Secret” to omniauth_provider_secret
in rsggithubgem/config/secrets.yml.
Restart the Rails server and visit (http://localhost:3000/signin)[http://localhost:3000/signin]. You should be redirected to the Github authorization page to authorize your application.
Boom! We have just implemented login with Github, the objective of this tutorial.
Now, we will introduce the Github API, the Octokit gem, and then dive right into building the demo project.
Introduction to Github API
Github exposes a very rich set of APIs that allow developers to access most (if not all) of the Github data. It provides a set of web hooks that gives you the power to build very interesting applications.
You can access Github APIs without authentication, but you’ll be limited on the number of requests per hour. Here’s a quote from the Github reference about the limits:
For requests using Basic Authentication or OAuth, you can make up to 5,000 requests per hour. For unauthenticated requests, the rate limit allows you to make up to 60 requests per hour.
In the API reference you can see all of the different areas, from the Activity APIs to the Enterprise API. We will focus on two sections: Users and Repositories
The Octokit.rb Gem
OmniAuth is just for authenticating users with their Github accounts. We’ll use the Octokit gem to access the API in a pragmatic way. Here’s a quote from their Github repository:
Octokit.rb wraps the GitHub API in a flat API client that follows Ruby conventions and requires little knowledge of REST. Most methods have positional arguments for required input and an options hash for optional parameters, headers, or other options
Let’s start building the API queries part of the demo project with the Octokit gem.
Add the gem to the Gemfile gem "octokit", "~> 3.0"
and run bundle install
.
Building the Demo Project
Before we start writing any code, let’s review some of the generated code inside the session_controller
, the user
model, and the routes.
Routes
Let’s focus on two routes. The first route is used as the success callback mentioned before, that you should hav added to the Github application.
get '/auth/:provider/callback' => 'sessions#create'
The other route is the failure route that Github redirects the user to if the user refused to authorize the application or any other authorization error occur.
get '/auth/failure' => 'sessions#failure'
User Model
We have made some changes to the user model, so this is not just the generated code. The following code creates a new user with provider
, uid
and the name
. Once the user is created, we add a GithubProfile
object with the basic info and access_token
to use later to access Github.
def self.create_with_omniauth(auth)
new_user = create! do |user|
user.provider = auth['provider']
user.uid = auth['uid']
if auth['info']
user.name = auth['info']['name'] || ""
end
end
new_user.create_github_profile auth
new_user
end
We’ll create the GithubProfile
model later, all we need to do now is create an object from this model and attach to the current user.
def create_github_profile auth
GithubProfile.create({user_id: id, nickname: auth[:info][:nickname]||"",
email: auth[:info][:email]||"", name: auth[:info][:name]||"",
image: auth[:info][:image]||"",
location: auth[:extra][:raw_info][:location]||"",
public_repo: auth[:extra][:raw_info][:public_repos]||"",
public_gists: auth[:extra][:raw_info][:public_gists]||"",
followers: auth[:extra][:raw_info][:followers]||"",
following: auth[:extra][:raw_info][:following]||"",
member_since: auth[:extra][:raw_info][:created_at]||"",
access_token: auth[:credentials][:token]})
end
Github Profile Model
This model holds the basic information about each user Github profile, along with their access token to the API.
rails g model github_profile user_id:integer nickname:string email:string name:string image:string location:string public_repo:integer public_gists:integer followers:integer following:integer member_since:string access_token:string
Add has_one :github_profile
to the User
model to associate each profile with it user.
Session Controller
The new
action directs users to a predefined route by OmniAuth /auth/github
. OmniAuth redirects the user Github with correct params and URL. You can hit this action with the /signin
route.
def new
redirect_to '/auth/github'
end
The create
action is standard in all the OmniAuth strategies. It extracts the provider name (github
in this case) and the unique id of the user on this provider. If there was no users with these params, it will create a new user using the create_with_omniauth
method in User
model.
def create
auth = request.env["omniauth.auth"]
user = User.where(:provider => auth['provider'],
:uid => auth['uid'].to_s).first || User.create_with_omniauth(auth)
reset_session
session[:user_id] = user.id
redirect_to root_url, :notice => 'Signed in!'
end
Github Controller
This controller displays the current user repos.
rails g controller github index
We’ll use the simplest query here for the purpose of the demo project, which is pulling the list of all public repos for the current user.
First, authenticate the client with the access token stored in the GithubProfile
model. After that, just query the user repos with client.repos
.
def index
client = Octokit::Client.new(:access_token => current_user.github_profile.access_token)
@repositories = client.repos
end
Views
Prepare the existing views to show the “login” and “logout” buttons, as well as display the basic information we have about the current user.
Stating with the navigationlinks.html.erb, add links to logout, login, the user Github profile.
<li>
<%= link_to 'rsg_github_gem', root_path, class: 'navbar-brand' %>
</li>
<% unless current_user %>
<li>
<%= link_to "Sign in with Github", "/signin" %>
</li>
<% else %>
<li>
<%= link_to "Github Profile", "/github" %>
</li>
<li>
<%= link_to "Sign out", "/signout" %>
</li>
<% end %>
We’ll display the basic information of the logged in user in visitors/index.html.erb.
<% if current_user %>
<div><h3>Welcome, <%= current_user.name %></h3></div>
<img src="<%= current_user.github_profile.image %>" width="100px" style="float: left;
margin-right: 12px;">
<div style="margin-left: 120px;">
<h4>From: <%= current_user.github_profile.location %></h4>
<h4>Public Repos: <%= current_user.github_profile.public_repo %></h4>
<h4>Public Gists: <%= current_user.github_profile.public_gists %></h4>
<h4>Followers: <%= current_user.github_profile.followers %></h4>
<h4>Following: <%= current_user.github_profile.following %></h4>
<h4>Member Since: <%= Date.parse current_user.github_profile.member_since %></h4>
</div>
<% else %>
<h1>Welcome, please login.</h1>
<% end %>
The last view that we are going to write is github/index.html.erb which displays the basic information for the current user’s public repostories.
<h1>Public Repositories</h1>
<ul style="list-style-type: none;">
<% @repositories.each do |repo| %>
<li>
<p><b><%= repo[:name].capitalize %></b>: <i><%= repo[:description].capitalize %></i> <b>[Watchers: <%= repo[:watchers] %>, Forks: <%= repo[:forks]%>, Stargazers: <%= repo[:stargazers_count] %>]</b></p>
</li>
<% end %>
</ul>
It looks like this:
Scopes
The last point related to accessing the Github API is Scopes, which defines what you can or can’t access from the user data. You can set up the scopes in the OmniAuth config in rsggithubgem/config/initializers/omniauth.rb like so:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :github, Rails.application.secrets.omniauth_provider_key, Rails.application.secrets.omniauth_provider_secret, scope: "user,repo,gist"
end
Wrapping up
I hope this tutorial was useful and a good introduction to using the Octokit gem and Github APIs. If you would like to suggest the next gem to cover, do so in the comments. Thanks for reading!