Given a database scheme like the following:
{
"lessons": {
"subjectId1": {
"lessonId2": { ... },
"lessonId4": { ... },
"lessonId6": { ... }
},
"subjectId2": {
"lessonId1": { ... },
"lessonId3": { ... },
"lessonId5": { ... },
"lessonId7": { ... }
}
}
}
How do I retrieve the id(s) of any subject(s) that include a given lessonId?
I came up with a function like this:
const refWithParent = database.ref("lessons");
const snapshot = await refWithParent.orderByChild(lessonId).limitToLast(1).once("value");
let firebaseId = null;
if (snapshot.val() !== null) {
snapshot.forEach(entry => {
firebaseId = entry.key;
return true;
});
}
return firebaseId;
This does work but I can't create an index in the database as the children are dynamically created.
Is there any easier way to retrieve the id?
As I understood you want to retrieve one index of any subject that contains a lesson with a specific id. If so, you can add a node "contains" to each subject into which you will put a key/value of the form: lessonId: true.
{
"lessons":{
"subjectId1":{
"contains":{
"lessonId2":true,
"lessonId4":true,
"lessonId6":true
},
"lessonId2":{ ... },
"lessonId4":{ ... },
"lessonId6":{ ... }
},
"subjectId2":{
"contains":{
"lessonId1":true,
"lessonId3":true,
"lessonId5":true,
"lessonId7":true
},
"lessonId1":{ ... },
"lessonId3":{ ... },
"lessonId5":{ ... },
"lessonId7":{ ... }
}
}
}
And then make a query (assuming lessonID is a variable in your code):
refWithParent.orderByChild(`contains/${lessonID)`).equalTo(true).limitToFirst(1).once('value');
and then check the obtained snaphost as follows:
if (snapshot.val() !== null)
{
let subjectId = snapshot.key;
//TODO something with subjectId...
}
Note, you must always add corresponding pair to "contains" node when you add a lesson to a subject. The same with removing.
P.S. If you want to obtain IDs of all subjects that contain given lesson, then simply remove .limitToFirst(1) from the query. That's all.
In Nosql like firebase you can denormalise or form your database as you want. I would advise that you make all your lesson names have a concatenation of it's subject when you creating them in order to avoid your querying of the database.
The codes below can help in getting a unique lesson id with a concatenation of the subject name before you create them.
let newId = database.ref("lessons").ref.push().key; //<-- get a new unique id
let newlessonname = 'Subject2_' + newId;
So your db would look like this
{
"lessons": {
"subjectId1": {
"subjectId1_lessonId2": { ... },
"subjectId1_lessonId4": { ... },
"subjectId1_lessonId6": { ... }
},
"subjectId2": {
"subjectId2_lessonId1": { ... },
"subjectId2_lessonId3": { ... },
"subjectId2_lessonId5": { ... },
"subjectId2_lessonId7": { ... }
}
}
}
Alternatively
You can include your Subjectid in the object of each lesson like this
{
"lessons": {
"subjectId1": {
"lessonId2": {subjectid:"subjectId1" ... },
"lessonId4": { ... },
"lessonId6": { ... }
},
"subjectId2": {
"lessonId1": { ... },
"lessonId3": { ... },
"lessonId5": { ... },
"lessonId7": { ... }
}
}
}
Related
So the goal is to have included only those endpoints (and its methods e.g. get, post...) which are defined in the configuration file.
Example structure object that holds all the endpoints.
et swaggerApis = {
header: {
propertyHeader: "valueHeader"
},
blocks: [
{
tags: ["Tenant & User"],
paths: {
"/tenants": {
post: {
property: "value"
},
get: {
property: "value"
}
},
"/tenants/{id}": {
post: {
property: "value"
},
get: {
property: "value"
},
delete: {
property: "value"
}
}
}
}
]
};
Example of the configuration file that holds only those endpoints and its methods we want to have included in the final object.
const CONFIG = {
api: {
include: {
"/tenants/{id}": ["get"]
}
}
};
So far here is my second version of the JavaScript code that works but introduces a high cyclometric complexity and is hard to read. I'm pretty new to JavaScript and looking a way not just to improve this code.
function includeEnpointsByConfig(data) {
for (let blockItem of data.blocks) {
for (let path in blockItem.paths) { //console.log(blockItem.paths[path])
let result = setMethodsOfEndpoint(path, blockItem.paths[path]);
if (result === 'undefined') {
delete blockItem.paths[path] // if the config does not contain, remove
} else {
blockItem.paths[path] = result;
}
}
}
return data;
}
function setMethodsOfEndpoint(path, value) {
let newMethods = {};
for (let confPath in CONFIG.api.include) {
if (path === confPath) { // match endpoint in config and swaggerApis object
if (CONFIG.api.include[confPath].length > 0) { // if array in config is not empty , filter
for (let c of CONFIG.api.include[confPath]) { //console.log(c); // get
for (let v in value) {// properties of object tenants/{id} => {get{}, post{}}
if (v === c) {
newMethods = { ...newMethods, [v]: value[v] };
}
}
}
} else {// if array in config is empty , return param "value" from setMethodsOfEndpoint so we will include all methods of endpoint
return value;
}
} else {
return 'undefined'
}
}
if (Object.keys(newMethods).length !==0) { // if in the config is in the array (nothing that match with swaggerEndpoints e.g. typo get --> gte)
return newMethods
} else {
return value;
}
}
console.log(includeEnpointsByConfig(swaggerApis));
Code can be found also here
https://codesandbox.io/s/blazing-worker-1emzl?file=/src/index2.js
I believe there is a way to do it much easier, cleaner and more effective.
Thank you
With some creative usage of Array.prototype.forEach(), Object.keys() and Object.entries():
swaggerApis.blocks.forEach(block => {
Object.entries(block.paths).forEach(([path, methods]) => {
if (!CONFIG.api.include[path]) {
delete block.paths[path];
} else {
Object.keys(methods).forEach(method => {
if (!CONFIG.api.include[path].includes(method)) {
delete methods[method];
}
});
}
});
});
Complete snippet:
const swaggerApis = {
header: {
propertyHeader: "valueHeader"
},
blocks: [
{
tags: ["Tenant & User"],
paths: {
"/tenants": {
post: {
property: "value"
},
get: {
property: "value"
}
},
"/tenants/{id}": {
post: {
property: "value"
},
get: {
property: "value"
},
delete: {
property: "value"
}
}
}
}
]
};
const CONFIG = {
api: {
include: {
"/tenants/{id}": ["get"]
}
}
};
swaggerApis.blocks.forEach(block => {
Object.entries(block.paths).forEach(([path, methods]) => {
if (!CONFIG.api.include[path]) {
delete block.paths[path];
} else {
Object.keys(methods).forEach(method => {
if (!CONFIG.api.include[path].includes(method)) {
delete methods[method];
}
});
}
});
});
console.log(swaggerApis);
I am using json-rule-engine .
https://www.npmjs.com/package/json-rules-engine
I am having a student list which have name and their percentage, Also I have business rule the percentage should be greater thank or equal to than 70 . so I want to print all students name those have percentage more than 70
here is my code
https://repl.it/repls/AlienatedLostEntropy#index.js
student list
const students = [
{
name:"naveen",
percentage:70
},
{
name:"rajat",
percentage:50
},
{
name:"ravi",
percentage:75
},
{
name:"kaushal",
percentage:64
},
{
name:"piush",
percentage:89
}
]
rule
engine.addRule({
conditions: {
all: [
{
fact: "percentage",
operator: "greaterThanInclusive",
value: 70
}
]
},
onSuccess(){
console.log('on success called')
},
onFailure(){
console.log('on failure called')
},
event: {
type: "message",
params: {
data: "hello-world!"
}
}
});
code
https://repl.it/repls/AlienatedLostEntropy#index.js
any update
The json-rules-engine module takes data in a different format. In your Repl.it you have not defined any facts.
Facts should be:
let facts = [
{
name:"naveen",
percentage:70
},
[...]
Also, the module itself doesn't seem to process an array of facts. You have to adapt it to achieve this. This can be done with:
facts.forEach((fact) => {
engine
.run(fact)
[...]
Finally, the student data is found inside the almanac. You can get these values with: results.almanac.factMap.get('[name|percentage|age|school|etc]').value
Here is the updated Repl.it: https://repl.it/#adelriosantiago/json-rules-example
I might have submitted a completely unrelated answer, but here goes. Since the students object is an array, you could just loop through it and then use an if else statement.
for (let i = 0; i < students.length; i++) {
if (students[i].percentage >= 70) {
console.log(students[i].name);
}
}
Sorry if this is incorrect!
Here is a working example.
Counting success and failed cases
const { Engine } = require("json-rules-engine");
let engine = new Engine();
const students = [
{
name:"naveen",
percentage:70
},
{
name:"rajat",
percentage:50
},
{
name:"ravi",
percentage:75
},
{
name:"kaushal",
percentage:64
},
{
name:"piush",
percentage:89
}
]
engine.addRule({
conditions: {
all: [{
fact: 'percentage',
operator: 'greaterThanInclusive',
value: 70
}]
},
event: { type: 'procedure_result'}
})
let result = {success_count : 0 , failed_count : 0}
engine.on('success', () => result.success_count++)
.on('failure', () => result.failed_count++)
const getResults = function(){
return new Promise((resolve, reject) => {
students.forEach(fact => {
return engine.run(fact)
.then(() => resolve())
})
})
}
getResults().then(() => console.log(result));
I have the following json data:
[
{
"product":{
"id_product":"1"
}
"id_productParent":""
},
{
"product":{
"id_product":"2"
},
"id_productParent":"1"
},
{
"product":{
"id_product":"3"
},
"id_productParent":"2"
},
{
"product":{
"id_product":"4"
},
"id_productParent":"3"
}
]
and I need to return an Array with all the descendant of a product, where the descendant of a product is defined (in pseudocode) like this
descendant(x) = return (p=> p.id_productParent==x.product + descendant(p)).
How can I solve this with typescript?
Here is a baseline for such senario :
var g=[
{
"product":{
"id_product":"1"
},
"id_productParent":""
},
{
"product":{
"id_product":"2"
},
"id_productParent":"1"
},
{
"product":{
"id_product":"3"
},
"id_productParent":"2"
},
{
"product":{
"id_product":"4"
},
"id_productParent":"2"
}
]
function eachRecursive(id)
{
var t=g.filter(i=>i.id_productParent==id);
if (!t.length)
{
console.log(id)
return id;
}
else
t.forEach(i=>{
eachRecursive(i.product.id_product);
})
}
eachRecursive(2) //3,4
I solved with the following solution.
Let products and id be respectful the array of products and the id of the product for which I want to find his descendants, I filter it like this:
...
this.g = this.products.filter(p => this.isDescendant(p, id));
...
isDescendant(p, id){
if(p.id_productParent == id)
return true;
if(!p.id_productParent)
return false;
return this.isDescendant(this.products.filter(p => p.product.id_product == p.id_productParent)[0], id);
}
Now in this.g I have all the descendants of id
I have two filters in my custom panel in Kibana3:
request = request.query(
$scope.ejs.FilteredQuery(
boolQuery,
filterSrv.getBoolFilter(filterSrv.ids()) // has to be merged with BoolFilter2
))
.size($scope.panel.size);
This works for either of the two filters but I want to filter with the two objects but I dont know how to merge them. Can anyone help?
Kibana 3 filters can be set to the "either" mode instead of "must".
This translates into filters that look like this:
{
"filter": {
"bool": {
"should": [
{
"fquery": {
"query": {
"query_string": {
"query": "test1"
}
},
"_cache": true
}
},
{
"fquery": {
"query": {
"query_string": {
"query": "test2"
}
},
"_cache": true
}
}
]
}
}
}
Here is the filterSrv code that handles that in Kibana 3: https://github.com/elasticsearch/kibana/blob/3.0/src/app/services/filterSrv.js#L144
this.getBoolFilter = function(ids) {
var bool = ejs.BoolFilter();
// there is no way to introspect the BoolFilter and find out if it has a filter. We must keep note.
var added_a_filter = false;
_.each(ids,function(id) {
if(dashboard.current.services.filter.list[id].active) {
added_a_filter = true;
switch(dashboard.current.services.filter.list[id].mandate)
{
case 'mustNot':
bool.mustNot(self.getEjsObj(id));
break;
case 'either':
bool.should(self.getEjsObj(id));
break;
default:
bool.must(self.getEjsObj(id));
}
}
});
// add a match filter so we'd get some data
if (!added_a_filter) {
bool.must(ejs.MatchAllFilter());
}
return bool;
};
this.getEjsObj = function(id) {
return self.toEjsObj(dashboard.current.services.filter.list[id]);
};
You could modify that function to take an optional argument that sets the mandate to either.
I have the following document:
{
"gameName":"Shooter",
"details":[
{
"submitted":1415215991387,
"author":"XYZ",
"subPlayer":{
"members":{
"squad1":[
{
"username":"John",
"deaths":0
}
]
},
"gameSlug":"0-shooter"
}
}
],
"userId":"foL9NpoZFq9AYmXyj",
"author":"Peter",
"submitted":1415215991608,
"lastModified":1415215991608,
"participants":[
"CXRR4sGf5AdvSjdgc",
"foL9NpoZFq9AYmXyj"
],
"slug":"1-shooterConv",
"_id":"p2QQ4TBwidjeZX6YS"
}
... and the following Meteor method:
Meteor.methods({
updateDeaths: function(gameSlug, user, squad) {
Stats.update({details.subPlayer.gameSlug: gameSlug}, ...}); // ???
}
});
My goal is to update the field deaths. The method has the argument user which is the user object (username = user.username). Furthermore, the argument squad is the squad name as string, e.g. squad1.
How can I do this?
Any help would be greatly appreciated.
You should use something like this:
db.collection.update(
{'userId':'foL9NpoZFq9AYmXyj', 'details.subPlayer.gameSlug':'0-shooter'},
{'$set':
{'details': [{
'subPlayer': {
'members': {
'squad1':[{'username':'John','death':1}]
}
}
}]
}
}
)
UPD:
Also, maybe a better way is to use find().snapshot().forEach(). But I don't know how Meteor supports it. Example:
Stats.find({'userId':'foL9NpoZFq9AYmXyj', 'details.subPlayer.gameSlug':'0-shooter' }).map(function (e) {
e.details.forEach(function (d) {
var squad = 'squad1';
d.subPlayer.members[squad].forEach(function (s) {
s.deaths = 10000;
});
});
Stats.update({'_id': e._id}, e);
});