Skip to content

Instantly share code, notes, and snippets.

@notduncansmith
Last active August 29, 2015 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save notduncansmith/7858d27a2b875ff10b84 to your computer and use it in GitHub Desktop.
Save notduncansmith/7858d27a2b875ff10b84 to your computer and use it in GitHub Desktop.
In-memory graph datastore with friendship and notification support in <100 lines
# Bring in static test data
data = require './data'
# Spin up graph store
Rel = require('./rel')()
# Set up the friendships
Rel.put [
data.events.aliceBefriendsBob
data.events.carolBefriendsBob
]
# Create some interactions
setTimeout (-> Rel.put(data.events.aliceOwnsPost)), 1000
setTimeout (-> Rel.put(data.events.bobLikesPost)), 2000
###
Output:
Alice isFriendsWith Bob
Carol isFriendsWith Bob
Alice owns {Alice: "I am happy"}
Bob isNotifiedBy <Rel: Alice owns {Alice: "I am happy"}>
Bob likes {Alice: "I am happy"}
Alice isNotifiedBy <Rel: Bob likes {Alice: "I am happy"}>
Carol isNotifiedBy <Rel: Bob likes {Alice: "I am happy"}>
###
data =
post:
_id: 0
_type: 'post'
text: 'I am happy'
Alice:
_id: 1
_type: 'user'
name: 'Alice'
Bob:
_id: 2
_type: 'user'
name: 'Bob'
Carol:
_id: 3
_type: 'user'
name: 'Carol'
events: {}
data.events.aliceBefriendsBob =
subject: data.Alice
predicate: 'isFriendsWith'
object: data.Bob
data.events.carolBefriendsBob =
subject: data.Carol
predicate: 'isFriendsWith'
object: data.Bob
data.events.aliceOwnsPost =
subject: data.Alice
predicate: 'owns'
object: data.post
data.events.bobLikesPost =
subject: data.Bob
predicate: 'likes'
object: data.post
module.exports = data
_ = require 'lodash'
$ = require 'highland'
uuid = require('node-uuid').v4
EventEmitter = require('events').EventEmitter
module.exports = ->
Rel =
ee: new EventEmitter()
data: {}
put: (rel) ->
if _.isArray(rel)
rel.map @put.bind(@)
else
rel._id = uuid()
rel._type = 'rel'
@data[rel._id] = rel
@ee.emit 'put', rel
get: (opts) ->
if typeof opts is 'string'
_.findWhere _.values(@data), {_id: id}
else
@find opts
find: (opts) ->
{subject, predicate, object} = _.extend {}, {subject:'*', predicate:'*', object:'*'}, opts
_.values(@data).filter (rel) ->
matchesSubject = (subject is '*') or (subject._id is rel.subject._id) or (subject is rel.subject._id)
matchesPredicate = (predicate is '*') or (predicate is rel.predicate) # always a string
matchesObject = (object is '*') or (object._id is rel.object._id) or (object is rel.object._id)
matchesSubject and matchesPredicate and matchesObject
on: (eventName) ->
$(eventName, @ee)
owner: (object) ->
result = @find {predicate: 'owns', object: object}
result[0].subject
friends: (user) ->
if typeof user isnt 'string'
user = user._id
befriended = _.pluck @find({subject: user, predicate: 'isFriendsWith'}), 'object'
befriendedBy = _.pluck @find({predicate: 'isFriendsWith', object: user}), 'subject'
allFriends = befriended.concat(befriendedBy)
_.uniq allFriends, '_id'
notify: (whom, ofWhat) ->
if _.isArray(whom)
return whom.map (w) => @notify w, ofWhat
what = @stringify ofWhat
# Would probably just merge this on the client-side and periodically compact DB
# But we'll de-dupe notifications here to show how easy it is
alreadyNotified = @find {subject: whom, object: what}
unless alreadyNotified.length > 0
@put {subject: whom, predicate: 'isNotifiedBy', object: what}
stringify: (rel) ->
if typeof rel is 'string'
return "<Rel: #{rel}>"
switch rel._type
when 'rel' then "#{@stringify(rel.subject)} #{rel.predicate} #{@stringify(rel.object)}"
when 'user' then "#{rel.name}"
when 'post' then "{#{@owner(rel).name}: \"#{rel.text}\"}"
else rel
# Logging
Rel.on('put').each (rel) -> console.log Rel.stringify(rel)
# Notify users when their friends create posts
Rel.on('put')
.filter (rel) -> rel.predicate is 'owns'
.each (rel) ->
friends = Rel.friends rel.subject
Rel.notify friends, rel
# Notify users when their posts are liked
Rel.on('put')
.filter (rel) -> rel.predicate is 'likes'
.each (rel) ->
owner = Rel.owner rel.object
Rel.notify owner, rel
# Notify users when their friends like posts
Rel.on('put')
.filter (rel) -> rel.predicate is 'likes'
.each (rel) ->
friends = Rel.friends rel.subject
Rel.notify friends, rel
return Rel
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment