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()
callsgetGigsData
without passing over theopts
, so that whengetGigsData
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 thedb.once
call, as that is what will be returned in that scope. When you doreturn gigs
, that's actually the return value for the(err, gigs) ->
callback on theGigs.find
call, rather than the return value for thegetGigsData
. It's all about scopes.As the database stuff is asynchronous, we need to make
getGigsData
asynchronous. To do this, we changeextendTemplateData: (opts) ->
toextendTemplateData: (opts,next) ->
to make it asynchronous, and changeopts.templateData["getGigsData"] = @getGigsData()
to simplyreturn @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) ->
togetGigsData: (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 havereturn gigs
, so lets changereturn gigs
toreturn 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"
toreturn next(err) if err
. You will need to fix up the indentation as we will need to remove theelse
block.Considering all that, and with a bit more cleaning applied, you'll end up with this: