Skip to content

Commit

Permalink
Imported code.
Browse files Browse the repository at this point in the history
  • Loading branch information
noteflakes committed Feb 28, 2007
1 parent 1d390db commit 5dab3f0
Show file tree
Hide file tree
Showing 8 changed files with 1,478 additions and 42 deletions.
140 changes: 98 additions & 42 deletions README
@@ -1,68 +1,124 @@
= about ServerSide
== about Sequel

ServerSide is an HTTP server framework designed to be as fast as possible, and
as easy as possible to use. ServerSide includes a full-featured HTTP server, a
controller-view system and a bunch of other tools to easily create servers and
clusters of servers.
Sequel is an ORM framework for Ruby. Sequel provides thread safety, connection pooling, and a DSL for constructing queries and table schemas.

== Sequel vs. ActiveRecord

Sequel offers the following advantages over ActiveRecord:

* Better performance with large tables: unlike ActiveRecord, Sequel does not load the entire resultset into memory, but fetches each record separately and implements an Enumerable interface.
* Easy construction of queries using a DSL.
* Using model classes is possible, but not mandatory.

== Resources

* {Project page}[http://code.google.com/p/serverside/]
* {Source code}[http://serverside.googlecode.com/svn/]
* {Bug tracking}[http://code.google.com/p/serverside/issues/list]
* {RubyForge page}[http://rubyforge.org/projects/serverside/]
* {Project page}[http://code.google.com/p/ruby-sequel/]
* {Source code}[http://ruby-sequel.googlecode.com/svn/]
* {Bug tracking}[http://code.google.com/p/ruby-sequel/issues/list]
* {RubyForge page}[http://rubyforge.org/projects/sequel/]

To check out the source code:

svn co http://serverside.googlecode.com/svn/trunk
svn co http://ruby-sequel.googlecode.com/svn/trunk

== Installation

sudo gem install serverside
sudo gem install sequel

== A Short Tutorial

=== Connecting to a database

There are two ways to create a connection to a database. The easier way is to provide a connection URL:

DB = Sequel.connect("postgres://postgres:postgres@localhost:5432/my_db")

You can also specify optional parameters, such as the connection pool size:

DB = Sequel.connect("postgres://postgres:postgres@localhost:5432/my_db",
:max_connections => 10)

== Usage
The second, more verbose, way is to create an instance of a database class:

Once you have the ServerSide gem installed, you can use the <tt>serverside</tt>
script to control servers. For example:
DB = Sequel::Postgres::Database.new(:database => 'my_db', :host => 'localhost',
:port => 5432)

serverside start .
=== Creating Datasets

will start an HTTP server on port 8000, serving the content of the working
directory. You can stop the server by running <tt>serverside stop .</tt>
Dataset is the primary means through which records are retrieved and manipulated. You can create an blank dataset by using the query method:

To run the server without forking, use the 'serve' command:
dataset = DB.query

serverside serve .
Or by using the from methods:

== Serving ERb Templates
posts = DB.from(:posts)

ServerSide can render ERb[http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/]
templates in a fashion similar to PHP. You can store templates in .rhtml files,
and ServerSide takes care of all the rest. ServerSide is also smart enough to
allow you to use nice looking URL's with your templates, and automatically adds
the .rhtml extension if the file is there.

== Serving Dynamic Content
You can also use the equivalent shorthand:

By default ServerSide serves static files, but you can change the behavior by
creating custom {routing rules}[classes/ServerSide/Connection/Router.html].
Here's a simple routing rule:
posts = DB[:posts]

ServerSide::Router.route(:path => '/hello/:name') {
send_response(200, 'text', "Hello #{@parameters[:name]}!")
}
Note: the dataset will only fetch records when you explicitly ask for them, as will be shown below. Datasets can be manipulated to filter through records, change record order and even join tables, as will also be shown below.

The ServerSide framework also lets you route requests based on any attribute of
incoming requests, such as host name, path, URL parameters etc.
=== Retrieving Records

To run your custom rules, you can either put them in a file called serverside.rb,
or tell serverside to explicitly load a specific file:
You can retrieve records by using the all method:

serverside start ~/myapp/myapp.rb
posts.all

== Running a Cluster of Servers
The all method returns an array of hashes, where each hash corresponds to a record.

You can also iterate through records one at a time:

posts.each {|row| p row}

Or perform more advanced stuff:

posts.map(:id)
posts.inject({}) {|h, r| h[r[:id]] = r[:name]}

You can also retrieve the first record in a dataset:

posts.first

If the dataset is ordered, you can also ask for the last record:

posts.order(:stamp).last

=== Filtering Records

The simplest way to filter records is to provide a hash of values to match:

my_posts = posts.filter(:category => 'ruby', :author => 'david')

You can also specify ranges:

my_posts = posts.filter(:stamp => 2.weeks.ago..1.week.ago)

Some adapters will also let you specify Regexps:

my_posts = posts.filter(:category => /ruby/i)

You can also use an inverse filter:

my_posts = posts.exclude(:category => /ruby/i)

You can then retrieve the records by using any of the retrieval methods:

my_posts.each {|row| p row}

You can also specify a custom WHERE clause:

posts.filter('(stamp < ?) AND (author <> ?)', 3.days.ago, author_name)

=== Counting Records

posts.count

=== Ordering Records

posts.order(:stamp)

You can also specify descending order

ServerSide makes it easy to control a cluster of servers. Just supply a range of
ports instead of a single port:
posts.order(:stamp.DESC)

serverside -p 8000..8009 start .
65 changes: 65 additions & 0 deletions lib/sequel/connection_pool.rb
@@ -0,0 +1,65 @@
require 'thread'

module ServerSide
class ConnectionPool
attr_reader :max_size, :mutex, :conn_maker
attr_reader :available_connections, :allocated, :created_count

def initialize(max_size = 4, &block)
@max_size = max_size
@mutex = Mutex.new
@conn_maker = block

@available_connections = []
@allocated = {}
@created_count = 0
end

def size
@created_count
end

def hold
t = Thread.current
if (conn = owned_connection(t))
return yield(conn)
end
while !(conn = acquire(t))
sleep 0.001
end
begin
yield conn
ensure
release(t)
end
end

def owned_connection(thread)
@mutex.synchronize {@allocated[thread]}
end

def acquire(thread)
@mutex.synchronize do
@allocated[thread] ||= available
end
end

def available
@available_connections.pop || make_new
end

def make_new
if @created_count < @max_size
@created_count += 1
@conn_maker.call
end
end

def release(thread)
@mutex.synchronize do
@available_connections << @allocated[thread]
@allocated.delete(thread)
end
end
end
end
93 changes: 93 additions & 0 deletions lib/sequel/database.rb
@@ -0,0 +1,93 @@
require 'uri'

require File.join(File.dirname(__FILE__), 'schema')

module ServerSide
class Database
def initialize(opts = {})
@opts = opts
end

# Some convenience methods

# Returns a new dataset with the from method invoked.
def from(*args); query.from(*args); end

# Returns a new dataset with the select method invoked.
def select(*args); query.select(*args); end

# returns a new dataset with the from parameter set. For example,
#
# db[:posts].each {|p| puts p[:title]}
def [](table)
query.from(table)
end

# Returns a literal SQL representation of a value. This method is usually
# overriden in descendants.
def literal(v)
case v
when String: "'%s'" % v
else v.to_s
end
end

# Creates a table.
def create_table(name, columns = nil, indexes = nil, &block)
if block
schema = Schema.new
schema.create_table(name, &block)
schema.create(self)
else
execute Schema.create_table_sql(name, columns, indexes)
end
end

# Drops a table.
def drop_table(name)
execute Schema.drop_table_sql(name)
end

# Performs a brute-force check for the existance of a table. This method is
# usually overriden in descendants.
def table_exists?(name)
from(name).count
true
rescue
false
end

@@adapters = Hash.new

# Sets the adapter scheme for the database class.
def self.set_adapter_scheme(scheme)
@@adapters[scheme.to_sym] = self
end

# Converts a uri to an options hash.
def self.uri_to_options(uri)
{
:user => uri.user,
:password => uri.password,
:host => uri.host,
:port => uri.port,
:database => (uri.path =~ /\/(.*)/) && ($1)
}
end

def self.connect(conn_string)
uri = URI.parse(conn_string)
c = @@adapters[uri.scheme.to_sym]
raise "Invalid database scheme" unless c
c.new(c.uri_to_options(uri))
end
end
end

class Time
SQL_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S'".freeze

def to_sql_timestamp
strftime(SQL_FORMAT)
end
end

0 comments on commit 5dab3f0

Please sign in to comment.