Skip to content

Commit

Permalink
Using asserts to verify method arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
assaf committed Nov 24, 2011
1 parent e0674b0 commit 170a1dd
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 23 deletions.
44 changes: 31 additions & 13 deletions lib/poutine/collection.coffee
@@ -1,9 +1,18 @@
# Collection represents a MongoDB collection.
#
# Scope limits operations on a collection to particular records (based on query selector) and allows specifying query
# and update options (e.g. fields, limit, sorting).
#
# Cursor represents a MongoDB cursor that can be used to retrieve one object at a time.


assert = require("assert")
{ Model } = require("./model")


# Represents a collection and all the operation you can do on one. Database
# methods like find and insert operate through a collection.
exports.Collection = class Collection
class Collection
constructor: (@name, @database, @model)->

# -- Finders --
Expand Down Expand Up @@ -62,7 +71,7 @@ exports.Collection = class Collection
[callback, options] = [options, null]
else
[callback, selector] = [selector, null]
throw new Error("Callback required") unless callback instanceof Function
assert callback instanceof Function, "This function requires a callback"
@_connect (error, collection, database)=>
return callback error if error
collection.findOne selector || {}, options || {}, (error, object)=>
Expand All @@ -87,7 +96,7 @@ exports.Collection = class Collection
[callback, options] = [options, null]
else
[callback, selector] = [selector, null]
throw new Error("Callback required") unless callback instanceof Function
assert callback instanceof Function, "This function requires a callback"
if selector instanceof Array
selector = { _id: { $in: selector } }
@_query selector, options, (error, cursor)=>
Expand Down Expand Up @@ -126,7 +135,7 @@ exports.Collection = class Collection
[callback, options] = [options, null]
else
[callback, selector] = [selector, null]
throw new Error("Callback required") unless callback instanceof Function
assert callback instanceof Function, "This function requires a callback"
objects = []
@each selector, options, (error, object)=>
return callback error if error
Expand All @@ -142,7 +151,7 @@ exports.Collection = class Collection
count: (selector, callback)->
unless callback
[callback, selector] = [selector, null]
throw new Error("Callback required") unless callback instanceof Function
assert callback instanceof Function, "This function requires a callback"
@_connect (error, collection, database)=>
return callback error if error
collection.count selector || {}, (error, count)=>
Expand All @@ -156,9 +165,10 @@ exports.Collection = class Collection
# in the collection. With three arguments, only looks at objects that match
# the selector.
distinct: (key, selector, callback)->
assert key, "This function requires a key as its first argument"
unless callback
[callback, selector] = [selector, null]
throw new Error("Callback required") unless callback instanceof Function
assert callback instanceof Function, "This function requires a callback"
@_connect (error, collection, database)=>
return callback error if error
collection.distinct key, selector || {}, (error, count)=>
Expand All @@ -180,6 +190,7 @@ exports.Collection = class Collection

# Passes error, collection and database to callback.
_connect: (callback)->
assert callback instanceof Function, "This function requires a callback"
@database.driver (error, connection, end)=>
return callback error if error
connection.collection @name, (error, collection)=>
Expand All @@ -196,6 +207,7 @@ exports.Collection = class Collection
[callback, options] = [options, null]
else
[callback, selector] = [selector, null]
assert callback instanceof Function, "This function requires a callback"
@_connect (error, collection, database)=>
return callback error if error
collection.find selector || {}, options || {}, (error, cursor)=>
Expand Down Expand Up @@ -281,13 +293,15 @@ class Scope
# Example:
# first_ten = posts.limit(10)
limit: (limit)->
assert limit, "This function requires limit as its first argument"
return @extend(limit: limit)

# Return records from specified offset.
#
# Example:
# next_ten = posts.skip(10).limit(10)
skip: (skip)->
assert skip, "This function requires skip as its first argument"
return @extend(skip: skip)

# Returns a scope with combined options.
Expand All @@ -301,7 +315,7 @@ class Scope

# Changes sorting order.
sort: (fields, dir)->
throw "Direction must be 1 (asc) or -1 (desc)" unless dir == 1 || dir == -1
assert dir == 1 || dir == -1, "Direction must be 1 (asc) or -1 (desc)"
sort = @options.sort || []
for field in fields
if Array.isArray(field)
Expand All @@ -318,23 +332,20 @@ class Scope
# Example:
# connect().find("posts", author_id: id).one (err, post, db)->
one: (callback)->
throw new Error("Callback required") unless callback instanceof Function
@collection.one @selector, @options, callback

# Passes each object to callback. Passes null as last object.
#
# Example:
# connect().find("posts").each (err, post, db)->
each: (callback)->
throw new Error("Callback required") unless callback instanceof Function
@collection.each @selector, @options, callback

# Passes all objects to callback.
#
# Example:
# connect().find("posts").all (err, posts)->
all: (callback)->
throw new Error("Callback required") unless callback instanceof Function
@collection.all @selector, @options, callback

# Passes number of records in this query to callback.
Expand Down Expand Up @@ -362,10 +373,11 @@ class Scope
# connect().find("posts").map ((post)-> "#{post.title} on #{post.created_at}"), (error, posts)->
# console.log posts
map: (fn, callback)->
assert fn, "This function requires a mapping function as its first argument"
unless typeof fn == "function"
name = fn
fn = (object)-> object[name]
throw new Error("Callback required") unless callback instanceof Function
assert callback instanceof Function, "This function requires a callback"
results = []
@collection.each @selector, @options, (error, object)->
return callback error if error
Expand All @@ -386,10 +398,11 @@ class Scope
# connect().find("posts").filter ((post)-> post.body.length > 500), (error, posts)->
# console.log "Found #{posts.count} posts longer than 500 characters"
filter: (fn, callback)->
assert fn, "This function requires a filter function as its first argument"
unless typeof fn == "function"
name = fn
fn = (object)-> object[name]
throw new Error("Callback required") unless callback instanceof Function
assert callback instanceof Function, "This function requires a callback"
results = []
@collection.each @selector, @options, (error, object)->
return callback error if error
Expand All @@ -414,9 +427,10 @@ class Scope
# connect().find("posts").reduce ((total, post)-> total + post.body.length), (error, total)->
# console.log "Wrote #{total} characters"
reduce: (value, fn, callback)->
assert fn, "This function requires a reduce function"
if arguments.length < 3
[value, fn, callback] = [null, value, fn]
throw new Error("Callback required") unless callback instanceof Function
assert callback instanceof Function, "This function requires a callback"
@collection.each @selector, @options, (error, object)->
return callback error if error
if object
Expand All @@ -433,6 +447,7 @@ class Scope
# Opens cursor and passes next result to query. Passes null if there are no
# more results.
next: (callback)->
assert callback instanceof Function, "This function requires a callback"
if @_cursor
@_cursor.nextObject (error, object)->
callback error, object
Expand All @@ -452,3 +467,6 @@ class Scope
if @_cursor
@_cursor.close()
return


exports.Collection = Collection
19 changes: 16 additions & 3 deletions lib/poutine/connect.coffee
@@ -1,3 +1,11 @@
# The configure function is used to configure a database connection.
#
# The connect function is used to acquire logicl connection to a database.


assert = require("assert")


# Database configurations. We use this to configure database access and then
# get the driver instance (Db object).
databases = {}
Expand All @@ -16,14 +24,15 @@ databases = {}
#
# You can also call this with an object, where each key is a database name, and
# the corresponding value the database configuration.
exports.configure = configure = (name, options)->
configure = (name, options = {})->
assert name, "This function requires a database name"
{ Configuration } = require("./database")
if name.constructor == Object
configs = name
for name, options of configs
configure name, options
else
throw "Already have configuration for #{name}" if databases[name]
assert !databases[name], "Already have configuration named #{name}"
options ||= {}
config = new Configuration(options.name || name, options)
databases[name] = config
Expand All @@ -34,9 +43,13 @@ configure.default = null
configure.DEFAULT = "development"

# Provides access to the specified database (null for default database).
exports.connect = connect = (name)->
connect = (name)->
{ Database } = require("./database")
name ||= configure.default || process.env.NODE_ENV || configure.DEFAULT
unless databases[name]
configure name
return new Database(databases[name])


exports.configure = configure
exports.connect = connect
14 changes: 9 additions & 5 deletions lib/poutine/database.coffee
@@ -1,3 +1,4 @@
assert = require("assert")
{ Collection } = require("./collection")
{ Db, Server } = require("mongodb")
{ EventEmitter } = require("events")
Expand All @@ -11,7 +12,7 @@ catch ex

# Database configuration. This is basically a wrapped around the Mongodb
# driver, specifically it's Db object.
exports.Configuration = class Configuration
class Configuration
constructor: (name, options = {})->
@_pool = new Pool
name: name
Expand All @@ -38,7 +39,7 @@ exports.Configuration = class Configuration
# new connection that you can use to access the database.
#
# Phytical connections are lazily initialized and pooled.
exports.Database = class Database extends EventEmitter
class Database extends EventEmitter
constructor: (@_configuration)->
@_collections = []
@ObjectID = require("mongodb").BSONPure.ObjectID
Expand All @@ -54,7 +55,7 @@ exports.Database = class Database extends EventEmitter
# passes, error, a connection and a reference to the end method. Don't forget
# to call the end function once done with the connection.
driver: (callback)->
throw new Error("Callback required") unless callback
assert callback instanceof Function, "This function requires a callback"
end = @end.bind(this)
# This is the TCP connection, which we use until it's returned to
# the pool (see end method).
Expand Down Expand Up @@ -124,8 +125,7 @@ exports.Database = class Database extends EventEmitter
if name instanceof Function
model = name
name = model.collection
unless name
throw new Error("No #{model.constructor}.collection, can't determine collection name")
assert name, "#{model.constructor}.collection is undefined, can't determine which collection to access"
@_collections[name] ||= new Collection(name, this, model)

# Finds all objects that match the query selector.
Expand Down Expand Up @@ -178,3 +178,7 @@ exports.Database = class Database extends EventEmitter
# . . .
distinct: (name, key, selector, callback)->
@collection(name).distinct key, selector, callback


exports.Configuration = Configuration
exports.Database = Database
4 changes: 2 additions & 2 deletions lib/poutine/index.coffee
@@ -1,6 +1,6 @@
{ Model } = require("./model")
{ connect, configure } = require("./connect")

exports.connect = connect
exports.connect = connect
exports.configure = configure
exports.Model = Model
exports.Model = Model
6 changes: 6 additions & 0 deletions lib/poutine/model.coffee
@@ -1,3 +1,6 @@
# Basis for all Poutine models.


assert = require("assert")
connect = require("./connect").connect

Expand Down Expand Up @@ -105,6 +108,9 @@ class Model
@prototype.__defineSetter__ name, setter


# Poutine uses these lifecycle methods to perform operations on models, but keeps them separate so we don't pollute the
# Model prototype with methods that are never used directly by actual model classes. Inheriting from a class that has
# hundreds of implementation methods is an anti-pattern we dislike.
Model.lifecycle =
# Used to instantiate a new instance from a loaded object.
load: (model, values)->
Expand Down

0 comments on commit 170a1dd

Please sign in to comment.