0

I'm using pouchDb and to query the database it requires the creation of a map function (which is standard practice for couchDB)

This version is working:

function (doc) {
  if (doc.type) {
    emit(doc.type)
  }
}.toString()

and it results in:

"function mapFunction(doc) {
  if (doc.type) {
    emit(doc.type);
  }
}"

However, I'm trying to change my function call to be more dynamic so I can pass a field through that the map function should be built on. With that in mind, I have a variable called field and I change my map function to this:

var field = '_id'
function (doc) {
  if (doc[field]) {
    emit(doc[field)
  }
}.toString()

the problem is, the string that's generated is like so:

"function mapFunction(doc) {
  if (doc[field]) {
    emit(doc[field]);
  }
}"

but I need to it to be:

"function mapFunction(doc) {
  if (doc['_id']) { //or doc._id (I don't mind)
    emit(doc['_id']);
  }
}"

Is it possible to achieve this?

Edit: Worse case scenario, I write it as a string and do it that way but would prefer to have it as a readable function.

6
  • That's broad... You'll need to parse and translate this stringified function, then. A simple replacement will cause issues. Commented Nov 17, 2016 at 19:35
  • 1
    Which is your objective? Commented Nov 17, 2016 at 19:37
  • I'm not sure what you mean, could you clarify please? Commented Nov 17, 2016 at 19:38
  • I can't be done the way you're thinking. It could be done creating the string and then making a string replacement with the value of that variable. Commented Nov 17, 2016 at 19:38
  • @acontell Right Ok, thanks. That's how I've done it in my work around but feel free to post an answer and I'll accept that as the solution. I just wanted to see if I could have my cake and eat it ;) Commented Nov 17, 2016 at 19:42

5 Answers 5

1

Perhaps a generator that takes a function, a variable name and a value and creates the string you want would do.

Something like

function functionGenerator(func, variable, value){
  var r = new RegExp(variable,'gi');
  return func.toString().replace(r, value);
}

function mapFunction(doc) {
  if (doc[field]) {
    emit(doc[field]);
  }
}

var map = functionGenerator(mapFunction, 'field','\'_id\'');

console.log(map);
  
 

Sign up to request clarification or add additional context in comments.

2 Comments

This is very dangerous, this replacement may break the code.
@FREEZE indeed, but i am assuming that the code is controlled by the OP. Plus it is just a pointer to an approach.
1

You could define a new method on the Function prototype that performs a toString, but allows to pass a collection of variables in an object format -- where each key is the variable to use. Those variables are injected in the string representation of the function, as var declarations right after the function body opens with a brace.

Each variable gets the JSON representation of its original value. This, of course, has some limitations, as not all values can be represented as JSON (cyclic references, objects with methods, ...etc). But it certainly works with primitive values such as strings:

Function.prototype.toStringWith = function(vars) {
    return this.toString().replace(/(\)\s*{)/, 
        '$1\n  var ' + Object.keys(vars)
            .map( key => key + ' = ' + JSON.stringify(vars[key]) )
            .join(',\n    ') + ';');
}

// Demo
var field = '_id'

var s = function mapFunction(doc) {
  if (doc[field]) {
    emit(doc[field])
  }
}.toStringWith({field}); // ES6 shortcut notation

console.log(s);

If you would have more variables that the function needs to "know", like size, weight, brand, then you call .toStringWith({field, size, weight, brand}), ...etc.

NB: solutions that search for the variable name in the function source and replace it with the literal value will need to be careful: the variable name could occur in a quoted string (between single quotes, doubles quotes), or template literals, or be part of a larger name, where it should not be replaced.

Comments

0

I think the easiest solution is a simple regexp.

var field = '_id';
var a = function (doc) {
  if (doc[field]) {
    emit(doc[field])
  }
}.toString();
             
console.log(a.replace(/field/gi, field));

Comments

0

As I've commented, that's broad because you need to parse and re-generate this stringified function. I can't believe a plugin will force someone to stringify a function.

Since it's broad to do that replacement from field to __id (because of other identifiers, etc.) you can only re-declare this field with its initial value in the stringified function (assign its value at the top).

Not related-advice: (Remind: var statement declares a variable in the entire scope, so the variable can be assigned before the var statement is present, too.)

//////////////////////////////////////////////
//////////////// References //////////////////
//////////////////////////////////////////////
var _stringify = JSON.stringify

//////////////////////////////////////////////
//////////////// Variables  //////////////////
//////////////////////////////////////////////
var field = '__id'
/* Store the variables to be copied in the top here */
var locals = { field: field }

/* String to contain the variables */
var stringified_locals = '',
    // thanks to this var length notation
    // we'll separate the variables by commas
    len = 0

/* Calculate the length of vars */
for (let prop in locals)
    ++len

/* Also useful for the variable separation */
i = 0; var i

/* Only declare the 'var' statement if there's at least
 * ONE var */
if (len)
    stringified_locals = 'var '

/* Now generate the string of variables */
for (let prop in locals) {
    let value = _stringify(locals[prop])
    stringified_locals += prop + ' = ' + value

    /* Add comma separator if neccessary */
    if (i++ < (len - 1))
        stringified_locals += ', '
}

/* And the complete stringified function */
stringified_locals + '\r\n' +
(function (doc) {
    if (doc.type) {
        emit(doc.type)
    }
}).toString()

Got result:

`var field = "__id"
function (doc) {
    if (doc.type) {
        emit(doc.type)
    }
}`

Comments

0

You could do this:

"(function() {\n" +
    "var field = " + JSON.stringify(field) + ";\n" +
    "return " + mapFunction.toString() + ";" +
"})()"

Caveat: There are rare cases where JSON.stringify doesn't produce valid javascript. I don't know exactly what those cases are or whether it would be possible for a malicious user to take advantage of them in some way. (Do you trust whoever is supplying the value of field?)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.