Skip to content

Instantly share code, notes, and snippets.

@leshill
Created December 20, 2009 17:22
Show Gist options
  • Save leshill/260566 to your computer and use it in GitHub Desktop.
Save leshill/260566 to your computer and use it in GitHub Desktop.
# Ryan, the hits in the db approach is flawed (not currently atomic, and
# probably not really performant for high rates) -- but you knew that.
# So in the spirit of gets it done, I have followed along.
#
# API Example
## User Model
require 'digest/md5'
class User < ActiveRecord::Base
API_RATE_LIMIT_WINDOW = 2.minutes
API_RATE_LIMIT = 10
acts_as_authentic
has_many :posts
before_create :generate_api_key
validates_presence_of :username
validates_presence_of :password
# Added interval_end to users table, default to 1/1/2009
# No need for cron
def rate_check!
if Time.now < interval_end
if hits > API_RATE_LIMIT
false
else
increment!(:hits)
end
else
update_attributes(:interval_end => Time.now + API_RATE_LIMIT_WINDOW, :hits => 1)
end
end
def self.reset_all_hits
User.update_all(:hits => 0, "hits > 0")
end
private
def generate_api_key
self.api_key = Digest::MD5.hexdigest(username + Time.now.to_s)
end
end
## Error Model
class Error
def self.[](key)
find_by_name!(key)
end
end
## Application Controller
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery # See ActionController::RequestForgeryProtection for details
# Scrub sensitive parameters from your log
filter_parameter_logging :password
end
## Posts Controller
class PostsController < ApplicationController
before_filter :require_token, :only => :create
before_filter :hit!, :only => :create
def index
@posts = Post.all
end
def create
@post = user.posts.build(params[:post])
respond_to do |format|
if @post.save
format.xml { render :xml => @post, :status => :created, :location => @post }
format.json { render :json => @post }
else
format.xml { render :xml => @post.to_xml(:include => :errors), :status => :not_created }
format.json { render :json => @post.to_json(:include => :errors), :status => :not_created }
end
end
end
def update
@post = Post.find(params[:id])
respond_to do |format|
if @post.user != @user
error = Error["Not Post Owner"]
format.xml { render :xml => error, :status => 403 }
format.json { render :json => error, :status => 403 }
else
if @post.update_attributes(params[:post])
format.xml { head :ok }
format.json { render :json => @post }
else
format.xml { render :xml => @post.to_xml(:include => :errors), :status => :unprocessable_entity }
format.json { render :json => @post.to_json(:include => :errors), :status => :unprocessable_entity }
end
end
end
end
protected
def require_token
unless token
error = Error["Invalid Token"]
respond_to do |format|
format.xml { render :xml => error, :status => 401 }
format.json { render :json => error, :status => 401 }
end
end
end
def hit!
unless user.try(:rate_check!)
error = Error["Rate Limited"]
respond_to do |format|
format.xml { render :xml => error, :status => 403 }
format.json { render :json => error, :status => 403 }
end
end
end
def token
@token ||= params[:token]
end
def user
@user ||= (token ? User.find_by_api_key(token) : nil)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment