-->

Docpad: using extendTemplateData via mongoose call

2019-09-10 07:55发布

问题:

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

回答1:

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.

  1. 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)

  2. 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.

  3. 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)

  4. 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()

  5. 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
        @