ruby course - lesson 8 - build a simple twitter clone with ruby

Post on 23-Jun-2015

70 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Build a simple Twitter clone with Ruby

Monday, September 20, 2010

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

Features

Monday, September 20, 2010

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

Monday, September 20, 2010

Monday, September 20, 2010

Monday, September 20, 2010

Facebook Graph API OAuth 2.0

Monday, September 20, 2010

Monday, September 20, 2010

Monday, September 20, 2010

Monday, September 20, 2010

Monday, September 20, 2010

Monday, September 20, 2010

Code walkthrough

Monday, September 20, 2010

Model

Monday, September 20, 2010

User Session

Chirp

1 n

1

n

n

1

1 1

followers

follows

has

has

Monday, September 20, 2010

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

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

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

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

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

Controller

Monday, September 20, 2010

%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

Facebook authentication

Monday, September 20, 2010

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

Monday, September 20, 2010

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

(2) User authorizes (or not)

Monday, September 20, 2010

(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

(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

(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

(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

(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

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

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

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

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

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

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

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

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

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

Authorization

Monday, September 20, 2010

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

Chirp feed

Monday, September 20, 2010

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

Adding chirps

Monday, September 20, 2010

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

Following users

Monday, September 20, 2010

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

View

Monday, September 20, 2010

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

=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

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

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

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

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

chirps.haml.chirps -@chirps.each 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

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 -@user.follows.each do |follow| %a{:href => "/user/#{follow.chirpy_id}"} %img.smallpic{:src => "#{follow.photo_url}", :width => '24px', :alt => "#{follow.name}"} %hr.space %h3 Followers -@user.followers.each do |follower| %a{:href => "/user/#{follower.chirpy_id}"} %img.smallpic{:src => "#{follower.photo_url}", :width => '24px', :alt => "#{follower.name}"}

Monday, September 20, 2010

Questions?

Monday, September 20, 2010

top related