Skip to content

Commit

Permalink
mtweet
Browse files Browse the repository at this point in the history
  • Loading branch information
steveyen committed Jan 6, 2011
1 parent 61f221c commit d828016
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 58 deletions.
20 changes: 13 additions & 7 deletions README.md
Original file line number Original file line Diff line number Diff line change
@@ -1,21 +1,20 @@
Retwis-RB mtweet
========= =========

An example Twitter application using the Membase NoSQL database.
An example Twitter application using the Redis key-value database.

Daniel Lucraft (dan@fluentradical.com)


Requirements Requirements
------------ ------------


* Ruby * Ruby
* Sinatra: sudo gem install sinatra * Sinatra: sudo gem install sinatra
* Redis: http://code.google.com/p/redis/ * Memcache-client: sudo gem install memcache-client
* Membase: http://membase.org


Starting Application Starting Application
-------------------- --------------------


Make sure the redis server is running. Make sure the membase server is running (at 127.0.0.1:11211).

Run: Run:


ruby app.rb ruby app.rb
Expand All @@ -24,3 +23,10 @@ License
------- -------


MIT MIT

Thanks
------

Daniel Lucraft (dan@fluentradical.com) for the original Retwis-rb
at http://danlucraft.com/blog/tag/retwis-rb

11 changes: 3 additions & 8 deletions app.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@
require 'rubygems' require 'rubygems'
require 'sinatra' require 'sinatra'
require 'erb' require 'erb'
require 'rubyredis' require 'store'

require 'domain' require 'domain'
require 'login-signup' require 'login-signup'


set :sessions, true set :sessions, true


def redis def mb
$redis ||= RedisClient.new(:timeout => nil) $mb ||= Store.new(['127.0.0.1:11211'])
end

before do
keys = redis.keys("*")
end end


get '/' do get '/' do
Expand Down
65 changes: 28 additions & 37 deletions domain.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class Timeline
def self.page(page) def self.page(page)
from = (page-1)*10 from = (page-1)*10
to = (page)*10 to = (page)*10
post_ids = redis.list_range("timeline", from, to) post_ids = mb.list_range("timeline", from, to)
post_ids.map {|post_id| Post.new(post_id)} post_ids.map {|post_id| Post.new(post_id)}
end end
end end
Expand All @@ -27,42 +27,42 @@ def #{name}
end end
def _#{name} def _#{name}
redis.get("#{klass}:id:" + id.to_s + ":#{name}") mb.get("#{klass}:id:" + id.to_s + ":#{name}")
end end
def #{name}=(val) def #{name}=(val)
redis.set("#{klass}:id:" + id.to_s + ":#{name}", val) mb.set("#{klass}:id:" + id.to_s + ":#{name}", val)
end end
RUBY RUBY
end end
end end


class User < Model class User < Model
def self.find_by_username(username) def self.find_by_username(username)
if id = redis.get("user:username:#{username}") if id = mb.get("user:username:#{username}")
User.new(id) User.new(id)
end end
end end


def self.find_by_id(id) def self.find_by_id(id)
if redis.key?("user:id:#{id}:username") if mb.exists?("user:id:#{id}:username")
User.new(id) User.new(id)
end end
end end


def self.create(username, password) def self.create(username, password)
user_id = redis.incr("user:uid") user_id = mb.incr("user:uid")
salt = User.new_salt salt = User.new_salt
redis.set("user:id:#{user_id}:username", username) mb.set("user:id:#{user_id}:username", username)
redis.set("user:username:#{username}", user_id) mb.set("user:username:#{username}", user_id)
redis.set("user:id:#{user_id}:salt", salt) mb.set("user:id:#{user_id}:salt", salt)
redis.set("user:id:#{user_id}:hashed_password", hash_pw(salt, password)) mb.set("user:id:#{user_id}:hashed_password", hash_pw(salt, password))
redis.push_head("users", user_id) mb.push("users", user_id)
User.new(user_id) User.new(user_id)
end end


def self.new_users def self.new_users
redis.list_range("users", 0, 10).map do |user_id| mb.list_range("users", 0, 10).map do |user_id|
User.new(user_id) User.new(user_id)
end end
end end
Expand All @@ -82,85 +82,85 @@ def self.hash_pw(salt, password)


def posts(page=1) def posts(page=1)
from, to = (page-1)*10, page*10 from, to = (page-1)*10, page*10
redis.list_range("user:id:#{id}:posts", from, to).map do |post_id| mb.list_range("user:id:#{id}:posts", from, to).map do |post_id|
Post.new(post_id) Post.new(post_id)
end end
end end


def timeline(page=1) def timeline(page=1)
from, to = (page-1)*10, page*10 from, to = (page-1)*10, page*10
redis.list_range("user:id:#{id}:timeline", from, to).map do |post_id| mb.list_range("user:id:#{id}:timeline", from, to).map do |post_id|
Post.new(post_id) Post.new(post_id)
end end
end end


def mentions(page=1) def mentions(page=1)
from, to = (page-1)*10, page*10 from, to = (page-1)*10, page*10
redis.list_range("user:id:#{id}:mentions", from, to).map do |post_id| mb.list_range("user:id:#{id}:mentions", from, to).map do |post_id|
Post.new(post_id) Post.new(post_id)
end end
end end


def add_post(post) def add_post(post)
redis.push_head("user:id:#{id}:posts", post.id) mb.push("user:id:#{id}:posts", post.id)
redis.push_head("user:id:#{id}:timeline", post.id) mb.push("user:id:#{id}:timeline", post.id)
end end


def add_timeline_post(post) def add_timeline_post(post)
redis.push_head("user:id:#{id}:timeline", post.id) mb.push("user:id:#{id}:timeline", post.id)
end end


def add_mention(post) def add_mention(post)
redis.push_head("user:id:#{id}:mentions", post.id) mb.push("user:id:#{id}:mentions", post.id)
end end


def follow(user) def follow(user)
return if user == self return if user == self
redis.set_add("user:id:#{id}:followees", user.id) mb.set_add("user:id:#{id}:followees", user.id)
user.add_follower(self) user.add_follower(self)
end end


def stop_following(user) def stop_following(user)
redis.set_delete("user:id:#{id}:followees", user.id) mb.set_delete("user:id:#{id}:followees", user.id)
user.remove_follower(self) user.remove_follower(self)
end end


def following?(user) def following?(user)
redis.set_member?("user:id:#{id}:followees", user.id) mb.set_member?("user:id:#{id}:followees", user.id)
end end


def followers def followers
redis.set_members("user:id:#{id}:followers").map do |user_id| mb.set_members("user:id:#{id}:followers").map do |user_id|
User.new(user_id) User.new(user_id)
end end
end end


def followees def followees
redis.set_members("user:id:#{id}:followees").map do |user_id| mb.set_members("user:id:#{id}:followees").map do |user_id|
User.new(user_id) User.new(user_id)
end end
end end


protected protected


def add_follower(user) def add_follower(user)
redis.set_add("user:id:#{id}:followers", user.id) mb.set_add("user:id:#{id}:followers", user.id)
end end


def remove_follower(user) def remove_follower(user)
redis.set_delete("user:id:#{id}:followers", user.id) mb.set_delete("user:id:#{id}:followers", user.id)
end end
end end


class Post < Model class Post < Model
def self.create(user, content) def self.create(user, content)
post_id = redis.incr("post:uid") post_id = mb.incr("post:uid")
post = Post.new(post_id) post = Post.new(post_id)
post.content = content post.content = content
post.user_id = user.id post.user_id = user.id
post.created_at = Time.now.to_s post.created_at = Time.now.to_s
post.user.add_post(post) post.user.add_post(post)
redis.push_head("timeline", post_id) mb.push("timeline", post_id)
post.user.followers.each do |follower| post.user.followers.each do |follower|
follower.add_timeline_post(post) follower.add_timeline_post(post)
end end
Expand All @@ -183,12 +183,3 @@ def user
User.new(user_id) User.new(user_id)
end end
end end









2 changes: 1 addition & 1 deletion login-signup.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
post '/signup' do post '/signup' do
if params[:username] !~ /^\w+$/ if params[:username] !~ /^\w+$/
@signup_error = "Username must only contain letters, numbers and underscores." @signup_error = "Username must only contain letters, numbers and underscores."
elsif redis.key?("user:username:#{params[:username]}") elsif mb.exists?("user:username:#{params[:username]}")
@signup_error = "That username is taken." @signup_error = "That username is taken."
elsif params[:username].length < 4 elsif params[:username].length < 4
@signup_error = "Username must be at least 4 characters" @signup_error = "Username must be at least 4 characters"
Expand Down
70 changes: 70 additions & 0 deletions store.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,70 @@
# A storage abstraction over memcached-client API.
#
require 'memcache'

class Store < MemCache
def to_s
"Store to #{self.servers}"
end

def exists?(key)
self.get(key) != nil
end

def incr(key)
n = super(key)
return n if n
self.add(key, '0', 0, true)
self.incr(key)
end

def list_range(key, from, to)
list = self.get(key, true) # Ex: nil or "+1+2+3".
return [] unless list
(list.split('+').drop(1) || []).slice(from, to - from + 1)
end

def push(key, value, prefix="+")
# Lists are '+' delimited.
if self.prepend(key, "#{prefix}#{value}").match("NOT_STORED")
self.add(key, '', 0, true)
self.push(key, value)
end
end

def set_members_hash(key)
s = self.get(key, true) # Ex: nil or "+1+2-1"
return {} unless s
acts = s.split(/[^\+-]/) # Ex: ["+", "+", "-"]
vals = s.split(/[\+-]/).drop(1) # Ex: ["1", "2", "1"]
m = {}
i = acts.length - 1
while i >= 0
val = vals[i]
if acts[i] == '+'
m[val] = true
else
m.delete(val)
end
i = i - 1
end
m
end

def set_members(key)
self.set_members_hash(key).keys()
end

def set_add(key, value)
self.push(key, value, prefix="+")
end

def set_delete(key, value)
self.push(key, value, prefix="-")
end

def set_member?(key, value)
self.set_members_hash(key)[value]
end
end

7 changes: 4 additions & 3 deletions views/footer.erb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
</div> </div>
<div class="span-24 last"> <div class="span-24 last">
<hr /> <hr />
Retwis-RB is a simple Twitter clone written in Ruby and Sinatra to show off mtweet is a simple twitter clone that uses
the <a href="http://code.google.com/p/redis/">Redis key-value database</a>. the <a href="http://www.membase.org">Membase database</a>.
The code is on <a href="http://github.com/danlucraft/retwis-rb">Github</a>. Thanks to <a href="http://github.com/danlucraft/retwis-rb">retwis-rb</a>
for the original fork source.
</div> </div>
</div> </div>
</body> </body>
Expand Down
4 changes: 2 additions & 2 deletions views/header.erb
Original file line number Original file line Diff line number Diff line change
@@ -1,6 +1,6 @@
<ntml> <ntml>
<head> <head>
<title>Retwis-RB</title> <title>mtweet</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen, projection"> <link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen, projection">
<link rel="stylesheet" href="/css/print.css" type="text/css" media="print"> <link rel="stylesheet" href="/css/print.css" type="text/css" media="print">
<!--[if IE]> <!--[if IE]>
Expand All @@ -13,7 +13,7 @@
<div class="container"> <div class="container">
<div id="header" class="span-24"> <div id="header" class="span-24">
<div class="span-12"> <div class="span-12">
<h1>Retwis-RB</h1> <h1>mtweet</h1>
</div> </div>
<div class="span-12 last right-align"> <div class="span-12 last right-align">
<% if @logged_in_user %> <% if @logged_in_user %>
Expand Down

0 comments on commit d828016

Please sign in to comment.