Skip to content

Commit

Permalink
Add option accept_null to the get method
Browse files Browse the repository at this point in the history
  • Loading branch information
wdavidw committed Mar 7, 2012
1 parent 56cae69 commit 7ddd2a0
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 75 deletions.
163 changes: 103 additions & 60 deletions lib/Records.coffee
Expand Up @@ -2,8 +2,8 @@
Schema = require './Schema'

###
Records
=======
Records access and manipulation
===============================
Implement object based storage with indexing support.
Expand Down Expand Up @@ -59,7 +59,6 @@ module.exports = class Records extends Schema
return callback err if err
@unserialize records
callback null, records

###
`clear(callback)` Clear all the records
Expand Down Expand Up @@ -125,7 +124,6 @@ module.exports = class Records extends Schema
multi.exec (err, results) ->
return callback err if err
callback null, count

###
Count the number of records present in the database.
###
Expand All @@ -135,7 +133,6 @@ module.exports = class Records extends Schema
@redis.scard "#{db}:#{name}_#{identifier}", (err, count) ->
return callback err if err
callback null, count

###
`create(records, [options], callback)` Create new records
----------------------------------------------
Expand Down Expand Up @@ -217,7 +214,6 @@ module.exports = class Records extends Schema
records = for record in records
record[identifier]
callback null, if isArray then records else records[0]

###
`exists(records, callback)` Check if one or more record exist
Expand Down Expand Up @@ -255,8 +251,69 @@ module.exports = class Records extends Schema
return callback err if err
@unserialize recordIds
callback null, if isArray then recordIds else recordIds[0]
###
`get(records, [options], callback)` Retrieve one or multiple records
--------------------------------------------------------------------
If options is an array, it is considered to be the list of properties to
retrieve. By default, unless the `force` option is defined, only the properties
not yet defined in the provided records are fetch from Redis.
`options` All options are optional. Options properties include:
* `properties` Array of properties to fetch, all properties if not defined.
* `force` Force the retrieval of properties even if already present in the record objects.
* `accept_null` Skip objects if they are provided as null.
`callback` Called on success or failure. Received parameters are:
* `err` Error object if the command failed
* `records` Object or array of object if command succeed. Objects are null if records are not found.
###
get: (records, options, callback) ->
if arguments.length is 2
callback = options
options = {}
if Array.isArray options
options = {properties: options}
{redis} = @
{db, name, identifier} = @data
isArray = Array.isArray records
records = [records] unless isArray
# Quick exit for accept_null
return callback null, null if options.accept_null? and not records.some((record) -> record isnt null)
# Retrieve records identifiers
@id records, {object: true, accept_null: options.accept_null?}, (err, records) =>
return callback err if err
cmds = []
records.forEach (record, i) ->
# An error would have been thrown by id if record was null and accept_null wasn't provided
return unless record?
if record[identifier] is null
records[i] = null
else if options.properties?
options.properties.forEach (property) ->
unless options.force or record[property]
recordId = record[identifier]
cmds.push ['hget', "#{db}:#{name}:#{recordId}", property, (err, value)->
record[property] = value
]
else
recordId = record[identifier]
cmds.push ['hgetall', "#{db}:#{name}:#{recordId}", (err, values)->
for property, value of values
record[property] = value
]
if cmds.length is 0
return callback null, if isArray then records else records[0]
multi = redis.multi cmds
multi.exec (err, values) =>
return callback err if err
@unserialize records
callback null, if isArray then records else records[0]
###
Create or extract one or several ids.
-------------------------------------
The method doesn't hit the database to check the existance of an id if it is already provided.
Expand All @@ -269,6 +326,28 @@ module.exports = class Records extends Schema
- object: return record objects instead of ids
- accept_null: prevent error throwing if record is null
The id will be set to null if the record wasn't discovered in the database
`records` Record object or array of record objects.
`options` Options properties include:
* `accept_null` Skip objects if they are provided as null.
* `object` Return an object in the callback even if it recieve an id instead of a record.
Provisionning 2 identifiers:
users = [
{username: 'username_1'}
{username: 'username_2'}
]
Users.get 'users', properties:
user_id: identifier: true
username: unique: true
Users.id users, (err, users) ->
should.not.exist err
ids = for user in users then user.user_id
console.log ids
###
id: (records, options, callback) ->
if arguments.length is 2
Expand All @@ -282,8 +361,9 @@ module.exports = class Records extends Schema
err = null
for record, i in records
if typeof record is 'object'
if not record?
if not options.accept_null
unless record?
# Check if we allow records to be null
unless options.accept_null
return callback new Error 'Invalid object, got ' + (JSON.stringify record)
else if record[identifier]?
# It's perfect, no need to hit redis
Expand Down Expand Up @@ -320,66 +400,31 @@ module.exports = class Records extends Schema
record[identifier]
@unserialize records
callback null, if isArray then records else records[0]

###
Retrieve one or multiple records
--------------------------------
If options is an array, it is considered to be the list of properties to retrieve.
Options are
- properties, array of properties to fetch, all properties if not provided
- force, force the retrieval of properties even if already present in the record objects
callback arguments are
- err, error object if the command failed
- records, object or array of object if command succeed. Object are null if record was not found
###
get: (records, options, callback) ->
if arguments.length is 2
callback = options
options = {}
if Array.isArray options
options = {properties: options}
{redis} = @
{db, name, identifier} = @data
isArray = Array.isArray records
records = [records] unless isArray
@id records, {object: true}, (err, records) =>
return callback err if err
cmds = []
records.forEach (record, i) ->
if record[identifier] is null
records[i] = null
else if options.properties?
options.properties.forEach (property) ->
if ! options.force and ! record[property]
recordId = record[identifier]
cmds.push ['hget', "#{db}:#{name}:#{recordId}", property, (err, value)->
record[property] = value
]
else
recordId = record[identifier]
cmds.push ['hgetall', "#{db}:#{name}:#{recordId}", (err, values)->
for property, value of values
record[property] = value
]
if cmds.length is 0
return callback null, if isArray then records else records[0]
multi = redis.multi cmds
multi.exec (err, values) =>
return callback err if err
@unserialize records
callback null, if isArray then records else records[0]
###
`list(options, callback)` List records
`list([options], callback)` List records
--------------------------------------
List records with filtering and sorting.
Using the `union` operation:
Using the `union` operation:
Users.list
where: group: ['admin', 'redis']
operation: 'union'
direction: 'desc'
, (err, users) ->
console.log users
An alternative syntax is to bypass the `where` option, the exemple above
could be rewritten as:
Users.list
group: ['admin', 'redis']
operation: 'union'
direction: 'desc'
, (err, users) ->
console.log users
###
list: (options, callback) ->
if typeof options is 'function'
Expand Down Expand Up @@ -444,7 +489,6 @@ module.exports = class Records extends Schema
multi.sort args...
multi.del tempkey if tempkey
multi.exec()

###
Remove one or several records
-----------------------------
Expand Down Expand Up @@ -476,7 +520,6 @@ module.exports = class Records extends Schema
multi.exec (err, results) ->
return callback err if err
callback null, records.length

###
`update(records, [options], callback)` Update one or several records
Expand Down
18 changes: 3 additions & 15 deletions lib/Schema.coffee
Expand Up @@ -5,9 +5,8 @@ isEmail = (email) ->
/^[a-z0-9,!#\$%&'\*\+\/\=\?\^_`\{\|}~\-]+(\.[a-z0-9,!#\$%&'\*\+\/\=\?\^_`\{\|}~\-]+)*@[a-z0-9\-]+(\.[a-z0-9\-]+)*\.([a-z]{2,})$/.test(email)

###
Schema
======
Define a new schema.
Schema definition
=================
`ron` Reference to the Ron instance
Expand All @@ -28,7 +27,7 @@ Record properties may be defined by the following keys:
Sample
------
ron.schema
ron.get
name: 'users'
properties:
user_id: identifier: true
Expand Down Expand Up @@ -60,7 +59,6 @@ module.exports = class Schema
if options.properties
for name, value of options.properties
@property name, value

###
Define property as en email
---------------------------
Expand All @@ -84,7 +82,6 @@ module.exports = class Schema
# Get the property
else
@data.email

###
Hash a key
----------
Expand All @@ -94,7 +91,6 @@ module.exports = class Schema
hash: (key) ->
key = "#{key}" if typeof key is 'number'
return if key? then crypto.createHash('sha1').update(key).digest('hex') else 'null'

###
Define a property as an identifier
----------------------------------
Expand All @@ -114,7 +110,6 @@ module.exports = class Schema
# Get the property
else
@data.identifier

###
Define a property as indexable or return all index properties
-------------------------------------------------------------
Expand Down Expand Up @@ -142,7 +137,6 @@ module.exports = class Schema
# Get the property
else
Object.keys(@data.index)

###
Retrieve/define a new property
------------------------------
Expand Down Expand Up @@ -175,7 +169,6 @@ module.exports = class Schema
@
else
@data.properties[property]

###
`name` Schema name
-------------------
Expand All @@ -187,7 +180,6 @@ module.exports = class Schema
###
name: ->
@data.name

###
`unserialize(records)` Cast record values to their correct type
--------------------------------------------------------
Expand All @@ -211,7 +203,6 @@ module.exports = class Schema
else if typeof record is 'number' or typeof record is 'string'
records[i] = parseInt record
if isArray then records else records[0]

###
`serialize(records)` Cast record values for their insertion
-----------------------------------------------------------
Expand All @@ -235,7 +226,6 @@ module.exports = class Schema
else if typeof value is 'object' and value instanceof Date
record[property] = value.getTime()
if isArray then records else records[0]

###
`temporal([options])` Define or retrieve temporal definition
------------------------------------------------------------
Expand All @@ -255,7 +245,6 @@ module.exports = class Schema
@property temporal.modification, type: 'date'
else
[ @data.temporal.creation, @data.temporal. modification ]

###
`validate(records, [options])` Validate
Expand Down Expand Up @@ -291,7 +280,6 @@ module.exports = class Schema
else validation[property.name] = 'invalid_email'
validation
if isArray then validations else validations[0]

###
Define a property as unique
---------------------------
Expand Down
33 changes: 33 additions & 0 deletions test/get.coffee
Expand Up @@ -71,3 +71,36 @@ describe 'get', ->
user.username.should.eql 'my_username'
should.not.exist user.email
Users.clear next

it 'should be able to get null records with option accept_null', (next) ->
Users.create
username: 'my_username',
email: 'my@email.com',
, (err, user) ->
userId = user.user_id
# A single null record
Users.get null, accept_null: true, (err, user) ->
should.not.exist err
should.not.exist user
# Multiple all null records
Users.get [null, null], accept_null: true, (err, user) ->
should.not.exist err
should.not.exist user
# Multiple with null records
Users.get [null, userId, null], accept_null: true, (err, users) ->
should.not.exist err
users.length.should.eql 3
should.not.exist users[0]
users[1].username.should.eql 'my_username'
should.not.exist users[2]
Users.clear next










0 comments on commit 7ddd2a0

Please sign in to comment.