John Wang » Learning https://www.johntwang.com Tue, 09 Nov 2010 08:01:33 +0000 en hourly 1 https://wordpress.org/?v=3.0.2 GData on Rails on Google AppEnginehttps://www.johntwang.com/blog/2010/06/10/gdata-on-rails-on-google-appengine/ https://www.johntwang.com/blog/2010/06/10/gdata-on-rails-on-google-appengine/#comments Thu, 10 Jun 2010 18:45:59 +0000 john https://www.johntwang.com/?p=857 There’s a great article by Jeff Scudder on Retrieving Authenticated Google Data Feeds with Google App Engine on the Google App Engine Articles site. Jeff’s tutorial utilizes the GDdata (Google Data) Python client library. The aim of this article is to replicate Jeff’s Tutorial using GData on Rails on Google App Engine running on JRuby on Rails.

Requirements

  • Ruby 1.8.6 patch level 114+
  • RubyGems 1.3.1+
  • Rails 2.3.5
  • GData Gem

Introduction

I’m sure your mind is positively buzzing with ideas for how to use Google App Engine, and a few of you might be Google Data AtomPub APIs. Quite of few of Google’s products expose a Google Data API, (a few interesting examples are YouTube, Google Calendar, and Blogger–you can find a complete list here) and these APIs can be used to read and edit the user-specific data they expose.

In this article we’ll use the Google Documents List Data API to walk through the process of requesting access from and retrieving data for a particular user. We’ll use Google App Engine’s webapp framework to generate the application pages, and the Users API to authenticate users with Google Accounts.

Google’s AuthSub APIs

Some Google Data services require authorization from your users to read data, and all Google Data services require their authorization before your app can write to these services on the user’s behalf. Google uses AuthSub to enable users to authorize your app to access specific services.

Using AuthSub, users type their password into a secure page at google.com, then are redirected back to your app. Your app receives a token allowing it to access the requested service until the user revokes the token through the Account Management page.

In this article, we’ll walk through the process of setting up the login link for the user, obtaining a session token to use for multiple requests to Google Data services, and storing the token in the datastore so that it can be reused for returning users.

Using the GData on Rails client library

Google offers a Data on Rails client library that simplifies token management and requesting data from specific Google Data APIs. In this article we’ll use this library, but of course you’re welcome to use whatever works best for your application. Download the Gdata on Rails client library.

To use this library with your Google App Engine application, simply install the library gem and require it as you usually would. There’s nothing more to it than that!

Installing the Google Data Ruby Utility Library

To obtain the library, you can either download the library source directly from project hosting or install the gem:

sudo gem install gdata

config/enviroment.rb

config.gem 'gdata', :lib => 'gdata'

config.ru
require 'appengine-rack'
require 'image'
require 'image-upload'
require 'user'
require 'gdata'
AppEngine::Rack.configure_app(
    :application => 'jruby-gdata',
    :precompilation_enabled => true,
    :sessions_enabled => true,
    :version => "1")

AppEngine::Rack.app.resource_files.exclude :rails_excludes
ENV['RAILS_ENV'] = AppEngine::Rack.environment

deferred_dispatcher = AppEngine::Rack::DeferredDispatcher.new(
    :require => File.expand_path('../config/environment', __FILE__),
    :dispatch => 'ActionController::Dispatcher')

map '/contacts' do
  use AppEngine::Rack::LoginRequired
  run deferred_dispatcher
end

map '/' do
  run deferred_dispatcher
end
Gemfile
# Critical default settings:
disable_system_gems
disable_rubygems
bundle_path '.gems/bundler_gems'

# List gems to bundle here:
gem 'rails_dm_datastore'
gem 'rails', "2.3.5"
gem 'appengine-apis'
gem 'gdata'

Step 1: Generating the Token Request Link (AuthSub)

Applications use an API called AuthSub to obtain a user’s permission for accessing protected Google Data feeds. The process is fairly simple. To request access from a user to a protected feed, your app will redirect the user to a secure page on google.com where the user can sign in to grant or deny access. Once doing so, the user is then redirected back to your app with the newly-granted token stored in the URL.

Your application needs to specify two things when using AuthSub: the common base URL for the feeds you want to access, and the redirect URL for your app, where the user will be sent after authorizing your application.

To generate the token request URL, we’ll use the GData::Auth::AuthSub module included in the Google Data client library. This module contains a method, get_url, which automatically generates the correct URL given the base feed URL(scope) and your website’s return address (next_url). In the code snippet below, we use this method to generate a URL requesting access to a user’s Google Document List feed.

In our routes.rb file, we will define a URL mapping to create a separate URL for each step. Here’s an example:

    map.connect 'step1', :controller => 'step1', :action => 'get'
    map.connect 'step2', :controller => 'step2', :action => 'get'
    map.connect 'step3', :controller => 'step3', :action => 'get'

To illustrate this first step of using AuthSub in the app, we will create a step1_countroller.rb that looks something like this:

class Step1Controller < ApplicationController

  def get
    scope = 'https://docs.google.com/feeds/'
    next_url = url_for :controller => self.controller_name, :action => self.action_name
    secure = false  # set secure = true for signed AuthSub requests
    session = true
    @authsub_link = GData::Auth::AuthSub.get_url(next_url, scope, secure, session)
  end
end

Step 2: Retrieving and Updating a Token

Once we’ve generated an authorization request URL for a particular Google Data service, we’ll need a way to use the token returned to our app to access the feed in question. Now, we need to retrieve the initial token returned to us for the Google Documents List API, and upgrade that token to a permanent session token. Remember that we told the service to redirect the user to the URL ‘https://jruby-gdata.appspot.com/step1′. Let’s extend our simple example above to do a few things. We’ll call this new version step2.

Let’s write the functionality that will handle the return request from the Google Data service the user signed in to. The Google Data service will request a URL that will look something like this:

https://jruby-gdata.appspot.com/?auth_sub_scopes=http%3A%2F%2Fdocs.google.com%2Ffeeds%2F&token=CKC5y...Mg

Which is just our return URL appended with the initial authorization token for the service which grants our app access for our user. The code below first takes this URL and extracts the service and the token. Then, it requests an upgrade for the token for the document list service.

We use two new methods to achieve this. First, we try to obtain the single use AuthSub token by examining the current page’s URL. The params[:token] function handles token extraction for us. To upgrade this initial token to a session token, we use client.auth_handler.upgrade().

require 'appengine-apis/users'

class Step2Controller < ApplicationController
  def get
    @client = GData::Client::DocList.new
    next_url = url_for :controller => self.controller_name, :action => self.action_name
    secure = false  # set secure = true for signed AuthSub requests
    sess = true
    @authsub_link = @client.authsub_url(next_url, secure, sess)

    if params[:token].nil? and session[:token].nil?
      @url = "nothing"
    elsif params[:token] and session[:token].nil?
      @client.authsub_token = params[:token] # extract the single-use token from the URL query params
      session[:token] = @client.auth_handler.upgrade()
    end

    @url1, @url_linktext = if AppEngine::Users.current_user
        [AppEngine::Users.create_logout_url(request.url), 'Sign Out']
      else
        [AppEngine::Users.create_login_url(request.url), 'Sign In']
      end

    @client.authsub_token = session[:token] if session[:token]

  end
end

After we upgrade the initial token using the client.auth_handler.upgrade() method. Below, we will take you through the steps to use this token and fetch your user’s feed in your application.

Step 3: Using a session token and fetching a data feed.

Now that we have obtained and stored the session token, we can use the AuthSub session token to retrieve the user’s document list feed with our application. The final step is to get the user feed from Google Docs and display it on our site!

Lets add a new method to our app to request the feed and handle a token required message. We will call this method fetch_feed and add it to the Fetcher request handler class. In this example, the app uses client.Get to try to read data from the feed.

Some Google Data feeds require authorization before they can be read. If our app had previously saved an AuthSub session token for the current user and the desired feed URL, then the token will be found automatically by the client object and used in the request. If we did not have a stored token for the combination of the current user and the desired feed, then we will attempt to fetch the feed anyway. If we receive a “token required” message from the server, then we will ask the user to authorize this app which will give our app a new AuthSub token.

Here is the code for the fetch_feed method:

def fetch_feed (client, feed_url)

  if feed_url.nil?
    'No feed_url was specified for the app to fetch.<br/>'
  else
      begin
        @feed = @client.get(feed_url).to_xml
        # If fetching fails
        rescue GData::Client::AuthorizationError
          @error = 'authorization error'
      end
  end
end

Now that we have a method to fetch the target feed, we can modify the Fetcher class’ get method to call this method after we upgrade and store the AuthSub token.

Our app also needs to know the URL which should be fetched, so we add a URL parameter to the incoming request to indicate which feed should be fetched. The below code for the get method adds the ability to find out which URL the app should fetch and fetches the desired feed.

require 'appengine-apis/users'
require 'appengine-apis/urlfetch'

class Step3Controller < ApplicationController

  def get
    @client = GData::Client::DocList.new
    next_url = url_for :controller => self.controller_name, :action => self.action_name
    secure = false  # set secure = true for signed AuthSub requests
    sess = true
    @authsub_link = @client.authsub_url(next_url, secure, sess)

    if params[:token].nil? and session[:token].nil?
      @url = "nothing"
    elsif params[:token] and session[:token].nil?
      @client.authsub_token = params[:token] # extract the single-use token from the URL query params
      session[:token] = @client.auth_handler.upgrade()
    end

    @url1, @url_linktext = if AppEngine::Users.current_user
        [AppEngine::Users.create_logout_url(request.url), 'Sign Out']
      else
        [AppEngine::Users.create_login_url(request.url), 'Sign In']
      end

    @client.authsub_token = session[:token] if session[:token]

    feed_url = params[:feed_url]

    if feed_url.nil?
      @sample_url = next_url + '?feed_url=http%3A%2F%2Fdocs.google.com%2Ffeeds%2Fdocuments%2Fprivate%2Ffull'

    end

    fetch_feed(@client,feed_url)

  end
end

In the above, we request the feed by calling fetch_feed(@client, feed_url).

You can see the final program at work by visiting: https://jruby-gdata.appspot.com/. Also, view the complete source code, where we put all of this together at the Google App Engine sample code project on Google Code Hosting.

The AuthSub session tokens are long lived, but they can be revoked by the user or by your application. At some point, a session token stored in your data store may become revoked so your application should handle cleanup of tokens which can no longer be used. The status of a token can be tested by querying the token info URL. You can read more about AuthSub token management in the AuthSub documentation. This feature is left as an exercise to the reader, have fun :)

Conclusion

Using the Google Data on Rails client library, you can easily manage your user’s Google Data feeds in your own Google App Engine application.

The Google Data on Rails client library includes support for almost all of the Google Data services. For further information, you can read the getting started guide for the library, visit the project to browse the source, and even ask questions on the ‘s Google group.

As always, for questions about Google App Engine, read Google’s online documentation and visit our google group.

Appendix: ClientLogin

This example uses AuthSub to authorize the app to act on the user’s behalf, but the Google Data APIs support other authorization mechanisms. In some cases, you might want to temporarily use ClientLogin while developing your application. If you are going to use ClientLogin, the Ruby library has a client class for each of the APIs and a Base class:

client = GData::Client::DocList.new
client.clientlogin('user@gmail.com', 'pa$$word')

client_login_handler = GData::Auth::ClientLogin.new('writely', :account_type => 'HOSTED')
token = client_login_handler.get_token('user@example.com', 'pa$$word', 'google-RailsArticleSample-v1')
client = GData::Client::Base.new(:auth_handler => client_login_handler)

You may receive a CAPTCHA challenge when requesting a ClientLogin token which you will need to handle in your app before you can receive a ClientLogin token. For this and a few other reasons, I don’t recommend using ClientLogin in Google App Engine, but the above is how you could use it while developing your app.

Resources

Share the Love:Digg del.icio.us Facebook StumbleUpon Design Float Reddit DZone FriendFeed Twitter email Print

]]>
https://www.johntwang.com/blog/2010/06/10/gdata-on-rails-on-google-appengine/feed/ 0
JSON with Ruby on Rails on Google AppEnginehttps://www.johntwang.com/blog/2010/04/23/json-with-ruby-on-rails-on-google-appengine/ https://www.johntwang.com/blog/2010/04/23/json-with-ruby-on-rails-on-google-appengine/#comments Sat, 24 Apr 2010 00:45:01 +0000 john https://www.johntwang.com/?p=854 Getting Started

Here’s a list of what you’ll need to get started with the demo:

  • a Google AppEngine account
  • Ruby 1.8.6
  • Rails 2.3.5
  • Ruby on Rails AppEngine Gem

Instructions

The first steps that you’ll want to follow are from John Woodell’s Gist on Rails 2.3.5 on App Engine with DataMapper.

As you’ll notice, the last couple of steps generate a RESTful resource called Contact and also it’s associated DataMapper Model.

Adding REST

Now in order to add JSON to the mix, we’ll need a couple more things:

We only have to make a few very small changes to the demo app that you got from the Gist to get JSON integrated. They’re listed below, and also at this Gist.

Gemfile changes: adding the json-jruby and dm-serializer to the gems for bundler
# Critical default settings:
disable_system_gems
disable_rubygems
bundle_path '.gems/bundler_gems'

# List gems to bundle here:
gem 'rails_dm_datastore'
gem 'rails', "2.3.5"
gem "json-jruby"
gem "dm-serializer"
config.ru changes: adding require ‘dm-serializer’ and ‘json’
require 'appengine-rack'
require 'cgi'
require 'json'
require 'dm-core'
require 'dm-serializer'

AppEngine::Rack.configure_app(
    :application => 'iphone-json',
    :precompilation_enabled => true,
    :sessions_enabled => true,
    :version => "1")

AppEngine::Rack.app.resource_files.exclude :rails_excludes
ENV['RAILS_ENV'] = AppEngine::Rack.environment

deferred_dispatcher = AppEngine::Rack::DeferredDispatcher.new(
     :require => File.expand_path('../config/environment', __FILE__),
     :dispatch => 'ActionController::Dispatcher', :isolate => true)

map '/admin' do
  use AppEngine::Rack::AdminRequired
  run deferred_dispatcher
end

map '/user' do
  use AppEngine::Rack::LoginRequired
  run deferred_dispatcher
end

map '/' do
  run deferred_dispatcher
end
and finally, the contacts_controller.rb needs the corresponding format.json render statements.
class ContactsController < ApplicationController
  # GET /contacts
  # GET /contacts.xml
  # GET /contacts.json
  def index
    @contacts = Contact.all

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :x ml => @contacts }
      format.json { render :json => @contacts }
    end
  end

  # GET /contacts/1
  # GET /contacts/1.xml
  # GET /contacts/1.json
  def show
    @contact = Contact.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :x ml => @contact }
      format.json { render :json => @contact }
    end
  end

  # GET /contacts/new
  # GET /contacts/new.xml
  # GET /contacts/new.json
  def new
    @contact = Contact.new

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :x ml => @contact }
      format.json { render :json => @contact }
    end
  end

  # GET /contacts/1/edit
  def edit
    @contact = Contact.find(params[:id])
  end

  # POST /contacts
  # POST /contacts.xml
  # POST /contacts.json
  def create
    @contact = Contact.new(params[:contact])

    respond_to do |format|
      if @contact.save
        flash[:notice] = 'Contact was successfully created.'
        format.html { redirect_to(@contact) }
        format.xml  { render :x ml => @contact, :status => :created, :location => @contact }
        format.json { render :json => @contact, :status => :created, :location => @contact }
      else
        format.html { render :action => "new" }
        format.xml  { render :x ml => @contact.errors, :status => :unprocessable_entity }
        format.json { render :json => @contact.errors, :status => :unprocessable_entity }
      end
    end
  end

  # PUT /contacts/1
  # PUT /contacts/1.xml
  # PUT /contacts/1.json
  def update
    @contact = Contact.find(params[:id])

    respond_to do |format|
      if @contact.update_attributes(params[:contact])
        flash[:notice] = 'Contact was successfully updated.'
        format.html { redirect_to(@contact) }
        format.xml  { head :o k }
        format.json { head :o k }
      else
        format.html { render :action => "edit" }
        format.xml  { render :x ml => @contact.errors, :status => :unprocessable_entity }
        format.json { render :json => @contact.errors, :status => :unprocessable_entity }
      end
    end
  end

  # DELETE /contacts/1
  # DELETE /contacts/1.xml
  def destroy
    @contact = Contact.find(params[:id])
    @contact.destroy

    respond_to do |format|
      format.html { redirect_to(contacts_url) }
      format.xml  { head :o k }
      format.json { head :o k }
    end
  end
end

Final Notes

  1. You may have noticed that by default you get RESTful XML when you generated the resource. It doesn’t work without the needed dm-serializer. Remember, we’re using DataMapper here and not ActiveResource. If you try, you’ll get this beautiful error.
    SEVERE: [1272067889120000] RuntimeError (Not all elements respond to to_xml)
  2. If you inspect your DataMapper model, you’ll notice that every field is a required field. This is key for when you’re testing your POST method.
  3. The last thing that is currently being looked into, is that the POST requires you to pass in the Class in lowercase for it to properly work. I’m not sure if this is a case-sensitive issue, or just purely needing to change the param to be “:Contact” instead of “:contact” in the controller.

Source Code

  • The fully working demo can be found running on Google AppEngine at: iphone-json.appspot.com
  • source is available on Github.
  • And there is an accompanying iPhone REST client also available on Github. (Android client is currently in development.)

Alternatively, if you just want to quickly test your newly created JSON RESTful resource, you can use the REST Client for Firefox or REST Client for Google Chrome.

Share the Love:Digg del.icio.us Facebook StumbleUpon Design Float Reddit DZone FriendFeed Twitter email Print

]]>
https://www.johntwang.com/blog/2010/04/23/json-with-ruby-on-rails-on-google-appengine/feed/ 2
Lessons Learned: Planning for a Successful Talkhttps://www.johntwang.com/blog/2010/03/25/lessons-learned-planning-for-a-successful-talk/ https://www.johntwang.com/blog/2010/03/25/lessons-learned-planning-for-a-successful-talk/#comments Fri, 26 Mar 2010 07:43:11 +0000 john https://www.johntwang.com/?p=852 On March 20th, I had the privilege of being a speaker at Unconferenz. I also ended up hosting a couple of other talks throughout the day as well. I presented on the topic of Designing iPhone/iPad Apps and 3rd Party SDKs. The slides are available here.

I learned quite a few lessons as a speaker about giving a better talk.

Internet Access

I didn’t run into any problems on Internet Access at Unconferenz, but I did make sure to ask the host if there would be Internet access provided. I also brought along a personal USB 3G Modem as a backup.

Power

Power actually could have been a problem. I currently use a MacBook Pro which about 2-3hrs of battery life. However, something to consider is the computer going to sleep mode while you’re in the middle of a presentation. Also, the power outlet may not be somewhere remotely close to how long your power cable reaches. For this, I carry both my power adapter and also a 10ft extension cable. You may also want to be a kind person and bring a multi-outlet mini surge protector so not to take the only one or 2 power outlet available.

Backup Plan

Always have a backup plan. The one thing that I did not have at all was a Mini-DisplayPort to VGA adapter for my laptop to connect to the projector. That was a bad thing. Luckily, someone at the conference did have one and I was able to borrow it. There was also a computer in the room that I could have used as another fallback as long as I had my presentation on a USB Key or uploaded somewhere like DropBox on the web.

Contact Info

I ran out of business cards. That was a bad thing. Always bring lots of those. I was lucky enough that some people had iPhones with the Bump app to swap contact info and also some who were willing to either give me theirs so I could contact them or just quickly add me to either Twitter or Gmail email address.

Double-Check Everything, Maybe Even Triple-Check

I had uploaded my slides ahead of time in case anyone wanted to download them. Unfortunately for me, I misspelled the file name and the link was dead. I quickly corrected the link and made sure to tweet it back out. That was very embarrassing to say the least. Make sure your stuff is set before you announce it.

Resources

Share the Love:Digg del.icio.us Facebook StumbleUpon Design Float Reddit DZone FriendFeed Twitter email Print

]]>
https://www.johntwang.com/blog/2010/03/25/lessons-learned-planning-for-a-successful-talk/feed/ 0
On 37 Signals’ REWORKhttps://www.johntwang.com/blog/2010/03/17/on-37-signals-rework/ https://www.johntwang.com/blog/2010/03/17/on-37-signals-rework/#comments Wed, 17 Mar 2010 21:49:28 +0000 john https://www.johntwang.com/?p=849 The folks over at 37 Signals recently released their new book called Rework. It’s a business book on how they run their company and business.

REWORK is a brilliant book. Check out the book’s website. The website contains videos and sample images of the contents.

Why You Should Read REWORK

The book is dirty cheap. It’s under $10 on the Amazon Kindle and just over $12 in hardcover on Amazon. It’s also an extremely short book coming in at just under 300 pages. I finished reading it less than 3 hours. All of the principles detailed in the book are brilliantly worded to the point that they’ll feel like common sense. The principles are also nice and short so that you can actually remember and apply them. Jason and David don’t throw a bunch of mumbo jumbo and useless buzz words.

This is a quick but very powerful read and definitely recommended for everyone. It’s not a book just for business owners, CEOs or the cubicle workers. REWORK is a book that everyone at every level should read.

What’s Next?

Head on over to the book’s website and check out the PDF excerpt for a sample or just go straight to Amazon and order the Kindle version or Hardcover version.

Share the Love:Digg del.icio.us Facebook StumbleUpon Design Float Reddit DZone FriendFeed Twitter email Print

]]>
https://www.johntwang.com/blog/2010/03/17/on-37-signals-rework/feed/ 1