I'm trying to build a simple plugin to get get data from Mongo into an object over which I can iterate when rendering. The full code is in my project, but the essence of it is a failed attempt to emulate the feedr example. I know the mongoose stuff is working as the console log works, but getting the content sent to the docpad object is defeating me
class mongoPlugin extends BasePlugin
name: 'mongo'
# Fetch list of Gigs
getGigsData: (opts) ->
mongoose.connect ('mongodb://localhost/test')
db = mongoose.connection;
db.on 'error', console.error.bind(console, 'connection error:')
db.once 'open', () ->
gigsSchema = mongoose.Schema {
date : String,
location : String
}
Gigs = mongoose.model 'Gigs', gigsSchema
Gigs.find {}, (err, gigs) ->
mongoose.connection.close()
if err then console.error "db error"
else
console.dir gigs
opts["getGigsData"] = gigs
opts.templateData["getGigsData"] = gigs
return gigs
extendTemplateData: (opts) ->
opts.templateData["getGigsData"] = @getGigsData()
Using node-inspector and triggering a regeneration by editing docpad.coffee, I can see that opts has a field templateData, but it is empty, and is very different from docpad.templateData, so I am picking up the wrong object in the plugin. I can see others did a trick of placing a name in { } but I don't know what that does.
After completing the plugin code I see that my database data becomes the argument to a promise, so perhaps that's where it is supposed to be reintegrated with the docpad.config.templateData but that does not seem to happen in practise
So the main issue here is that we have an asynchronous function getGetsData
being executed inside a synchronous function, your templating engine. This simply, isn't possible, as the templating engine will go on and do its thing, while the synchronous stuff happens in the background. This is just an issue with just writing node.js/asynchronous code in general.
The fixes for this is pretty easy to do.
opts.templateData["getGigsData"] = @getGigsData()
calls getGigsData
without passing over the opts
, so that when getGigsData
tries and uses the opts, it can't, so that would throw an error. The fix for this is to do @getGigsData(opts)
opts.templateData["getGigsData"] = @getGigsData(opts)
assigns the return value of @getGigsData(opts)
to the template data, however, the result of this is the result of the db.once
call, as that is what will be returned in that scope. When you do return gigs
, that's actually the return value for the (err, gigs) ->
callback on the Gigs.find
call, rather than the return value for the getGigsData
. It's all about scopes.
As the database stuff is asynchronous, we need to make getGigsData
asynchronous. To do this, we change extendTemplateData: (opts) ->
to extendTemplateData: (opts,next) ->
to make it asynchronous, and change opts.templateData["getGigsData"] = @getGigsData()
to simply return @getGigsData(opts,next)
Now that we have the event and call asynchronous. We now need to make the definition of getGigsData support it. So lets change getGigsData: (opts) ->
to getGigsData: (opts,next) ->
to take in the completion callback (next
) that we defined in step 3. And what we will do, is we will call next where we have return gigs
, so lets change return gigs
to return next()
It should now work. But as a little bit of cleaning, we can make the error handling better by changing if err then console.error "db error"
to return next(err) if err
. You will need to fix up the indentation as we will need to remove the else
block.
Considering all that, and with a bit more cleaning applied, you'll end up with this:
class mongoPlugin extends BasePlugin
name: 'mongo'
config:
hostname: 'mongodb://localhost/test'
# Fetch list of Gigs
getGigsData: (opts={}, next) ->
config = @getConfig()
docpad = @docpad
mongoose.connect(config.hostname)
db = mongoose.connection
db.on 'error', (err) ->
docpad.error(err) # you may want to change this to `return next(err)`
db.once 'open', ->
gigsSchema = mongoose.Schema {
date: String,
location: String
}
Gigs = mongoose.model('Gigs', gigsSchema)
Gigs.find {}, (err, gigs) ->
mongoose.connection.close()
return next(err) if err
return next(null, gigs)
# Chain
@
extendTemplateData: (opts,next) ->
@getGigsData null, (err, gigs) ->
return next(err) if err
opts.templateData.gigs = gigs
# Chain
@