How do I reference a local variable from within a $where query in Mongoose?
For example:
var testName = 'Some Dude';
Cab.$where('this.name === testName').exec((_err, cabs) => {
// Do something...
});
If testName will be decided on runtime, how should it be referenced in the query?
My actual query is a bit complicated so I need to execute a Javascript expression.
I've tried using this.testName which didn't work either.
One way is to use ES6 template literals, e.g.:
var testName = 'Some Dude';
Cab.$where(`this.name === ${testName}`).exec((_err, cabs) => {
// Do something...
});
Related
I'm trying to write a small Vue plugin that just returns the env variable content. Similar to PHPs env() method.
Context: I need a url in multiple components, obviously I put this in the .env file because it could possibly change in the future. You cannot use process.env inside of the components template though because: Property or method "process" is not defined on the instance but referenced during render.
This is what I've tried to far:
I call the prototype function in a mounted hook like this:
console.log('test: ' + this.env('MIX_COB_PARTNER_URL'));
import _get from "lodash/get";
const Env = {
// eslint-disable-next-line
install(Vue, options) {
Vue.prototype.env = function (name) {
console.log(name); // "MIX_VARIABLE"
console.log(typeof name); // string
// variant 1
return process.env[name]; // undefined
// variant 2
return process.env["MIX_VARIABLE"]; // "works" but isnt dynamic obviously
// variant 3-5 with lodash
return _get(process.env, name); // undefined
// or
return _get(process, 'env[' + name + ']'); // undefined
// or
return _get(process.env, '[' + name + ']'); // undefined, worth a try lol
// variant 6
const test = "MIX_VARIABLE"
return process.env[test]; // again just for the sake of trying
}
}
}
export default Env;
I know that usually object[variable] works fine. But somehow it doesnt in this case, maybe it has to do with the fact that the process.env is empty when accessed without a key and the [name] ist as "direct" as .MIX_VARIABLE would be.
Does this just not work?
Ive researched and found some people (e.g. here on SO) suggesting this type of accessing (process.env[variable]) so I'm not sure.
I believe your environment variable name needs to start with VUE_APP_ in order to be picked up by Vue's environment variable processing. So, maybe rename MIX_VARIABLE to VUE_APP_MIX_VARIABLE? (See https://cli.vuejs.org/guide/mode-and-env.html#environment-variables)
I have some problems with a map reduce I tried to do in MongoDB. A function I defined seems to not be visible in the reduce function.
This is my code:
function getName(user_id){
var users = db.users.aggregate({$project:{"_id":"$_id", "name":"$name"}});
users.forEach((it) => {if (user_id == it._id) return it.name;});
return "user not found";
}
var mapFunc = function(){ emit(this.user_id, this.book_id) };
var reduceFunc = function(key, values){return getName(key);};
db.booksToRecover.mapReduce(mapFunc, reduceFunc, {out:'users_to_recover_books_from'});
This is what I get:
The function was defined in the locally running javascript instance, not the server.
In order for that function to be callable from the server you will need to either predefine it there or include the definition inside the reduce function.
But don't do that.
From the reduce function documentation:
The reduce function should not access the database, even to perform read operations.
Look at using aggregation with a $lookup stage instead.
I use a JSON file for common phrases so I don't have to type them and maybe in the future they can be translated. So for example in my main code I want to say You don't have the permission to use ${command_name}. This works perfectly fine hardcoded into my .js file but ultimately I want this to be in a JSON file, which does not allow any variables to be inserted.
Does anyone know a solution to my problem?
EDIT: Thanks for the suggestions. I guess string.replace would be my best option here. Wish there was some built in feature that'd convert variables in a JSON string to variables declared in that JS file.
You cannot treat template string literals in JSON files like in Javascript "code". You said it yourself. But: You could use a template engine for this - or just simple String.replace().
Example for a template engine: https://github.com/janl/mustache.js
With Mustache (as an example) your code will look like this
var trans = {
command_name: "dump"
};
var output = Mustache.render("You don't have the permission to use {{command_name}}", trans);
With simple String.replace():
var str = "You don't have the permission to use %command_name%";
console.log(str.replace('%command_name%', 'dump'));
You can simply use placeholders. The following function replaces the placeholders with user-defined values:
const messages = {
msgName: 'Foo is :foo: and bar is :bar:!'
}
function _(key, placeholders) {
return messages[key].replace(/:(\w+):/g, function(__, item) {
return placeholders[item] || item;
});
}
Usage:
_('msgName', { foo: 'one', bar: 'two' })
// "Foo is one and bar is two!"
It's just an example. You can change the placeholders style and the function behavior the way you want!
You can use config npm module and separate your JSON files according to your environment.
./name.json
{
command: "this is the output of 'command'"
}
./Node.js
cost names = require('./name.json');
console.log('name === ', name.command);
// name === this is the output of 'command'
So the main challenge is getting separated file with string constants when some of them being parametrizable, right?
JSON format itself operates on strings(numbers, booleans, lists and hashmap) and knows nothing about substitution and parameters.
You are also unable to use template strings like you don't have permission to do ${actionName} since template strings are interpolated immediately.
So what can you do?
Writing your own parser that takes config data from JSON file, parse a string, find a reference to variable and substitute it with value. Simple example:
const varPattern = /\${([^{}]+)}/g;
function replaceVarWithValue(templateStr, params) {
return templateStr.replace(varPattern, (fullMatch, varName) => params[varName] || fullMatch);
}
or you can use any npm package aimed on localization like i18n so it would handle templates for you
Basically you can implement a function parse which, given a text and a dictionary, it could replace any ocurrence of each dictionary key:
const parse = (template, textMap) => {
let output = template
for (let [id, text] of Object.entries(textMap)) {
output = output.replace(new RegExp(`\\$\{${id}}`, 'mg'), text)
}
return output
}
const textMap = {
commandName: 'grep',
foo: 'hello',
bar: 'world'
}
const parsed = parse('command "${commandName}" said "${foo} ${bar}"', textMap)
console.log(parsed)
BTW, I would suggest you that you should use some existing string templating engine like string-template to avoid reinventing the wheel.
I'm using Hogan.js, which is compatible with the Mustache spec.
And im having trouble implementing a solid way of doing pluralization.
I would like to keep using Hogan and use http://i18next.com/ for i18n handling
doing something like this works for the simple cases
tpl:
{{#plural(count)}}
I have {{count}} apples!
{{/plural(count)}}
data:
{
count: 2,
'plural(count)': function () {
return function () {
return _t[arguments[0].trim()][this['count']]
}
}
}
this requires parsing/scanning/rendering in seperate steps to be able to generate all of the required plural methods (plural(key.val) etc.) but thats fine, it only needs to be done once, at server boot.
this breaks on things like
{{#plural(key.nested)}}
that would match if the data looked like
{
'plural(key': {
'val)': ...
}
}
this also requires me to manually lookup the values from the context, not a major problem but there are some cases with lambda's/partials that might be impossible to resolve
for the default translation mappings, thing are a lot less complex, and thats easy to handle
Ok found the way I think is best to handle this problem:
var tpl_data = fs.readFileSync('./tpl/test.hjs', 'utf8');
var scan = Hogan.scan(tpl_data);
var tree = Hogan.parse(scan);
var gen = Hogan.generate(tree, tpl_data, {asString:false});
var out = gen.render(data);
alter the tree, replacing all tag keys to i18n
where the n matches your pattern /i18n .+/
I use {{#i18n {count: count, text: 'I have <%count%> apples!'} }} and the like to add the options for i18next
so i match all n's starting with i18n
add the i18n to Hogan.codegen
Hogan.codegen.i18n = function (node, context) {
context.code += 't.b(t.v(t.i18n("' + esc(node.n) + '",c,p,0)));';
}
add the i18n method to the prototype of Hogan.Template
Hogan.Template.prototype.i18n = function (key, ctx, partials, returnFound) {
//here the ctx is an array with from right to left the render scopes
// most right is the most inner scope, most left is the full render data
//1. get the config from the key,
//2. get the values out of the scope
//3. get the values for the translation
//4. lookup the translation, and repleace the values in the translation
//5. return the translated string
};
note that inside the Hogan.Template.prototype.i18n you can access all of the template's methods
I'm kind of confused on how to use closures in mongodb shell.
I want to create a function that i can use exclusively during development to quickly look up a document by a part of it's _id.
The function should return a $where selector that does the necessary matching.
I wanted to write it like this:
var id = function(pattern, selector) {
return Object.extend({
$where: function() {return (this._id + "").indexOf(pattern) != -1;}
}, selector);
};
But when i try it i get the following error:
db.mycollection.find(id("ab1"));
error: {
"$err" : "JavaScript execution failed: ReferenceError: pattern is not defined near ').indexOf(pattern) ' ",
"code" : 16722
}
Invoking $where manually does seem to work oddly enough:
id("ell").$where.call({_id: "hello"}); // true
I could only think of this as a solution to make it work, but this is obviously terrible.
To clarify: This method with new Function works fine, but i don't like it as the above method should work as well.
var id = function(pattern, selector){
return Object.extend({
$where: new Function("return (this._id + '').indexOf('" + pattern + "') != -1;")
}, selector);
};
Why is my closure not working, do closures work in mongodb shell ?
Bonus question: Can i register this function somewhere in a user profile to load automatically ?
It appears that the MongoDB shell serializes (in this case, string-ifies) $where function objects in order to send them to the server.
A function in JavaScript is much more than its functional code -- it's part of a closure, which includes both the function code itself and the function's variable referencing environment for accessing non-local variables. After the function is serialized, it gets unserialized and executed with different referencing environment. When the function tries to reference pattern, it's asking for pattern in a totally different referencing environment, which does not have a pattern variable.
That's why your literal string function works. That function contains the actual value of pattern, not the variable identifier pattern.
See this MongoDB bug, which explains:
That would work if the function was being evaluated locally, but the closure isn't sent over automatically.
In babble it used to be, but not in the shell.
This suggests that your code may work outside of the shell, but I don't know enough about Mongo internals to say for sure.