September 19, 2013

Ruby on Rails and Couchbase-Model for a Social Application!

Also using HAML, SASS, Bootstrap, And Twitter OmniAuth...

Before we start – Here’s the GitHub code for this App…

We recently had our 3rd Couchbase San Francisco conference which, by the way,  was a great success and a fantastic day / night.  At the conference, I gave an 80-minute session based on the very sample application we are going to be building today.  The purpose of my talk was to show how we can model JSON documents within Couchbase and utilise the full Map/Reduce capabilities to build an application end-to-end.

This blog assumes knowledge of Rails and some use of the Twitter Developer Portal.  If you haven’t used Couchbase or the Couchbase-Model gem before, don’t worry, I will guide you through that part, but it is worth reading up on Couchbase-Model first!

The overall idea behind this app is that Users can post their own Vine videos, to be voted on by the public to see who has the Funniest Vine videos globally.  Users must be authenticated before submitting a Vine, and we need to keep track of which Vine belongs to which user by means of referential ID’s. i.e. each Vine should have a User_ID within it’s JSON document, so we know who it belongs to.

So without further hesitation, let's dive in and see how we can build a gamified application atop-of Couchbase using Ruby and Rails.  The whole idea of having a social application with a leaderboard and a competitive element is one that has been a very common use-case, but not necessarily one that has been well documented (especially for us in the Ruby community!)  Now then, enough chit-chat, let’s go!

Prerequisites:

  • Couchbase Server 2.0+
  • Ruby 1.9.3+
  • Rails 3+

Instead of posting every individual gem for our Rails app here, simply click here to view my Gemfile to see which Ruby Gems we need to bundle for our application.

1 note to make here is my choice of Ruby server.  I am using Puma.  This isn’t necessarily a benefit in this small use-case, more a personal habit.  Puma is a threaded, concurrent webserver for Ruby that starts on port :9292 by default.  You can use whichever web server you feel comfortable with, and simply start your app with ‘rails s’ or ‘rails server’.

Now, before we get started with our code, let’s go ahead and create a bucket in our Couchbase instance called ‘rvine’.  It’s not necessary to worry about sizing this bucket, so depending on how much RAM you have allocated to your cluster, just give it 200mb or so.

So, let’s generate a new Rails app.  We’re going to append our usual command with the ‘-O’ flag.  This is to skip active_record includes. (We can’t use active_record with Couchbase, but that is why we have the Couchbase-Model gem!)

Rails new rvine –O

Now then,  let’s make sure you’ve copied the Gemfile from the repo linked above.  The importance of this is to make sure we’ve got both Couchbase and Couchbase-Model bundled.  Couchbase-Model is an abstraction over the Couchbase gem allowing Active-Record like design patterns.  It makes it super easy for us to get up and running with Rails & Couchbase.  Make sure you click on the link above and read through the gem details to find out more.

When we know we have both gems in our Gemfile, run ‘bundle install.’  Once we’ve done that, we setup our rails app with Couchbase as a backend.  From your rails directory in Terminal, run the command:

Rails generate couchbase:config

This will generate a couchbase.yml file in our config directory that links up our app with our database backend.   Go ahead and edit that file, and enter our bucket credentials.  Your config file should reflect this:

common: &common
  hostname: localhost
  port: 8091
  username:
  password:
  pool: default

development:
  <<: *common
  bucket: rvine

test:
  <<: *common
  bucket: rvine

# set these environment variables on your production server
production:
  hostname: <%= ENV['COUCHBASE_HOST'] %>
  port: <%= ENV['COUCHBASE_PORT'] %>
  username: <%= ENV['COUCHBASE_USERNAME'] %>
  password: <%= ENV['COUCHBASE_PASSWORD'] %>
  pool: <%= ENV['COUCHBASE_POOL'] %>
  bucket: <%= ENV['COUCHBASE_BUCKET'] %>

 

Ok, so now that that’s done, we should have a Rails barebone project, that’s hooked up to Couchbase as it’s backend data store.  Seem simple? That’s because Couchbase-Model makes our life 1000x easier when it comes to data modeling!

Now, the next thing we need to do is setup our User & Authentication classes.

Let’s create our User model first.  Create the file /app/models/user.rb

class User < Couchbase::Model

  attribute :name
  attribute :twit_username
  attribute :avatar

  def self.find_or_create_from_auth_hash(hash)
    user = User.find_by_id(hash[:uid])
    unless user
      user = User.create!(:id => hash[:uid],
                          :name => hash[:info][:name],
                          :twit_username => hash[:info][:nickname],
                          :avatar => hash[:info][:image])
    end
    user
  end
end

 

The attributes in this document are the fields we wish to include in our JSON document.  We are collecting the user’s Name, Twitter Username & Avatar.  We need to give a unique key to each User in our database.  In this instance, we are doing so by collecting the User’s twitter UID and creating a hash from that to generate a key for our User document.  It’s important to remember that the key of every document in our database must be unique.  Also, worth noting is the class we have created. ‘find_or_create_from_auth_hash’  This class is doing exactly what it says on the tin! If a user exists; auth them. If not, create them from the Twitter details we receive.

You will hopefully have noticed that we have chosen to use Twitter-Omniauth for this, to save us a hell of a lot of time instead writing an Auth class from scratch!  The first thing to do here, is to head over to Twitter Dev and grab some application keys!  If you have never done this before, click here and sign in with your Twitter account.  Create a new application called ‘rvine’ and once you’ve done that, you will be presented with 2 application keys.

Create a config file in config/initializers/omniauth.rb.  Once you have created this file, open it up and drop in the following code:

Rails.application.config.middleware.use OmniAuth::Builder do
    provider :twitter, "CONSUMER_KEY", "CONSUMER_SECRET"
end

 

This should now mean we have Omniauth setup with our Twitter application keys, and almost ready to go.  The next thing we need to do is create a route in our Rails application to handle the Authentication process.  So, let’s open up our config/routes.rb file, and enter the following:

match '/auth/twitter/callback', :to => 'sessions#create'

 

Now, we’re telling Rails to send our Twitter Auth’d callback to our sessions controller…. What sessions controller?   We need to create that now. 

Go ahead and create file app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def create
    user = User.find_or_create_from_auth_hash(auth_hash)
    session[:user_id] = user.id
    flash[:success] = "Welcome to the club!"
    redirect_to dashboard_path
  end

  protected
  def auth_hash
    request.env['omniauth.auth']
  end
end

 

As you can see from this code, our ‘Create’ method is calling the class we defined in our User.rb to initialize a session when a User clicks to authenticate from the frontend, or create the User if they don’t already exist.  Once authenticated, we are redirecting the User to their Dashboard. (Which we will create soon!)

Next up, let’s update our application_controller.rb to include a helper classes for our Authentication – including a helper method to define a Current_User and a helper method to ensure a User is Signed in.

class ApplicationController < ActionController::Base
  protect_from_forgery

  def authenticate!
    if signed_in?
      return true
    else
      flash[:error] = "You aren't authorized to access this page"
      redirect_to(root_path)
    end
  end

  def current_user
    @current_user ||= User.find_by_id(session[:user_id])
  end
  helper_method :current_user

  def signed_in?
    !!current_user
  end
  helper_method :signed_in?
end

 

Next up, we need to create the Dashboard for our Users to be redirected to upon authorisation.  For this, let’s use a Rails Generate command.

rails generate controller dashboards

 

Now, let’s open up the file app/controllers/dashboards_controller.rb and enter the following:

class DashboardsController < ApplicationController

  def show
  end

end

 

Let’s open up our frontend view of the Dashboard, and edit that. In my case, it’s going to be a HAML file.  Regardless of whether you choose ERB or HAML, the file is going to look pretty much the same!  In app/views/dashboards - just create the file show.haml or show.erb.

%section#welcome
  .page-header
    - if signed_in?
      %h1
        Hello, #{current_user.name}
      %p
        %img{:src => current_user.avatar, :alt => "Avatar"}
      %p= link_to("All vines", "/vines")
    - else
      %h1
        Hello, stranger
      %p
        = link_to("Sign in with your twitter account", "/auth/twitter")
    %p
      &nbsp;

 

Now we’ve got our dashboard in place, we need to setup some more Routing, then we can go ahead and test things!  Open up config/routes.rb and add the following:

resource :dashboard

 

Once you’ve done that, save, run your app, and Authenticate as a user, and you should see something like the following:

Great! We can now see that our Authentication works, and our Users have a Dashboard.  If we open up our Couchbase console, we should also see that a User has been created inside our Rvine bucket!

Now we’ve got our Users and Authentication in place, let’s get onto the main part of the application: The Vine Videos!  To get started here, we’re going to need to create a model for Vines.  Now, unfortunately, Couchbase-Model does not yet support the rails Generator on this action.  So we need to manually create  /app/models/vine.rb

Once we’ve done that, let’s fill in the following:

require 'open-uri'
require 'uri'
require 'nokogiri'

class Vine < Couchbase::Model
  after_save :extract_video_url

  belongs_to :user

  attribute :title
  attribute :vine_url
  attribute :video_url

  #Voting API
  attribute :score, :default => 1

  validates_presence_of :title, :vine_url

  private

  def extract_video_url
    doc = Nokogiri(open(vine_url).read)
    self.video_url = doc.css("source").first["src"]
    save_without_callbacks
  end

end

 

As we can see from the code above, we are including open-uri, uri, and nokogiri.  This is because Vine does not have a public API, but we need to get those videos somehow!   So, with the help of those libraries we’ve written a cheeky script to scrape the source of a Vine video and grab the exact mp4 URI when a User enters the Vine URL.

The class extract_video_url is called using the method after_save.  From the code we can see Nokogiri is opening the URL entered by our User on posting a Vine.  It then searches the Vine page’s source to find the line declaring the Vine’s actual mp4 URI.

Other than that, we can see that each Vine belongs to a User, has attributes for the Title, Vine URL (Entered by the user) and Video URL (Actual mp4 URI).  We also have a Score attribute. (The most important attribute.) Also note that we are setting each video to start with a score of 1.

Now, let’s generate a controller & views for our Vine model. Run:

rails generate scaffold vines

 

You may get a conflict error at this point saying the model already exists. Don’t worry, just say ‘N’ to not overwrite it.  You may also see an error Couchbase not found.  Again, don’t worry about this, as I stated above Couchbase-Model isn’t compatible with the Rails Model generator.

If all went to plan, you should now have a vines_controller.rb and a whole views folder for vines with the frontend HAML or ERB files.  Go ahead and open up vines_controller.rb and make sure it reflects the file here.

In our controller file, you may have noticed a method called ‘upvote’.  This is the voting mechanism for our Vine videos.  The finish the implementation of this voting system, and to actually give the Vine videos a place to live, open up the file app/views/vines/show.haml(or .erb)

Make sure it relfects the file found here...

Before our voting system will work completely, we need to add the following to our routes.rb file:

  resources :vines do
    member do
      put :upvote
    end
  end

 

Ok, so now our Vine videos can be displayed, and the voting mechanism is in place!  The next thing we need to do is setup the main page for the app – The Leaderboard!  Our leaderboard, although being the main feature of the app, is incredibly simple.  Open up the file  app/views/vines/index.haml (or .erb)  and ensure it matches the code here.

Now, if this were any ordinary relational Rails app, we should in theory have a leaderboard already created, listing each Vine video in our database.  BUT this isn’t the case here.

In Couchbase, we need to create our leaderboard using Views in Couchbase and utilising the Map/Reduce technique before we get a leaderboard that actually works properly!  So, let’s do that!  Before we proceed here, if you haven’t used Couchbase Views prior to this, I recommend you read these docs, to give you a good bit of background knowledge on what Couchbase Views are, how we use them and to ease your way into using them in this Rails application

For our application, we need to create a View in Couchbase, that outputs each vine, with it’s Score and Title.  They also need to be ordered, descending, by Score so the Vine video with the highest score is naturally at the top of the Leaderboard.

IF you have used Couchbase Views before, you may have created them from within the Admin console itself.  Views can be created from within the Admin UI, from any of our SDK clients and by means of the REST API.  In this case, Couchbase-Model has a unique, and rather brilliant way of allowing us to generate Views for our application. Have a quick read of these docs to see how it can be done, and how we’re about to do it.

In Terminal, simply run:

rails generate couchbase:view vine all

 

Now, in your  app/models directory, you should have a new sub-directory named vine, with a subdirectory named all.  This directory contains 2 .js files – our map.js and reduce.js.  For now, all we’re interested in the map.js file.  Go ahead and open it up, and enter the following:

function(doc, meta) {
  if (doc.type == "vine" && doc.title) {
    emit(doc.score, doc.title);
  }
}

 

As we can see from this Map function; we are wrapping the entire thing in an IF statement.  This is a best practice within Couchbase to check for attributes before trying to emit rows into our Index.  In this case, our IF statement is ensuring only docs with the type == “vine” and ensuring the vine has a title.  The emit function is then creating a row in our index using the document Score as the indexed key.  We are also outputting the document Title as the output value.

The reason we are outputting the Score as the Indexed key, is that we can take advantage of the Unicode Sorting that Couchbase automatically applies to this field.  In our case, we need to ensure our Score’s are descending, to push the highest scoring Vine to the top of the Leaderboard.  This map function will work fine on it’s own, and if you ran the application now, you will have a list of Vine videos, but they would be in the wrong order!

It’s time to apply another feature of Couchbase to polish off our Leaderboard and ensure it works as it should.  It’s time to Query our View to create our final product.   Once again, Couchbase-Model allows us to do this straight from our Rails code.  Open up your model file  vine.rb  and add the following line just above the ‘private’ declaration:

 view :all, :limit => 10, :descending => true

 

Not only is this code necessary to add query params to our View, but it must be added to our Vine.rb file with or without query parameters to ensure our application knows which View(s) it has to use in our application.  In this case, we can see, not only are we defining which view our application is to use, but we have also added the query parameters: 

 :limit => 10, :descending => true

 

By doing so, we are limiting the View’s output to 10 results and we are ensuring the Score is in descending order. Without much care to styling, we should have something that looks like this:

That’s it!  If you populate your database with a few Vine videos, you should see you have a fully working Rate my Vine application.  Throughout this article we have seen how we can model data with the Couchbase Rails gem Couchbase-Model, we have seen how we can use Views in Couchbase to create interactive applications, and we have seen how we can Query on those Views to get specific subsets of data for use in our application.

I realise that not everything inside the app is in this article (styling etc.) but that is unimportant.  I hope I have covered the main points (Couchbase-Model, Document Modelling, Views and Queries) well enough so that you feel you could start your own Rails app using the Couchbase-Model gem.

If you are a London native, make sure you join our Couchbase London Meetup as over the coming weeks we will be hosting events detailing Document Modelling, Couchbase Mobile and many other topics.

If you have read this far, I can only congratulate you! I know this has been a LONG article!  If you have any questions, as always, I am happy to answer them.  Hit me up on Twitter at @Rbin and ping me any questions on there!

Robin Johnson
Developer Advocate, Europe.

Comments