This entry is part 1 of 3 in the series Ruby Social Gems

Introduction to the Ruby Social Gems series

In this series, we are going to cover gems based around social media and social media services, such as LinkedIn, Twitter, Facebook, Youtube, and any other suggested gems.

LinkedIn Gem

The first gem in the series is the LinkedIn gem, and the LinkedIn APIs. We’ll see how simple and clear the LinkedIn API is, and how the gem makes it more accessible.

I should point out, before we start, another important gem: omniauth and omniauth-linkedin. These gems allow the user to login with their LinkedIn account, or Facebook, or Twitter, etc. We’ll dedicate a tutorial to Omniauth later, but we’ll use Devise to create simple user accounts in this article.

The Demo Application

The application we are going to build with the LinkeIn gem is pretty straightforward. We will get all the user data from LinkedIn into our rails app models, so we can display it in whatever format we want.

Starting with the application:

You can download the full source code of the tutorial form github here

Create a new rails app:
rails new rsg_linkedin_gem

Add the linkedin, devise, bootstrap gems in path/to/project/Gemfile

gem 'devise'
gem 'linkedin'
gem 'twitter-bootstrap-rails'

And then run
bundle install

We are going to use bootstrap for the UI component.

Initializing

Before we start, we need to run few commands to initialize the project.

Create the User Model and Bootstrap Styling

The following commands are found on the github page of the devise project and the twitter bootstrap project

To install devise
rails generate devise:install

To create the user model
rails generate devise user

To configure views
rails generate devise:views

To install bootstrap
rails g bootstrap:install

To set a layout bootstrap
rails g bootstrap:layout application fixed

Remove the index file from
path/to/project/public/index.html

Application Structure

Controllers

We will need only to create one controller, which will handle the connection to LinkedIn. The controller will pull the data from the API and push it to our models.

rails g controller linkedin

Add the following line at the beginning of the linkedin controller

before_filter :authenticate_user!

We’ll add a few helper methods to handle the authentication and APIs queries.

First, and before diving into any methods, we should define the configuration hash. The hash holds all the information to negotiate a connecion with the LinkedIn APIs. The request_token_path element grants permissions to access the different parts of the APIs, such as r_fullprofile for a full profile or r_network for connections.

You could add more tokens to request_token_path element by concatenating them with a +, such as:
/uas/oauth/requestToken?scope=r_basicprofile+r_fullprofile+r_emailaddress+r_network

The full object, which we will add at the top of the LinkedIn controller, is below:

@@config = {
    :site => 'https://api.linkedin.com',
    :authorize_path => '/uas/oauth/authenticate',
    :request_token_path => '/uas/oauth/requestToken?scope=r_basicprofile+r_fullprofile',
    :access_token_path => '/uas/oauth/accessToken'
  }

The LinkedinOauthSetting object is used to store the access token, and access secret.

The purpose of generate_linkedin_oauth_url method is to generate the linkedin authentication uri, which asks the user for permissions. The method checks first if the object already exists for the current user. If not, it’ll create a linkedin client object and build the request_token object with the oauth_callback. This callback is the method that will be called after the user grants the access. Now, store the request_token.token and request_token.secret in the session, as they will be used to create the persisted access tokens. Finally, redirect the user to the authorize_url. If the user has been here before, it will redirect them to the /oauth_account action directly.

generate_linkedin_oauth_url

def generate_linkedin_oauth_url
    if LinkedinOauthSetting.find_by_user_id(current_user.id).nil?
      client = LinkedIn::Client.new('your-api-key', 'your-secret-key', @@config)
      request_token = client.request_token(:oauth_callback => "http://#{request.host}:#{request.port}/oauth_account")
      session[:rtoken] = request_token.token
      session[:rsecret] = request_token.secret
      redirect_to request_token.authorize_url
    else
      redirect_to "/oauth_account"
    end
  end

The authentication method, which is set as the callback in the request_token object, authenticates the user for the first time. If the user is already authenticated, it will redirect the user to home without doing anything.

oauth_account

def oauth_account
  client = LinkedIn::Client.new('your-api-key', 'your-secret-key', @@config)
  pin = params[:oauth_verifier]
  if pin
    atoken, asecret = client.authorize_from_request(session[:rtoken], session[:rsecret], pin)
    LinkedinOauthSetting.create!(:asecret => asecret, :atoken => atoken, :user_id => current_user.id)
  end
  redirect_to "/"
end

First, we create a linkedin client object with our keys (‘your-api-key’, ‘your-secret-key’) which you get after registering your application on the linked-in api. If the oauth_verifier params exists, which is passed by LinkedIn, the user has granted access to the application so we’ll authorize the client. When we authorize for the first time, we need to store and get the access token and secret to retrieve them later.

The get_client helper simply returns an authorized linkedin client object. Here, the linkedin_oauth_setting object is retrieved from database and authorizes the client object.

get_client

def get_client
  linkedin_oauth_setting = LinkedinOauthSetting.find_by_user_id(current_user.id)
  client = LinkedIn::Client.new('iv6uehul4g5m', 'wtMfG2MbFerSULTC', @@config)
  client.authorize_from_access(linkedin_oauth_setting.atoken, linkedin_oauth_setting.asecret)
  client
end

Here is the root action. It checks if this user already has a linkedin oauth setting. If so,it’ll redirect them to the linkedin_profile action.
index

def index
  unless LinkedinOauthSetting.find_by_user_id(current_user.id).nil?
    redirect_to "/linkedin_profile"
  end
end
view raw index.rb This Gist is brought to you using Simple Gist Embed.

Here we show simple html page of the data pulled from the linkedin APIs.
linkedin_profile

def linkedin_profile
  basic_profile = get_basic_profile
  full_profile = get_full_profile
  positions = get_positions
  educations = get_educations
end

Routes

Add the following to path/to/project/config/routes

resources :linkedin
match '/linkedin_profile'
match '/oauth_account' => "linkedin#oauth_account"
match '/linkedin_oauth_url' => 'linkedin#generate_linkedin_oauth_url'
root :to => 'linkedin#index'
view raw routes.rb This Gist is brought to you using Simple Gist Embed.

Views

We should make few changes before running the server.

Modify the layout view path/to/project/app/views/layouts/application.html.erb
by removing the following block 65-75, and then replacing the following lines 50-52 with

<li><%= link_to current_user.email, "/users/edit" %></li>
<li><%= link_to "Sign out", "/users/sign_out", :method => "DELETE" %></li>

Modify the index view [path/to/project/app/views/linkedin/index.html.erb]:

<h1>Linkedin Application</h1>
<a href="/linkedin_oauth_url" class="btn btn-primary btn-larg">Connect to LinkedIn</a>

Models

We should stop here to look at the LinkedIn APIs and what we are going to cover here. We will also learn the specification of the models we need to create in our application.

The LinkedIn developer site is the best place, this Quick Start Guide is comprehensive. The guide shows how to create the application on the LinkedIn, as well as how to get your API Key and Secret Key (which you should “never share with anyone”).

You’ll see a demonstration of how to connect with a ruby script without any gems which takes much more time than the gem.

The table on the Authentication page lists the permissions, which we’ll see how to use it later.

Here’s few of them:

PermissionScope
Your Profile Overviewr_basicprofile
Your Full Profiler_fullprofile
Your Email Addressr_emailaddress
Your Connectionsr_network

We’ll focus on the Profile Fields part of the APIs, because we can’t cover the whole API. I think you’ll get a good sense of how the gem works by writing this demo application.

Let’s focus first on the Basic Profile Fields. This will be the first part from the API to map to our rails application.

LinkedinOauthSetting

This is an important model that stores two important values for each user that allow access their account without asking for permission each time.

Run the following commands to generate the above model:

rails g model linkedin_oauth_setting atoken:string asecret:string user_id:integer
rake db:migrate

Remember always to check this line above each table “These fields require the r_basicprofile member permission” to use this permission params later.

Add the association to the linkedin_oauth_setting model:
path/to/project/app/models/linkedin_oauth_setting.rb

belongs_to :user

BasicProfile

Selecting few attributes to add to the model, and here’s a list of them:

FieldDescription
first-namethe member’s first name
last-namethe member’s last name
maiden-namethe member’s maiden name
formatted-namethe member’s name formatted based on language
headlinethe member’s headline (often “Job Title at Company”)
location:(name)Generic name of the location of the LinkedIn member, (ex: “San Francisco Bay Area”)
industrythe industry the LinkedIn member has indicated their profile belongs to (Industry Codes)
summaryA long-form text area where the member describes their professional profile
specialtiesA short-form text area where the member enumerates their specialties
picture-urlA URL to the profile picture, if the member has associated one with their profile and it is visible to the requestor
public-profile-urlA URL to the member’s public profile, if enabled

Now run the following command to create the basic_profiles table:

rails g model basic_profile first_name:string last_name:string maiden_name:string formatted_name:string headline:string location:string industry:string summary:string specialties:string picture_url:string public_profile_url:string user_id:integer

rake db:migrate

Add the association to the basic_profile model:
path/to/project/app/models/basic_profile.rb

belongs_to :user

FullProfile

Selecting few attributes to add to the model, you’ll notice that the full_profile table contains many other complex objects like (publications, patents, languages, skills, etc) We are going to build separate models for a few of them. Here’s a list of them:

FieldDescription
associationsA short-form text area enumerating the Associations a member has
honorsA short-form text area describing what Honors the member may have
interestsA short-form text area describing the member’s interests

Now run the following command to create the full_profiles table:

rails g model full_profile associations:string honors:string interests:string user_id:integer

rake db:migrate

Add the association to the full_profile model:
path/to/project/app/models/full_profile.rb

belongs_to :user
has_many :educations
has_many :positions

Position

Let’s add a few attributes to the model:

FieldDescription
titlethe job title held at the position, as indicated by the member
summarya summary of the member’s position
start-datea structured object with month and year fields indicating when the position began
end-datea structured object with month and year fields indicating when the position ended
is-currenta “true” or “false” value, depending on whether it is marked current
companythe company the member works for

Now run the following command to create the positions table:

rails g model position title:string summary:string start_date:date end_date:date is_current:boolean company:string full_profile_id:integer

rake db:migrate

Add the association to the position model:
path/to/project/app/models/position.rb

belongs_to :full_profile

Education

For the Education model we have:

FieldDescription
school-namethe name of the school, as indicated by the member
field-of-studythe field of study at the school, as indicated by the member
start-datea structured object a year field indicating when the education began
end-datea structured object with a year field indicating when the education ended
degreea string describing the degree, if any, received at this institution
activitiesa string describing activities the member was involved in while a student at this institution
notesa string describing other details on the member’s studies

Now run the following command to create the educations table:

rails g model education school_name:string field_of_study:string start_date:date end_date:date degree:string activities:string notes:string full_profile_id:integer

rake db:migrate

Add the association to the model:
path/to/project/app/models/education.rb

belongs_to :full_profile

User

Add the association to the model:
path/to/project/app/models/user.rb

has_one :basic_profile
has_one :full_profile
has_one :linkedin_oauth_setting
view raw user.rb This Gist is brought to you using Simple Gist Embed.

LinkedIn APIs Inquiries

Now we have in the linkedin controller the required helper methods for authentication. We have stored the required tokens for later access, so it is time to write some helper methods to inquiry the APIs. We will use are model objects for this portion.

Querying the APIs is very simple with the gem. After authorizing the client object, you call the profile method on the object and pass whatever fields you want to get, for example:
client.profile(:fields => [:first_name, :last_name, :maiden_name , :formatted_name ,:headline])

The following methods are used to pull the data from the APIs and push it into rails models. As you can see, we are creating the object with the retrieved json object from the APIs with a few customizations.

get_basic_profile

def get_basic_profile
  bprofile = BasicProfile.find_by_user_id(current_user.id)
  if bprofile.nil?
    client = get_client
    profile = client.profile(:fields => ["first-name", "last-name", "maiden-name", "formatted-name" ,:headline, :location, :industry, :summary, :specialties, "picture-url", "public-profile-url"])

    basic_profile = profile.to_hash
    basic_profile[:location] = basic_profile["location"]["name"]
    new_basic_profile = BasicProfile.new(basic_profile)
    new_basic_profile.user = current_user
    new_basic_profile.save
    new_basic_profile
  else
    bprofile
  end
end

get_full_profile

def get_full_profile
  fprofile = FullProfile.find_by_user_id(current_user.id)
  if fprofile.nil?
    client = get_client
    full_profile = client.profile(:fields => [:associations, :honors, :interests])
    full_profile = full_profile.to_hash
    new_full_profile = FullProfile.new(full_profile)
    new_full_profile.user = current_user
    new_full_profile.save
    new_full_profile
  else
    fprofile
  end
end

get_positions

def get_positions
  positions = Position.find_all_by_full_profile_id(current_user.full_profile.id)
  if positions.empty?
    client = get_client
    positions = client.profile(:fields => [:positions]).positions.all
    positions.each do |p|
      if p.is_current == "true"
        Position.create(
          title: p.title,
          summary: p.summary,
          start_date: Date.parse("1/#{p.start_date.month ? p.start_date.month : 1}/#{p.start_date.year}"),
          end_date: Date.parse("1/#{p.end_date.month ? p.end_date.month : 1}/#{p.end_date.year}"),
          is_current: p.is_current,
          company: p.company.name,
          full_profile_id: current_user.full_profile.id)
      else
        Position.create(
          title: p.title,
          summary: p.summary,
          start_date: Date.parse("1/#{p.start_date.month ? p.start_date.month : 1}/#{p.start_date.year}"),
          is_current: p.is_current,
          company: p.company.name,
          full_profile_id: current_user.full_profile.id)
      end
    end
    current_user.full_profile.positions
  else
    positions
  end
end

get_educations

def get_educations
  educations = Education.find_all_by_full_profile_id(current_user.full_profile.id)
  if educations.empty?
    client = get_client
    educations = client.profile(:fields => [:educations]).educations.all
    educations.each do |e|
      new_educations = Education.create(
        school_name: e.school_name,
        field_of_study: e.field_of_study,
        start_date: Date.parse("1/#{e.end_date.month ? p.end_date.month : 1}/#{e.end_date.year}"),
        end_date: Date.parse("1/#{e.end_date.month ? p.end_date.month : 1}/#{e.end_date.year}"),
        degree: e.degree,
        activities: e.activities,
        notes: e.notes,
        full_profile_id: current_user.full_profile.id)
    end
    current_user.full_profile.educations
  else
    educations
  end
end

There are a couple of examples in the gem repo on github, for example:

# update status for the authenticated user
client.update_status('is playing with the LinkedIn Ruby gem')

# clear status for the currently logged in user
client.clear_status

# get network updates for the authenticated user
client.network_updates

# get profile picture changes
client.network_updates(:type => 'PICT')

# view connections for the currently authenticated user
client.connections

Let’s the put it all together and build a simple view to display for our application to display the stored data.

Here’s the view path/to/project/linkedin/linkedin_profile.html.erb

<div class="row">
  <div class="span4">
    <div class="thumbnail">
      <img src="<%= @basic_profile.picture_url %>" style="float: left;margin: 5px;">
      <h3><%= @basic_profile.formatted_name %></h3>
      <h4><%= @basic_profile.headline %></h4>
      <br>
      <p><%= @basic_profile.summary %></p>
    </div>
  </div>
  <div class="span4">
    <div class="well">
      <h5>Interests</h5>
      <p><%= @full_profile.interests %></p>
      <br>
      <h5>Associations</h5>
      <p><%= @full_profile.associations %></p>
      <br>
      <h5>Honors</h5>
      <p><%= @full_profile.honors %></p>
      <br>
    </div>
  </div>
</div>

<div class="row" style="margin-top: 16px;">
  <div class="span10">
<h1>Positions</h1>
   <table class="table table-striped">
   <tr>
   <th>Title</th>
    <th>Summary</th>
   <th>Company Name</th>
   <th>Is Current</th>
   <th>Start Date</th>
   <th>End Date</th>
   </tr>
   <% @positions.each do |p| %>
   <tr>
   <th><%= p.title %></th>
   <th><%= p.summary %></th>
   <th><%= p.company %></th>
   <th><%= p.is_current %></th>
   <th><%= p.start_date %></th>
   <th><%= p.end_date %></th>
           </tr>
   <% end %>
</table>
  </div>
</div>

<div class="row" style="margin-top: 16px;">
  <div class="span10">
    <h1>Educations</h1>
    <table class="table table-striped">
      <tr>
   <th>School Name</th>
   <th>Field of Study</th>
   <th>Degree</th>
   <th>Activities</th>
   <th>Notes</th>
   <th>Start Date</th>
   <th>End Date</th>
      </tr>
      <% @educations.each do |e| %>
   <tr>
   <th><%= e.school_name %></th>
   <th><%= e.field_of_study %></th>
   <th><%= e.degree %></th>
   <th><%= e.activities %></th>
   <th><%= e.notes %></th>
   <th><%= e.start_date %></th>
   <th><%= e.end_date %></th>
   </tr>
      <% end %>
    </table>
  </div>
</div>

Wrapping up

Well, I hope this tutorial to be useful, and be a good introduction to using LinkedIn gem, and the APIs in general, if you would like to suggest the next gem to cover, we’ll go for it, and thanks for reading.

Series NavigationRuby Social Gems: Twitter >>