🤩 Access a heap of free courses with a SitePoint account

YouTube on Rails


This article is out of date, as YouTube has released version 3 of their API. If you need a tutorial on using version 3, check out this post instead.


YouTube. The third most visited resource in the world. We have all visited it and, probably, uploaded videos to it. But how can we, as Rails developers, fetch information about YouTube videos? How can we display them on our website? Are there any libraries that can help us with that? Let’s investigate, shall we?

In this article, I will show you how to create a web application that allows users to add and watch videos from YouTube. Upon adding, the video’s info will be fetched automatically via the YouTube API. This app will also display these added videos using the typical YouTube player with the help of the YouTube IFrame API. Lastly, there are some potential problems and gotchas that you might face.

The source code is available on GitHub.

The working demo can be found on Heroku.

Preparing a Demo Application

For this article I will be using Rails 3.2.17, but you can implement practically the same solution with Rails 4.

Start by creating a new Rails app without the default testing suite:

$ rails new yt_videos -T

First of all, we should grab the gems that will be used in this demo. We are going to use youtube_it, a convenient gem written by Kyle J. Ginavan, to work with the YouTube API. For styling, let’s use our old friend Twitter Bootstrap and, specifically, the bootstrap-sass gem. Go ahead and add these two lines into your Gemfile:


gem 'bootstrap-sass', '~>'
gem 'youtube_it', '~> 2.4.0'

Then run

$ bundle install

Now add

//= require bootstrap

to the application.js and

@import "bootstrap";

to the application.css.scss to include the Bootstrap styles and scripts. Of course, in a real application you would choose only the required components.

We should think about what our Video model will look like. Of course, it should contain a link to the video that the user has added. The YouTube API only supplies the unique video identifier (which we will learn to extract, shortly), so we’ll need to grab it. Also, we’ll want to store some info about the video. Here is the list of all attributes for our ActiveRecord model:

  • id – integer, primary key, indexed, unique
  • link – string
  • uid – string. It is also a good idea to add an index here to enforce uniqueness. For this demo, we don’t want users to add the same videos multiple times
  • title – string. This will contain the video’s title extracted from YouTube
  • author – string. Author’s name extracted from YouTube
  • duration – string. We will store the video’s duration, formatted like so: “00:00:00”
  • likes – integer. Likes count for a video
  • dislikes – integer. Dislikes count for a video.

That will be enough for the purposes of this demo, but keep in mind that the YouTube API allows you to fetch a lot more information.

Run this command to create a model and a corresponding migration:

$ rails g model Video link:string title:string author:string duration:string likes:integer dislikes:integer

Now set up the routes:


resources :videos, only: [:index, :new, :create]
root to: 'videos#index'

We will not need the edit, update, show and destroy actions for the purposes of this demo. Also, do not forget to remove the public/index.html file if you are working with Rails 3.

The last thing to do is add a block for rendering flash messages into the layout:



<div class="container">
  <% flash.each do |key, value| %>
    <div class="alert alert-<%= key %>">
      <button type="button" class="close" data-dismiss="alert">&times;</button>
      <%= value %>
  <% end %>


Adding Videos

When a user visits our website, the first thing he probably should see is the “Add video” button.


<div class="jumbotron">
  <div class="container">
    <h1>Share your videos with the world!</h1>
    <p>Click the button below to share your video from YouTube.</p>
      <%= link_to 'Add video now!', new_video_path, class: 'btn btn-primary btn-lg' %>

Thanks to Bootstrap, this block will look pretty nice. The next step is to implement the new action.

But let’s stop for a second and check the models/video.rb file. If you are using Rails 3, all the attributes (except for id, updated_at and created_at) were made accessible by default. The only thing we need from the user is the link to the video he wants to add. Other attributes will be populated automatically, so let’s make the appropriate modifications:



attr_accessible :link


For Rails 4, you should only permit the link attribute in the controller:


Okay, now we can move on. Add these two methods into the controller:



def new
  @video = Video.new

def create
  @video = Video.new(params[:video])
  if @video.save
    flash[:success] = 'Video added!'
    redirect_to root_url
    render 'new'


Nothing fancy going on here.

The view:


<div class="container">
  <h1>New video</h1>

  <%= form_for @video do |f| %>
    <%= render 'shared/errors', object: @video %>

    <div class="form-group">
      <%= f.label :link %>
      <%= f.text_field :link, class: 'form-control', required: true %>
      <span class="help-block">A link to the video on YouTube.</span>

    <%= f.submit class: 'btn btn-default' %>
  <% end %>

As you can see, we only ask the user to enter a link to a video on YouTube. Here, also render a shared partial to display any errors, which looks like:


<% if object.errors.any? %>
  <div class="panel panel-danger">
    <div class="panel-heading">
      <h3 class="panel-title">The following errors were found while submitting the form:</h3>

    <div class="panel-body">
        <% object.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
<% end %>

It is time for the fun part – extracting information about the video specified by the user. We are going to do this inside a callback before creating a record.


before_create -> do
  # Our code here

To fetch information from the YouTube API, we will need the video’s unique identifier. Actually, this identifier is already present in the link that the user provides. The trick here is that these links can be specified in multiple formats, for example:

  • http://www.youtube.com/watch?v=0zM3nApSvMg&feature=feedrecgrecindex
  • http://www.youtube.com/user/IngridMichaelsonVEVO#p/a/u/1/QdK8U-VIH_o
  • http://www.youtube.com/v/0zM3nApSvMg?fs=1&hl=en_US&rel=0
  • http://www.youtube.com/watch?v=0zM3nApSvMg#t=0m10s
  • http://www.youtube.com/embed/0zM3nApSvMg?rel=0
  • http://www.youtube.com/watch?v=0zM3nApSvMg
  • http://youtu.be/0zM3nApSvMg

All these URLs point to the same video with the id 0zM3nApSvMg (you can read more about it on StackOverflow).

It is also worth mentioning that a video’s uID contains 11 symbols. To fetch the uID from the link, we will use the following RegExp:


Before we do that, though, let’s make sure we have a valid link:


YT_LINK_FORMAT = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/i

validates :link, presence: true, format: YT_LINK_FORMAT


Ok, fetch the uID inside the callback:



before_create -> do
  uid = link.match(YT_LINK_FORMAT)
  self.uid = uid[2] if uid && uid[2]

  if self.uid.to_s.length != 11
    self.errors.add(:link, 'is invalid.')
  elsif Video.where(uid: self.uid).any?
    self.errors.add(:link, 'is not unique.')

Here, there are some validations for the uid, specifically for checking its length and uniqueness. If everything is okay, call a method to fetch information about the video:




def get_additional_info
    client = YouTubeIt::OAuth2Client.new(dev_key: 'Your_YT_developer_key')
    video = client.video_by(uid)
    self.title = video.title
    self.duration = parse_duration(video.duration)
    self.author = video.author.name
    self.likes = video.rating.likes
    self.dislikes = video.rating.dislikes
    self.title = '' ; self.duration = '00:00:00' ; self.author = '' ; self.likes = 0 ; self.dislikes = 0

def parse_duration(d)
  hr = (d / 3600).floor
  min = ((d - (hr * 3600)) / 60).floor
  sec = (d - (hr * 3600) - (min * 60)).floor

  hr = '0' + hr.to_s if hr.to_i < 10
  min = '0' + min.to_s if min.to_i < 10
  sec = '0' + sec.to_s if sec.to_i < 10

  hr.to_s + ':' + min.to_s + ':' + sec.to_s

This is where youtube_it comes into play. First, a client variable is being created – it will be used to issue queries. You might be wondering “What is a YouTube developer key?” This key is used to issue queries against the public YouTube data. Please note that some actions require user authorization, which you can read more about here.

To get your developer key, register a new app at https://code.google.com/apis/console. Open its settings, go to “APIs & auth”, then “Credentials”. Create a new public key for browser applications, which is your developer key.

After initializing a client, fetch the video using video_by, getting the information about it. Note that the video’s duration is presented in seconds, so we have to implement a parse_duration method to format it the way we want.

At this point our app allows users to add their videos. It also fetches some info and provides validation for the user input. Nice, isn’t it?

Displaying the Videos

OK, we have the videos, now to display them. Add the following to your controller:


def index
  @videos = Video.order('created_at DESC')

On to the view:



<% if @videos.any? %>
  <div class="container">
    <h1>Latest videos</h1>

    <div id="player-wrapper"></div>

    <% @videos.in_groups_of(3) do |group| %>
      <div class="row">
        <% group.each do |video| %>
          <% if video %>
            <div class="col-md-4">
              <div class="yt_video thumbnail">
                <%= image_tag "https://img.youtube.com/vi/#{video.uid}/mqdefault.jpg", alt: video.title,
                              class: 'yt_preview img-rounded', :"data-uid" => video.uid %>

                <div class="caption">
                  <h5><%= video.title %></h5>
                  <p>Author: <b><%= video.author %></b></p>
                  <p>Duration: <b><%= video.duration %></b></p>
                    <span class="glyphicon glyphicon glyphicon-thumbs-up"></span> <%= video.likes %>
                    <span class="glyphicon glyphicon glyphicon-thumbs-down"></span> <%= video.dislikes %>
          <% end %>
        <% end %>
    <% end %>
<% end %>

The #player-wrapper is an empty block where the YouTube player will be shown. The in_groups_of method groups our records by 3 and to displays them in rows. Please note, if there is not enough elements to form a group, Rails replaces each missing element with nil, so we have to add a if video condition.

Another important thing to mention is the video preview image. To get a preview image for a video, use one of the following links:

  • https://img.youtube.com/vi/mqdefault.jpg – 320×180 image with no black stripes above and below the picture;
  • https://img.youtube.com/vi/hqdefault.jpg – 480×360 image with black stripes above and below the picture;
  • https://img.youtube.com/vi/<1,2,3>.jpg – 120×90 image with different scenes from the video with black stripes above and below the picture.

Also, when rendering video preview images we are storing video’s uID using HTML5 data-* attribute. This attribute will be used shortly.

To display the actual videos on our website, the YouTube IFrame API will be used. It creates the YouTube player with certain parameters, allowing the user to change the video, pause and stop it, etc. You can read more about it here. Hook up the required javascript files like this:



  <script src="https://www.google.com/jsapi"></script>
  <script src="https://www.youtube.com/iframe_api"></script>


Now, let’s write some CoffeeScript code in a new yt_player.coffee file:


jQuery ->
  # Initially the player is not loaded
  window.ytPlayerLoaded = false

  makeVideoPlayer = (video) ->
    if !window.ytPlayerLoaded
      player_wrapper = $('#player-wrapper')
      player_wrapper.append('<div id="ytPlayer"><p>Loading player...</p></div>')

      window.ytplayer = new YT.Player('ytPlayer', {
        width: '100%'
        height: player_wrapper.width() / 1.777777777
        videoId: video
        playerVars: {
          wmode: 'opaque'
          autoplay: 0
          modestbranding: 1
        events: {
          'onReady': -> window.ytPlayerLoaded = true
          'onError': (errorCode) -> alert("We are sorry, but the following error occured: " + errorCode)

First of all, we initialize the ytPlayerLoaded boolean variable which checks whether the player has been loaded. After that, create a makeVideoPlayer function which takes one argument – the video’s uID – and creates a YouTube player or changes the video being played. If the player is not yet loaded, we append a new #ytPlayer block to the #player-wrapper which is eventually replaced by the player. With all that in place, finally create the YouTube player object assigning it to the ytplayer (we will use it to call API functions).

Let’s stop for a second a talk a bit about the arguments that are being passed when creating this object. ytPlayer is the DOM id of the block that should be replaced with the player. The second argument is a javascript object containing settings for the player:

  • width – width of the player. Our site has responsive layout so we set it to 100%
  • height – height of the player. We have to calculate it based on the width. The typical resolution for YouTube is 16:9 which means 1.7(7) ratio
  • videoId – uID of the video to load
  • wmode – this parameter controls layering and transparency of Flash object (learn more). If we do not set it to opaque, the player will overlay modal windows (for example the ones created with jQuery UI) and look terrible
  • autoplay – set to 1, the video will play on load. In this demo, we do not want this to happen
  • modestbranding – if this is set to 1, the YouTube logo will not be shown in the control bar, but still will be visible in the upper right corner when the video is paused
  • events – here we specify two events to handle. When the player has loaded (onReady), set ytPlayerLoaded to true. If an error occurrs while loading the player or video (for example, this video was deleted), alert the user.

If the player has already been loaded, we use loadVideoById function to change the video being played and then pause it using pauseVideo (for demonstration purposes).

Okay, our function is ready. We want to load the player and display the latest video as soon as user opens our website. How do we achieve this? We are going to use a special function presented by Google API:



_run = ->
  # Runs as soon as Google API is loaded

google.setOnLoadCallback _run


This google.setOnLoadCallback _run means that the _run function should be called as soon as the API has finished loading. If we do not use this callback and try to load the player as soon as DOM is ready, an error occurs stating that YT.Player is not a function – this is because API has not loaded yet.

The last thing to do is to bind a click event handler to the video previews:



$('.yt_preview').click -> makeVideoPlayer $(this).data('uid')


And that is all! Now our users can add videos and watch them on our website.

Actually, there is one small problem left. We specified the player’s width in percent, but the player’s height is specified in pixels. That means that if a user tries to resize the window, the player will become narrower, but it will preserve the same width – this is bad. To solve this, we can bind a resize event handler to the window like this:



$(window).on 'resize', ->
  player = $('#ytPlayer')
  player.height(player.width() / 1.777777777) if player.size() > 0


Now the player’s height will be rescaled correctly – go on and give it a try. You can also delay firing this event until the user stops resizing, otherwise it will be fired constantly. To do this, use the BindWithDelay library written by Brian Grinstead. It is super simple:



$(window).bindWithDelay('resize', ->
  player = $('#ytPlayer')
  player.height(player.width() / 1.777777777) if player.size() > 0
, 500)


With this structure we are delaying firing this event by 500ms.


This brings us to the end of the article. We talked about the youtube_it gem which can be used to work with the YouTube API and about the YouTube IFrame API. I hope that information provided here was useful. Do not forget to share your opinion about this article in the comments. See you again soon!

JavaScript: Novice to Ninja, 2nd Edition