index.coffee | |
---|---|
flo is an redis powered node.js autocompleter inspired by soulmate. You can check out some examples here. | _ = require "underscore"
async = require "async" |
Sets up a new Redis Connection. options - Optional Hash of options.
Returns a Connection instance. | exports.connect = (options) ->
new exports.Connection options || {} |
Handles the connection to the Redis server. | class Connection
constructor: (options) ->
@helper = new Helper
@redis = options.redis || connectToRedis options
@namespace = options.namespace || 'flo'
@mincomplete = options.mincomplete || 1
@redis.select options.database if options.database? |
Public: Get all prefixes for a phrase
Returns an array of unique prefixes for the phrase | prefixes_for_phrase: (phrase) ->
words = @helper.normalize(phrase).split(' ')
_.uniq(
_.flatten(
_.map(words, (w) =>
_.map([(@mincomplete-1)..(w.length-1)], (l) ->
w[0..l]
)
)
)
) |
Public: Search for a term
| search_term: (types, phrase, args...) ->
if typeof(args[0]) == 'number'
limit = args[0]
else
limit = 5
callback = args[args.length-1]
async.map types, (type, callb) =>
words = _.uniq(
@helper.normalize(phrase).split(' ')
).sort() |
for caching purpose | cachekey = @key(type, "cache", words.join('|'))
async.waterfall([
((cb) =>
@redis.exists cachekey, cb
),
((exists, cb) =>
if !exists
interkeys = _.map(words, (w) =>
@key(type, "index", w)
)
@redis.zinterstore cachekey, interkeys.length, interkeys..., (err, count) =>
@redis.expire cachekey, 10 * 60, -> # expire after 10 minutes
cb()
else
cb()
),
((cb) =>
@redis.zrevrange cachekey, 0, (limit - 1), (err, ids) =>
if ids.length > 0
@redis.hmget @key(type, "data"), ids..., cb
else
cb(null, [])
)
], (err, results) ->
data = {}
data[type] = results
callb(err, data)
)
, (err, results) ->
results = _.extend results...
results.term = phrase
callback(err, results) |
Public: Add a new term
Returns nothing. | add_term: (type, id, term, score, data, callback) -> |
store the data in parallel | async.parallel([
((callb) =>
@redis.hset @key(type, "data"), id,
JSON.stringify term: term, score: score, data: (data || []),
->
callb()
),
((callb) =>
async.forEach @prefixes_for_phrase(term),
((w, cb) =>
@redis.zadd @key(type, "index", w), score, id, # sorted set
-> cb()
), ->
callb()
)
], ->
callback() if callback?
) |
Public: Get the redis instance Returns the redis instance. | redis: ->
@redis |
Public: Quits the connection to the Redis server. Returns nothing. | end: ->
@redis.quit() |
Builds a namespaced Redis key with the given arguments.
Returns an assembled String key. | key: (args...) ->
args.unshift @namespace
args.join ":"
class Helper |
Public: Normalize a term to remove all other characters than a-z and 0-9.
Returns a normalized term. | normalize: (term) ->
@strip(@gsub(term.toLowerCase(), /[^a-z0-9 ]/i, '')) |
Public: This function partially simulates the Ruby's String gsub method.
Example:
Returns the modified string. | gsub: (source, pattern, replacement) ->
unless pattern? and replacement?
return source
result = ''
while source.length > 0
if (match = source.match(pattern))
result += source.slice(0, match.index)
result += replacement
source = source.slice(match.index + match[0].length)
else
result += source
source = ''
result |
Public: Strip out leading and trailing whitespaces.
Returns a copy of str with leading and trailing whitespace removed. | strip: (source) ->
source.replace(/^\s+/, '').replace(/\s+$/, '')
connectToRedis = (options) ->
redis = require('redis').createClient options.port, options.host
redis.auth options.password if options.password?
redis
exports.Helper = new Helper
exports.Connection = Connection
|