Three-Way Relationship in Firebase - json

I've been learning a lot about denormalised data over the past few months, but I wanted to know if the following is possible in a flattened architecture world. I know how to handle two-way relationships in Firebase, but what about a three-way relationship. Let me explain...
I have 5 items in my database, services, providers, serviceAtProvider, reviews and users. I want to be able to add providers to services and vice versa.
I'd also like there to be a specific page for a provider inside a service, and for there to be reviews linked to the provider at that specific service. The page url might look like this (site.com/serviceId/providerId). The rating is unique to that providerId inside of that serviceId – you can't rate serviceIds or providerIds separately.
I'm not sure how to go about creating such a complex relationship. How would I join the serviceId and providerId in that serviceAtProvider item?
This is what I've got so far:
"services": {
"service1": {
"name": "Hernia Repair",
"providers": {
"provider1": true,
"provider2": true
}
}
},
"providers": {
"provider1": { "name": "The Whittington Hospital" },
"provider2": { "name": "Homerton Hospital" }
},
"serviceAtProvider": {
"service1AtProvider1": { "rating": 4 },
"service1AtProvider2": { "rating": 3 }
},
"reviews": {
"service1AtProvider1": {
"review1": {
"body": "A review from user 1",
"user": "user1"
}
},
"service1AtProvider2": {
"review1": {
"body": "A review from user 2",
"user": "user2"
}
}
},
"users": {
"user1": { "name": "Ben Thomas" },
"user2": { "name": "Beatrix Potter" }
}
I don't know how to create that serviceAtProviderjoin, or how would I go about accessing the service1.name, provider1.name, service1AtProvider1.rating, reviews.service1AtProvider1 on one page. Can anyone explain how to do this?
Also, are there any best practices I should follow?
Any help is appreciated. Thanks in advance!
UPDATE
{
"availableServices": {
"service1": { "name": "Hernia Repair" },
"service2": { "name": "Chemotherapy" }
},
"services": {
"provider": {
"name": "The Whittington Hospital",
"service": {
"service1": {
"rating": 4,
"reviewId1": true
},
"service2": {
"rating": 3,
"reviewId2": true
},
}
}
},
"reviews": {
"reviewId1": {
"review1": {
"rating": 4,
"body": "A review from user 1",
"user": "user1"
}
}
},
"users": {
"user1": { "name": "Raphael Essoo-Snowdon" },
"user2": { "name": "Sharlyne Slassi" }
}
}

I would start by making the data structure a bit simpler and more direct. It's hard to determine the correct data structure for your needs without a detailed use case. I'll do my best to make some generic assumptions here. You'll have to adapt as necessary.
{
"service": {
"service1": { "name": "Foo Service" },
...
},
"provider": {
"provider1": { name: "Foo Place" },
...
},
"ratings": {
"service1": { // service id
"provider1": { // provider id
"average_rating": 4
},
...
},
...
},
"reviews": {
"service1": { // service id
"provider1": { // provider id
"user": "user1",
"rating": 4
},
...
},
...
},
"user": {
"user1": { "name": "Foo Bar" },
...
}
}
Now, to look up the providers who offer a given service, and grab their reviews, I would do the following:
var ref = new Firebase(...);
ref.child('ratings/service1').on('child_added', function(reviewSnap) {
console.log(
'Provider ' + reviewSnap.key(),
'Average rating ' + reviewSnap.val().average_rating
);
});
Joining in the names of the services and providers could be done in several ways. Here's a manual technique:
var ref = new Firebase(...);
ref.child('ratings/service1').on('child_added', accumulateReview);
function accumulateReview(reviewSnap) {
var reviewData = reviewSnap.val();
var reviewid = reviewSnap.key();
fetchService(reviewSnap.parent().key(), function(serviceSnap) {
loadRec('provider', reviewSnap.key(), function(providerSnap) {
console.log('Provider: ', providerSnap.key(), providerSnap.val().name);
console.log('Service: ', serviceSnap.key(), serviceSnap.val().name);
console.log('Average rating: ', reviewData.average_rating);
});
});
}
var serviceCache = {};
function fetchService(serviceid, done) {
// demonstrates creating a local cache for things that will be
// looked up frequently
if( !serviceCache.hasOwnProperty(serviceid) ) {
cacheService(serviceid, done);
}
else {
done(serviceCache[serviceid]);
}
}
function cacheService(serviceid, done) {
loadRec('service', function(ss) {
serviceCache[serviceid] = ss;
fetchService(serviceid, done);
});
}
function loadRec(type, key, done) {
ref.child(type).child(key).once('value', done);
}
I could also automate some of this process with Firebase.util's NormalizedCollection:
var ref = new Firebase(...);
var nc = Firebase.util.NormalizedCollection(
[ref.child('reviews/service1'), 'review'],
ref.child('provider'),
ref.child('user')
)
.select('review.rating', {key: 'provider.name', alias: 'providerName'}, {key: 'user.name', alias: 'userName'})
.ref();
nc.on('child_added', function(snap) {
var data = snap.val();
console.log('Provider', data.providerName);
console.log('User', data.userName);
console.log('Rating', data.rating);
});
Note that nothing here is set in stone. This is how I would approach it. There are probably dozens of structures at least as good or better.

Related

Sequelize Insert Cascade

im trying insert in multiple tables.
let me explain User create a new client, client insert id into bill - (idClient) table
this is the payload
{
"name": "Evelyn",
"lastName": "Doe",
"phone": "4534534",
"email": "eve#hotmail.com",
"identification": "xxxxx",
"services": [1, 2, 3],
"bill":{
"description": "New project"
}
}
the insert
_service.create = async (client) => {
const { description } = client.bill;
try {
const data = await Client.create(
{ client, bill: { description: description } },
{ include: { model: Bill } }
);
return data.id;
} catch (err) {
handleError = err.hasOwnProperty("errors")
? err.errors.map((error) => error.message)
: err;
throw new Error(handleError);
}
};
but im getting this error
{
"name": "Error",
"message": "ReferenceError: description is not defined"
}
and yes, bill table has that column.
the relation
Client.hasOne(models.Bill, { foreignKey: "idClient" });
so, im stuck, i read the documentation and i trying to do the same way as they do but i dont know what i doing wrong
https://sequelize.org/master/manual/creating-with-associations.html
I already did, i dont know if the best way
i modified the payload
{
"name": "Evelyn",
"lastName": "Doe",
"phone": "4534534",
"email": "eve#hotmail.com",
"identification": "xxxxx",
"description": "new Proyect",
}
the insert query, change bill to Bill as my table name and then add description value
const { description } = client;
const data = await Client.create(
{ ...client, Bill: { description } },
{ include: { model: Bill } }
);
and the result
{
"id": 6,
"name": "Evelyn",
"lastName": "Doe",
"phone": "4534534",
"email": "eve#hotmail.com",
"identification": "xxxxx",
"idStatus": 2,
"createdAt": "2020-11-13T08:52:37.000Z",
"updatedAt": "2020-11-13T08:52:37.000Z",
"Bill": {
"id": 4,
"idClient": 6,
"totalAmount": null,
"description": "new Proyect",
"idStatus": 2,
"createdAt": "2020-11-13T08:52:37.000Z",
"updatedAt": "2020-11-13T08:52:37.000Z"
}
}

Mapping data that contains dots in React

I am trying to map the data in React that is coming from the API but I am having problems mapping the object that contains dots for example this: name.en_US.
What is the proper way to map this object and keeping the data structure that I have?
I am getting the date in this format from the API:
{
"user": "User",
"employeeId": "0000",
"businessCustomer": "customer",
"endCustomer": {
"name": "",
"address": "",
"place": ""
},
"device": {
"shipmentIds": "23",
"name.en_US": "wasi",
"name.fi_FI": " masi"
},
"task": {
"time": "2019-02-10T16:55:46.188Z",
"duration": "00:00:24",
"sum": "75€"
}
},
And then I am trying to map it using the following code.
const {
user,
employeeId,
businessCustomer,
endCustomer,
device,
task
} = task;
const{
endCustomerName,
address,
place
} = endCustomer;
const {
shipmentIds,
names
} = device;
const{
en_US,
fi_FI
} = names;
const {
time,
duration,
summa
} = task;
const data = {
"user": "User",
"employeeId": "0000",
"businessCustomer": "customer",
"endCustomer": {
"name": "",
"address": "",
"place": ""
},
"device": {
"shipmentIds": "23",
"name.en_US": "wasi",
"name.fi_FI": " masi"
},
"task": {
"time": "2019-02-10T16:55:46.188Z",
"duration": "00:00:24",
"sum": "75€"
}
};
const { device } = data;
const {
shipmentIds,
'name.en_US': name_en_US,
'name.fi_FI': name_fi_FI
} = device;
const nameUS = device['name.en_US'];
console.log(name_en_US, nameUS);
Use [ ] notation like, device['name.en_US'] .
You can destructure your propery as #Vishnu mentioned, or you could also destructure it by providing a valid key name
const {
shipmentIds,
'name.en_US': name_en_US,
'name.fi_FI': name_fi_FI
} = device;
And then you could access your variable with name_en_US.

Set next step for the waterfall dialogue in Microsoft BotBuilder NodeJS SDK

I am using Microsoft Bot Framework for my facebook messenger bot. I want to load the dialog data from json files instead of hard coding in the js file. I would like to configure the next step in the dialog, based on result from the "current" step, which is part of the json file configuration, something like this.
{
"name": "welcome",
"type": "waterfall",
"steps": [
{
"id": 0,
"data": [
{
"type": "text",
"value": "Hey, It's nice to meet you."
},
{
"type": "quickReplies",
"value": "What do you want to do next?",
"options": [
{
"text": "some option 1",
"value": "option1"
},
{
"text": "some option 2",
"value": "option2"
}
]
}
],
"next": [
{
"result": "option1",
"action": "goto step 2"
},
{
"result": "option2",
"action": "goto step 5"
}
]
}
]
}
I would like to process all the incoming messages and respond with correct dialog or correct step in the dialog for the user.
I am trying something like this;
handleMessage = function (session) {
var step = session.dialogData["BotBuilder.Data.WaterfallStep"] || 0;
// check response data from previou step and identify the next step.
// set the waterfall step id
session.dialogData["BotBuilder.Data.WaterfallStep"] = 2;
session.send("Hello");
}
var bot = new builder.UniversalBot(connector, function (session) {
handleMessage(session);
})
.set('storage',tableStorage);
With this code, I am always getting step as zero for session.dialogData["BotBuilder.Data.WaterfallStep"] even after setting this to a different number.
Also, as soon as I set the waterfall step number, all other state data that is stored in my table storage for this conversation is gone.
Storage data before setting waterfall step:
{
"BotBuilder.Data.SessionState": {
"callstack": [
{
"id": "*:/",
"state": {
"BotBuilder.Data.WaterfallStep": 0
}
},
{
"id": "*:welcome",
"state": {
"BotBuilder.Data.WaterfallStep": 1
}
},
{
"id": "BotBuilder:prompt-text",
"state": {
"options": {
"prompt": {
"type": "message",
"agent": "botbuilder",
"source": "facebook",
"address": {
"id": "mid.$cAAAlr-0LRH9niO21L1hV6hs83GuJ",
"channelId": "facebook",
"user": {
"id": "XXXX",
"name": "XXXX"
},
"conversation": {
"isGroup": false,
"id": "XX"
},
"bot": {
"id": "XXX",
"name": "XXX"
},
"serviceUrl": "https://facebook.botframework.com"
},
"text": "what do you want to next"
//ignored for simplicity
},
"promptAfterAction": true,
"libraryNamespace": "*"
},
"turns": 0,
"lastTurn": 1517594116372,
"isReprompt": false
}
}
],
"lastAccess": 1517594112740,
"version": 0
}
}
After I set the waterfall step:
{
"BotBuilder.Data.SessionState": {
"callstack": [
{
"id": "*:/",
"state": {
"BotBuilder.Data.WaterfallStep": 2
}
}
],
"lastAccess": 1517602122416,
"version": 0
}
}
Interestingly the step number is saved to the database (but in session state) but my "session" variable do not have this value anywhere. Also, even after configuring custom state service, the serviceUrl is still https://facebook.botframework.com which I thought is the default state service used if there is no state service set for the bot.
Per your code, as your bot actually contains only one waterfall step: handleMessage(session);, which raised your issue. You can consider to create multiple dialogs from json configration instead of complex waterfall steps.
Here is my quick test, for your information:
const json = `
[{
"name": "welcome",
"type": "waterfall",
"steps": [
{
"id": 0,
"data": [
{
"type": "text",
"value": "Hey, It's nice to meet you."
},
{
"type": "quickReplies",
"value": "What do you want to do next?",
"options": [
{
"text": "some option 1",
"value": "option1"
},
{
"text": "some option 2",
"value": "option2"
}
]
}
],
"next": [
{
"result": "option1",
"action": "dialog2"
},
{
"result": "option2",
"action": "dialog3"
}
]
}
]
},{
"name":"dialog2",
"type": "waterfall",
"steps": [
{
"data": [
{
"type": "text",
"value": "Hey, this is dialig2."
}]
}
]
},{
"name":"dialog3",
"type": "waterfall",
"steps": [
{
"data": [
{
"type": "text",
"value": "Hey, this is dialig3."
}]
}
]
}]
`;
const generateSignleStep = (step) => {
return (session, args, next) => {
step.forEach(sentence => {
switch (sentence.type) {
case 'quickReplies':
let choices = sentence.options.map(item => {
return item.value
});
let card = new builder.ThumbnailCard(session)
.text(sentence.value)
.buttons(sentence.options.map(choice => new builder.CardAction.imBack(session, choice.value, choice.text)))
let message = new builder.Message(session).addAttachment(card);
builder.Prompts.choice(session, message, choices);
break;
case 'text':
default:
session.send(sentence.value)
break;
}
})
}
}
const generatenextAction = (actions) => {
return (session, args, next) => {
const response = args.response;
actions.map(action => {
if (action.result == response.entity) {
session.beginDialog(action.action);
}
})
}
}
const generateWaterfallSteps = (steps) => {
let waterfall = [];
steps.forEach(step => {
waterfall.push(generateSignleStep(step.data));
if (step.next) {
waterfall.push(generatenextAction(step.next));
}
});
return waterfall;
}
var bot = new builder.UniversalBot(connector);
const jsonobj = JSON.parse(json);
jsonobj.forEach(dialog => {
bot.dialog(dialog.name, generateWaterfallSteps(dialog.steps))
.triggerAction({
matches: new RegExp(dialog.name, "g")
})
});
The result is:

Using JSON API Serializer to create more complicated JSON

The examples here don't go nearly far enough in explaining how to produce a more complicated structure...
If I want to end up with something like:
{
"data": {
"type": "mobile_screens",
"id": "1",
"attributes": {
"title": "Watch"
},
"relationships": {
"mobile_screen_components": {
"data": [
{
"id": "1_1",
"type": "mobile_screen_components"
},
{
"id": "1_2",
"type": "mobile_screen_components"
},
...
]
}
}
},
"included": [
{
"id": "1_1",
"type": "mobile_screen_components",
"attributes": {
"title": "Featured Playlist",
"display_type": "shelf"
},
"relationships": {
"playlist": {
"data": {
"id": "938973798001",
"type": "playlists"
}
}
}
},
{
"id": "938973798001",
"type": "playlists",
"relationships": {
"videos": {
"data": [
{
"id": "5536725488001",
"type": "videos"
},
{
"id": "5535943875001",
"type": "videos"
}
]
}
}
},
{
"id": "5536725488001",
"type": "videos",
"attributes": {
"duration": 78321,
"live_stream": false,
"thumbnail": {
"width": 1280,
"url":
"http://xxx.jpg?pubId=694940094001",
"height": 720
},
"last_published_date": "2017-08-09T18:26:04.899Z",
"streams": [
{
"url":
"http://xxx.m3u8",
"mime_type": "MP4"
}
],
"last_modified_date": "2017-08-09T18:26:27.621Z",
"description": "xxx",
"fn__media_tags": [
"weather",
"personality"
],
"created_date": "2017-08-09T18:23:16.830Z",
"title": "NOAA predicts most active hurricane season since 2010",
"fn__tve_authentication_required": false
}
},
...,
]
}
what is the most simple data structure and serializer I can set up?
I get stumped after something like:
const mobile_screen_components = responses.map((currentValue, index) => {
id[`id_${index}`];
});
const dataSet = {
id: 1,
title: 'Watch',
mobile_screen_components,
};
const ScreenSerializer = new JSONAPISerializer('mobile_screens', {
attributes: ['title', 'mobile_screen_components'],
mobile_screen_components: {
ref: 'id',
}
});
Which only gives me:
{
"data": {
"type": "mobile_screens",
"id": "1",
"attributes": { "title": "Watch" },
"relationships": {
"mobile-screen-components": {
"data": [
{ "type": "mobile_screen_components", "id": "1_0" },
{ "type": "mobile_screen_components", "id": "1_1" },
{ "type": "mobile_screen_components", "id": "1_2" },
{ "type": "mobile_screen_components", "id": "1_3" },
{ "type": "mobile_screen_components", "id": "1_4" },
{ "type": "mobile_screen_components", "id": "1_5" }
]
}
}
}
}
I have no idea how to get the "included" sibling to "data." etc.
So, the question is:
what is the most simple data structure and serializer I can set up?
Below is the simplest object that can be converted to JSON similar to JSON in the question using jsonapi-serializer:
let dataSet = {
id: '1',
title: 'Watch',
mobile_screen_components: [
{
id: '1_1',
title: 'Featured Playlists',
display_type: 'shelf',
playlists: {
id: 938973798001,
videos: [
{
id: 5536725488001,
duration: 78321,
live_stream: false
},
{
id: 5535943875001,
duration: 52621,
live_stream: true
}
]
}
}
]
};
To serialize this object to JSON API, I used the following code:
let json = new JSONAPISerializer('mobile_screen', {
attributes: ['id', 'title', 'mobile_screen_components'],
mobile_screen_components: {
ref: 'id',
attributes: ['id', 'title', 'display_type', 'playlists'],
playlists: {
ref: 'id',
attributes: ['id', 'videos'],
videos: {
ref: 'id',
attributes: ['id', 'duration', 'live_stream']
}
}
}
}).serialize(dataSet);
console.log(JSON.stringify(json, null, 2));
The first parameter of JSONAPISerializer constructor is the resource type.
The second parameter is the serialization options.
Each level of the options equals to the level of the nested object in serialized object.
ref - if present, it's considered as a relationships.
attributes - an array of attributes to show.
Introduction
First of all we have to understand the JSON API document data structure
[0.1] Refering to the top level (object root keys) :
A document MUST contain at least one of the following top-level
members:
data: the document’s “primary data”
errors: an array of error objects
meta: a meta object that contains non-standard meta-information.
A document MAY contain any of these top-level members:
jsonapi: an object describing the server’s implementation
links: a links object related to the primary data.
included: an array of resource objects that are related to the primary data and/or each other (“included resources”).
[0.2]
The document’s “primary data” is a representation of the resource or
collection of resources targeted by a request.
Primary data MUST be either:
a single resource identifier object, or
null, for requests that target single resources
an array of resource identifier
objects, or an empty array ([]), for reqs. that target
collections
Example
The following primary data is a single resource object:
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
// ... this article's attributes
},
"relationships": {
// ... this article's relationships
}
}
}
In the (jsonapi-serializer) documentation : Available serialization option (opts argument)
So in order to add the included (top-level member) I performed the following test :
var JsonApiSerializer = require('jsonapi-serializer').Serializer;
const DATASET = {
id:23,title:'Lifestyle',slug:'lifestyle',
subcategories: [
{description:'Practices for becoming 31337.',id:1337,title:'Elite'},
{description:'Practices for health.',id:69,title:'Vitality'}
]
}
const TEMPLATE = {
topLevelLinks:{self:'http://example.com'},
dataLinks:{self:function(collection){return 'http://example.com/'+collection.id}},
attributes:['title','slug','subcategories'],
subcategories:{ref:'id',attributes:['id','title','description']}
}
let SERIALIZER = new JsonApiSerializer('pratices', DATASET, TEMPLATE)
console.log(SERIALIZER)
With the following output :
{ links: { self: 'http://example.com' },
included:
[ { type: 'subcategories', id: '1337', attributes: [Object] },
{ type: 'subcategories', id: '69', attributes: [Object] } ],
data:
{ type: 'pratices',
id: '23',
links: { self: 'http://example.com/23' },
attributes: { title: 'Lifestyle', slug: 'lifestyle' },
relationships: { subcategories: [Object] } } }
As you may observe, the included is correctly populated.
NOTE : If you need more help with your dataSet, edit your question with the original data.

MongoDB, NodeJS: updating an embedded document with new members

Using: MongoDB and native nodeJS mongoDB driver.
I'm trying to parse all the data from fb graph api, send it to my API and then save it to my DB.
PUT handling in my server:
//Update user's data
app.put('/api/users/:fbuser_id/:category', function(req, res) {
var body = JSON.stringify(req.body);
var rep = /"data":/;
body = body.replace(rep, '"' + req.params.category + '"' + ':');
req.body = JSON.parse(body);
db.fbusers.update({
id: req.params.fbuser_id
}, {
$set: req.body
}, {
safe: true,
multi: false
},
function(e, result) {
if (e) return next(e)
res.send((result === 1) ? {
msg: 'success'
} : {
msg: 'error'
})
});
});
I'm sending 25 elements at a time, and this code just overrides instead of updating the document.
Data I'm sending to the API:
{
"data": [
{
"category": "App page",
"name": "SoundCloud",
"id": "7919071058",
"created_time": "2013-09-16T18:16:59+0000"
},
{
...and so on
}
]
}
Basically my API changes "data" key from sent json to the category name, f.e.:
PUT to /api/users/000/likes will change the "data" key to "likes":
{
"likes": [
{
"category": "App page",
"name": "SoundCloud",
"id": "7919071058",
"created_time": "2013-09-16T18:16:59+0000"
},
{
...and so on
}
]
}
Then this JSON is put to the db.
Hierarchy in mongodb:
{
"_id": ObjectID("556584c8e908f0042836edce"),
"id": "0000000000000",
"email": "XXXX#gmail.com",
"first_name": "XXXXXXXX",
"gender": "male",
"last_name": "XXXXXXXXXX",
"link": "https://www.facebook.com/app_scoped_user_id/0000000000000/",
"locale": "en_US",
"name": "XXXXXXXXXX XXXXXXXXXX",
"timezone": 3,
"updated_time": "2015-05-26T18:11:59+0000",
"verified": true,
"likes": [
{
"category": "App page",
"name": "SoundCloud",
"id": "7919071058",
"created_time": "2013-09-16T18:16:59+0000"
},
{
"category": "App page",
"name": "SoundCloud",
"id": "7919071058",
"created_time": "2013-09-16T18:16:59+0000"
},
{
....and so on
}
]
}
So the problem is that my api overrides the field (in this case "likes") with newly sent data, instead of appending it to already existing data document.
I am pretty sure that I should be using other parameter than "$put" in the update, however, I have no idea which one and how to pass parameters to it programatically.
Use $push with the $each modifier to append multiple values to the array field.
var newLikes = [
{/* new item here */},
{/* new item here */},
{/* new item here */},
];
db.fbusers.update(
{ _id: req.params.fbuser_id },
{ $push: { likes: { $each: newLikes } } }
);
See also the $addToSet operator, it adds a value to an array unless the value is already present, in which case $addToSet does nothing to that array.