Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

58
Build a simple Twitter clone with Ruby Monday, September 20, 2010

Transcript of Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Page 1: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Build a simple Twitter clone with Ruby

Monday, September 20, 2010

Page 2: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Today’s lesson

Features of a simple micro-blogging site

Authentication with Facebook Graph API OAuth 2.0

Code walkthrough

http://github.com/sausheong/chirpy

Monday, September 20, 2010

Page 3: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Features

Monday, September 20, 2010

Page 4: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Authentication via Facebook OAuth 2.0

Users can post a text message called a chirp in his own chirp feed

Users can follow and unfollow any other user

Users’ feeds are added to their followers’ feeds

Users can reply to chirps or re-chirp

Monday, September 20, 2010

Page 5: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Monday, September 20, 2010

Page 6: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Monday, September 20, 2010

Page 7: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Monday, September 20, 2010

Page 8: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Facebook Graph API OAuth 2.0

Monday, September 20, 2010

Page 9: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Monday, September 20, 2010

Page 10: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Monday, September 20, 2010

Page 11: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Monday, September 20, 2010

Page 12: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Monday, September 20, 2010

Page 13: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Monday, September 20, 2010

Page 14: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Code walkthrough

Monday, September 20, 2010

Page 15: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Model

Monday, September 20, 2010

Page 16: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

User Session

Chirp

1 n

1

n

n

1

1 1

followers

follows

has

has

Monday, September 20, 2010

Page 17: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

class Session include DataMapper::Resource property :id, Serial property :uuid, String, :length => 255 belongs_to :userend

class Friendship include DataMapper::Resource belongs_to :source, 'User', :key => true belongs_to :target, 'User', :key => trueend

Monday, September 20, 2010

Page 18: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

class User include DataMapper::Resource

property :id, Serial property :name, String, :length => 255 property :photo_url, String property :facebook_id, String property :chirpy_id, String has n, :chirps has 1, :session has n, :follower_relations, 'Friendship', :child_key => [ :source_id ] has n, :follows_relations, 'Friendship', :child_key => [ :target_id ] has n, :followers, self, :through => :follower_relations, :via => :target has n, :follows, self, :through => :follows_relations, :via => :source def chirp_feed feed = follows.collect {|follow| follow.chirps}.flatten + chirps feed.sort { |chirp1, chirp2| chirp2.created_at <=> chirp1.created_at} endend

Monday, September 20, 2010

Page 19: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

class Chirp include DataMapper::Resource

property :id, Serial property :text, String, :length => 140 property :created_at, Time

belongs_to :user

before :save do if starts_with?('follow ') process_follow else process end end

Monday, September 20, 2010

Page 20: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

def process urls = self.text.scan(URL_REGEXP) urls.each { |url| tiny_url = open("http://tinyurl.com/api-create.php?url=#{url[0]}") {|s| s.read} self.text.sub!(url[0], "<a href='#{tiny_url}'>#{tiny_url}</a>") }

ats = self.text.scan(AT_REGEXP) ats.each { |at| self.text.sub!(at, "<a href='/#{at[2,at.length]}'>#{at}</a>") } end

def process_follow user = User.first :chirpy_id => self.text.split[1] user.followers << self.user user.save throw :halt # don't save this chirp end

Monday, September 20, 2010

Page 21: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

def starts_with?(prefix) prefix = prefix.to_s self.text[0, prefix.length] == prefix endend

URL_REGEXP = Regexp.new('\b ((https?|telnet|gopher|file|wais|ftp) : [\w/#~:.?+=&%@!\-] +?) (?=[.:?\-] * (?: [^\w/#~:.?+=&%@!\-]| $ ))', Regexp::EXTENDED)AT_REGEXP = Regexp.new('\s@[\w.@_-]+', Regexp::EXTENDED)

Monday, September 20, 2010

Page 22: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Controller

Monday, September 20, 2010

Page 23: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

%w(haml sinatra rack-flash json rest_client active_support dm-core).each { |gem| require gem}%w(config models helpers).each {|feature| require feature}

set :sessions, trueset :show_exceptions, falseuse Rack::Flash

get '/' do redirect '/home' if session[:id] redirect '/login'end

get '/login' do haml :login, :layout => false end

Monday, September 20, 2010

Page 24: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Facebook authentication

Monday, September 20, 2010

Page 25: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

(1) Redirect user to /oauth/authorize with: - client id - redirect URI

Monday, September 20, 2010

Page 26: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

(1) Redirect user to /oauth/authorize with: - client id - redirect URI

(2) User authorizes (or not)

Monday, September 20, 2010

Page 27: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

(1) Redirect user to /oauth/authorize with: - client id - redirect URI

(2) User authorizes (or not)

(3a) Facebook calls redirect URI with: - code

Monday, September 20, 2010

Page 28: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

(1) Redirect user to /oauth/authorize with: - client id - redirect URI

(2) User authorizes (or not)

(3b) Facebook calls redirect URI with: - error reason

(3a) Facebook calls redirect URI with: - code

Monday, September 20, 2010

Page 29: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

(1) Redirect user to /oauth/authorize with: - client id - redirect URI

(4) Go to /oauth/access_token with: - client id - redirect URI - client secret - code

(2) User authorizes (or not)

(3b) Facebook calls redirect URI with: - error reason

(3a) Facebook calls redirect URI with: - code

Monday, September 20, 2010

Page 30: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

(1) Redirect user to /oauth/authorize with: - client id - redirect URI

(4) Go to /oauth/access_token with: - client id - redirect URI - client secret - code

(5) Facebook responds with: - access token - expiry

(2) User authorizes (or not)

(3b) Facebook calls redirect URI with: - error reason

(3a) Facebook calls redirect URI with: - code

Monday, September 20, 2010

Page 31: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

(1) Redirect user to /oauth/authorize with: - client id - redirect URI

(4) Go to /oauth/access_token with: - client id - redirect URI - client secret - code

(5) Facebook responds with: - access token - expiry

(6) Call Graph API with access token

(2) User authorizes (or not)

(3b) Facebook calls redirect URI with: - error reason

(3a) Facebook calls redirect URI with: - code

Monday, September 20, 2010

Page 32: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

get '/login/facebook' do facebook_oauth_authorizeend

def facebook_oauth_authorize redirect "https://graph.facebook.com/oauth/authorize?client_id=" + FACEBOOK_OAUTH_CLIENT_ID + "&redirect_uri=" + "http://#{env['HTTP_HOST']}/#{FACEBOOK_OAUTH_REDIRECT}" end

helper.rb

chirpy.rb

1

Monday, September 20, 2010

Page 33: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

get "/#{FACEBOOK_OAUTH_REDIRECT}" do redirect_with_message '/login', params[:error_reason] if params[:error_reason] facebook_get_access_token(params[:code])end

chirpy.rb

Monday, September 20, 2010

Page 34: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

get "/#{FACEBOOK_OAUTH_REDIRECT}" do redirect_with_message '/login', params[:error_reason] if params[:error_reason] facebook_get_access_token(params[:code])end

chirpy.rb

3a

Monday, September 20, 2010

Page 35: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

get "/#{FACEBOOK_OAUTH_REDIRECT}" do redirect_with_message '/login', params[:error_reason] if params[:error_reason] facebook_get_access_token(params[:code])end

chirpy.rb

3a

3b

Monday, September 20, 2010

Page 36: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

def facebook_get_access_token(code) oauth_url = "https://graph.facebook.com/oauth/access_token" oauth_url << "?client_id=#{FACEBOOK_OAUTH_CLIENT_ID}" oauth_url << "&redirect_uri=" + URI.escape("http://#{env['HTTP_HOST']}/#{FACEBOOK_OAUTH_REDIRECT}") oauth_url << "&client_secret=#{FACEBOOK_OAUTH_CLIENT_SECRET}" oauth_url << "&code=#{URI.escape(code)}"

response = RestClient.get oauth_url oauth = {} response.split("&").each do |p| ps = p.split("="); oauth[ps[0]] = ps[1] end user_object = get_user_from_facebook_with URI.escape(oauth['access_token']) user = User.first_or_create :facebook_id => user_object['id'] user.name = user_object['name'] user.photo_url = "http://graph.facebook.com/#{user_object['id']}/picture" user.chirpy_id = user_object['name'].gsub " ","-" user.session = Session.new :uuid => oauth['access_token'] user.save session[:id] = oauth['access_token'] session[:user] = user.id redirect '/' end

helper.rb

Monday, September 20, 2010

Page 37: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

def facebook_get_access_token(code) oauth_url = "https://graph.facebook.com/oauth/access_token" oauth_url << "?client_id=#{FACEBOOK_OAUTH_CLIENT_ID}" oauth_url << "&redirect_uri=" + URI.escape("http://#{env['HTTP_HOST']}/#{FACEBOOK_OAUTH_REDIRECT}") oauth_url << "&client_secret=#{FACEBOOK_OAUTH_CLIENT_SECRET}" oauth_url << "&code=#{URI.escape(code)}"

response = RestClient.get oauth_url oauth = {} response.split("&").each do |p| ps = p.split("="); oauth[ps[0]] = ps[1] end user_object = get_user_from_facebook_with URI.escape(oauth['access_token']) user = User.first_or_create :facebook_id => user_object['id'] user.name = user_object['name'] user.photo_url = "http://graph.facebook.com/#{user_object['id']}/picture" user.chirpy_id = user_object['name'].gsub " ","-" user.session = Session.new :uuid => oauth['access_token'] user.save session[:id] = oauth['access_token'] session[:user] = user.id redirect '/' end

helper.rb

4

Monday, September 20, 2010

Page 38: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

def facebook_get_access_token(code) oauth_url = "https://graph.facebook.com/oauth/access_token" oauth_url << "?client_id=#{FACEBOOK_OAUTH_CLIENT_ID}" oauth_url << "&redirect_uri=" + URI.escape("http://#{env['HTTP_HOST']}/#{FACEBOOK_OAUTH_REDIRECT}") oauth_url << "&client_secret=#{FACEBOOK_OAUTH_CLIENT_SECRET}" oauth_url << "&code=#{URI.escape(code)}"

response = RestClient.get oauth_url oauth = {} response.split("&").each do |p| ps = p.split("="); oauth[ps[0]] = ps[1] end user_object = get_user_from_facebook_with URI.escape(oauth['access_token']) user = User.first_or_create :facebook_id => user_object['id'] user.name = user_object['name'] user.photo_url = "http://graph.facebook.com/#{user_object['id']}/picture" user.chirpy_id = user_object['name'].gsub " ","-" user.session = Session.new :uuid => oauth['access_token'] user.save session[:id] = oauth['access_token'] session[:user] = user.id redirect '/' end

helper.rb

4 5

Monday, September 20, 2010

Page 39: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

6

def facebook_get_access_token(code) oauth_url = "https://graph.facebook.com/oauth/access_token" oauth_url << "?client_id=#{FACEBOOK_OAUTH_CLIENT_ID}" oauth_url << "&redirect_uri=" + URI.escape("http://#{env['HTTP_HOST']}/#{FACEBOOK_OAUTH_REDIRECT}") oauth_url << "&client_secret=#{FACEBOOK_OAUTH_CLIENT_SECRET}" oauth_url << "&code=#{URI.escape(code)}"

response = RestClient.get oauth_url oauth = {} response.split("&").each do |p| ps = p.split("="); oauth[ps[0]] = ps[1] end user_object = get_user_from_facebook_with URI.escape(oauth['access_token']) user = User.first_or_create :facebook_id => user_object['id'] user.name = user_object['name'] user.photo_url = "http://graph.facebook.com/#{user_object['id']}/picture" user.chirpy_id = user_object['name'].gsub " ","-" user.session = Session.new :uuid => oauth['access_token'] user.save session[:id] = oauth['access_token'] session[:user] = user.id redirect '/' end

helper.rb

4 5

Monday, September 20, 2010

Page 40: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

def get_user_from_facebook_with(token) JSON.parse RestClient.get "https://graph.facebook.com/me?access_token=#{token}" end

helper.rb

get '/logout' do @user = User.get session[:user] @user.session.destroy session.clear redirect '/'end

chirpy.rb

6

Monday, September 20, 2010

Page 41: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Authorization

Monday, September 20, 2010

Page 42: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

def require_login if session[:id].nil? redirect_with_message('/login', 'Please login first') elsif Session.first(:uuid => session[:id]).nil? session[:id] = nil redirect_with_message('/login', 'Session has expired, please log in again') end end

helper.rb

Monday, September 20, 2010

Page 43: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Chirp feed

Monday, September 20, 2010

Page 44: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

get '/home' do require_login @myself = @user = User.get(session[:user]) @chirps = @user.chirp_feed haml :homeend

get '/user/:id' do require_login @myself = User.get session[:user] @user = User.first :chirpy_id => params[:id] @chirps = @user.chirps haml :homeend

chirpy.rb

Monday, September 20, 2010

Page 45: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Adding chirps

Monday, September 20, 2010

Page 46: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

post '/update' do require_login @user = User.get session[:user] @user.chirps.create :text => params[:chirp], :created_at => Time.now redirect "/home"end

chirpy.rb

Monday, September 20, 2010

Page 47: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Following users

Monday, September 20, 2010

Page 48: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

get '/follow/:id' do require_login @myself = User.get session[:user] @user = User.first :chirpy_id => params[:id] unless @myself == @user or @myself.follows.include? @user @myself.follows << @user @myself.save end redirect '/'end

get '/unfollow/:id' do require_login @myself = User.get session[:user] @user = User.first :chirpy_id => params[:id] unless @myself == @user if @myself.follows.include? @user follows = @myself.follows_relations.first :source => @user follows.destroy end end redirect '/'end

chirpy.rb

Monday, September 20, 2010

Page 49: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

View

Monday, September 20, 2010

Page 50: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Sinatra does not have partial templates

Implement as helper

def snippet(page, options={}) haml page, options.merge!(:layout => false) end

Snippets

Monday, September 20, 2010

Page 51: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

=snippet :'snippets/top'

.span-16.append-1 =snippet :'snippets/update_box' =snippet :'snippets/follow' if @myself %h2 Home =snippet :'snippets/chirps'

.span-7.last =snippet :'snippets/info_box'

home.haml

Monday, September 20, 2010

Page 52: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

top.haml.span-18 %a{:href => '/'} %h2.banner Chirpy.span-6.last %a{:href => '/'} #{@myself.name} | %a{:href => '/'} home | %a{:href => '/logout'} logout

Monday, September 20, 2010

Page 53: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

update_box.haml=snippet :'/snippets/text_limiter_js'%h2 What are you doing?%form{:method => 'post', :action => '/update'} %textarea.update.span-15#update{:name => 'chirp', :rows => 2, :onKeyDown => "text_limiter($('#update'), $('#counter'))"} .span-6 %span#counter 140 characters left .prepend-12 %input#button{:type => 'submit', :value => 'update'}

Monday, September 20, 2010

Page 54: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

text_limiter_js.haml:javascript function text_limiter(field,counter_field) { limit = 139; if (field.val().length > limit) field.val(field.val().substring(0, limit)); else counter_field.text(limit - field.val().length); }

Monday, September 20, 2010

Page 55: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

follow.haml.span-15.last - if @myself == @user &nbsp; - elsif @myself.follows.include? @user You are following this user | %a{:href => "/unfollow/#{@user.chirpy_id}"} stop following this user - else %a{:href => "/follow/#{@user.chirpy_id}"} follow this user

Monday, September 20, 2010

Page 56: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

chirps.haml.chirps [email protected] do |chirp| %hr .span-2 %img.span-2{:src => "#{chirp.user.photo_url}"} .span-12 %a{:href => "/user/#{chirp.user.chirpy_id}"} =chirp.user.name &nbsp; =chirp.text .span-2.last %a{:href =>"#", :onclick => "$('#update').attr('value','@#{chirp.user.chirpy_id} ');$('#update').focus();"} reply %br %a{:href =>"#", :onclick => "$('#update').attr('value','RT @#{chirp.user.chirpy_id}: #{chirp.text} ');$('#update').focus();"} rechirp

.span-15.last %em.quiet =time_ago_in_words(chirp.created_at) %hr.space

Monday, September 20, 2010

Page 57: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

info_box.haml.span-2 %a %img.span-2{:src => "#{@user.photo_url}"}.span-3.last .span-3 %em #{@user.name} .span-3 #{@user.follows.count} following .span-3 #{@user.followers.count} followers .span-3 #{@user.chirps.count} chirps

%hr.space

.span-5.last %h3 Follows [email protected] do |follow| %a{:href => "/user/#{follow.chirpy_id}"} %img.smallpic{:src => "#{follow.photo_url}", :width => '24px', :alt => "#{follow.name}"} %hr.space %h3 Followers [email protected] do |follower| %a{:href => "/user/#{follower.chirpy_id}"} %img.smallpic{:src => "#{follower.photo_url}", :width => '24px', :alt => "#{follower.name}"}

Monday, September 20, 2010

Page 58: Ruby Course - lesson 8 - Build a simple Twitter clone with Ruby

Questions?

Monday, September 20, 2010