How to access data on a `through` table with Bookshelf - javascript

I am using [BookshelfJS][bookshelfjs] for my ORM and am wondering how to access data on a though table.
I have 3 Models, Recipe, Ingredient and RecipeIngredient which joins the two.
var Recipe = BaseModel.extend({
tableName: 'recipe',
defaults: { name: null },
ingredients: function () {
return this
.belongsToMany('Ingredient')
.through('RecipeIngredient')
.withPivot(['measurement']);
}
}));
var Ingredient = BaseModel.extend({
tableName: 'ingredients',
defaults: { name: null },
recipes: function () {
return this
.belongsToMany('Recipe')
.through('RecipeIngredient');
}
}));
var RecipeIngredient = BaseModel.extend({
tableName: 'recipe_ingredients',
defaults: { measurement: null },
recipe: function () {
return this.belongsToMany('Recipe');
},
ingredient: function () {
return this.belongsToMany('Ingredient');
}
}));
I then attempt to retrieve a Recipe along with all the Ingredients however cannot work out how to access measurement on the RecipeIngredient.
Recipe
.forge({
id: 1
})
.fetch({
withRelated: ['ingredients']
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.error(err);
});
Return:
{
"id": 1,
"name": "Delicious Recipe",
"ingredients": [
{
"id": 1,
"name": "Tasty foodstuff",
"_pivot_id": 1,
"_pivot_recipe_id": 1,
"_pivot_ingredient_id": 1
}
]
}
With no measurement value.
I had thought that the .withPivot(['measurement']) method would have grabbed the value but it does not return any additional data.
Have I missed something or misunderstood how this works?

I'm not exactly sure why you want to use through. If it's just a basic many-to-many mapping, you can achieve this by doing the following:
var Recipe = BaseModel.extend({
tableName: 'recipe',
defaults: { name: null },
ingredients: function () {
return this
.belongsToMany('Ingredient').withPivot(['measurement']);
}
}));
var Ingredient = BaseModel.extend({
tableName: 'ingredients',
defaults: { name: null },
recipes: function () {
return this
.belongsToMany('Recipe').withPivot(['measurement']);;
}
}));
You don't need an additional model for junction table. Just be sure to define a junction table in your database as ingredients_recipe (alphabetically joining the name of tables!). Or , you can provide your own custom name to belongsToMany function for what the junction table should be named. Be sure to have ingredients_id and recipe_id in ingredients_recipe
This is pretty much it. Then you can do
Recipe
.forge({
id: 1
})
.fetch({
withRelated: ['ingredients']
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.error(err);
});

Related

Map the nested data from other table using promise and async-await

I need the expert advice for this code. I need to know Is there any better way to solve this.
I am using the mongoose for db. I have a dataset like this:
Below is matchTable:
{
_id: 617bc0113176d717f4ddd6ce,
car: [],
status: true
},
{
_id: 617bc0113176d717f4ddd6cg,
car: [
{
aid: '5c1b4ffd18e2d84b7d6febcg',
}
],
status: true
}
And I have a Car table in which car name is there on behalf of id
like this
{ _id: ObjectId('5c1b4ffd18e2d84b7d6febce'), name: 'ford' },
{ _id: ObjectId('5c1b4ffd18e2d84b7d6febcg'), name: 'mitsubishi' },
So I want to make join the data from car table, so that response get name on behalf of aid.
Desired result will be like
{
_id: 617bc0113176d717f4ddd6ce,
car: [],
status: true
},
{
_id: 617bc0113176d717f4ddd6cg,
car: [
{
aid: '5c1b4ffd18e2d84b7d6febcg',
name: 'mitsubishi'
}
],
status: true
}
For that I have to merge the car table on matchTable. I have done this but I want to give some suggestion that is there any better way to do or is it fine. I need expert advice.
const getData = await matchTable.find(
{ status: true }
).lean().exec();
let dataHolder = [];
await Promise.all (
getData.map(async x => {
await Promise.all(
x.car.map(async y => {
let data = await Car.findOne(
{ _id: ObjectId(y.aid) },
{ name: 1 }
).lean().exec();
y.name = '';
if (data) {
y.name = data.name;
}
})
)
// If I return { ...x }, then on response it will return {}, {} on car column
dataHolder.push(x) //So I have chosen this approach
})
);
Please guide me if any better and efficient solution is there. Thanks in advance
You can make use of aggregation here.
const pipeline = [
{
$match : { status : true }
},
{
$unwind: '$matchtable',
},
{
$lookup: {
from: "cars",
localField: "car.aid",
foreignField: "_id",
as: "matchcars"
}
},
{
$addFields: {
"car.carName": { $arrayElemAt: ["$matchcars.name", 0] }
}
},
{
$group: {
_id: "$_id",
cars: { $push: "$matchcars" }
}
}
]
const result = await matchTable.aggregate(pipeline).exec();
Please make sure, aid field inside car array (in matchTable collection) is an ObjectId because its being matched to _id (which is an ObjectId) inside cars collection.

Sequelize many-to-many self association

I am trying to create a model Users with many-to-many association to itself to allow users to follow another users. In one query I want to retrieve the Users followed by the current user; in another query I want to retrieve the people that follows the current user.
This is my Users model:
module.exports = (sequelize, Sequelize) => {
const Users = sequelize.define(
'Users',
{
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: Sequelize.STRING,
},
},
);
Users.associate = function(models) {
Users.belongsToMany(Users, { as: 'following', through: models.UsersUsers });
};
return Users;
};
I declare UsersUsers, just in case I need to add any field there:
module.exports = (sequelize, Sequelize) => {
const UsersUsers = sequelize.define(
'UsersUsers',
{}
);
UsersUsers.associate = function(models) {};
return UsersUsers;
};
Then I query Users as:
models.Users.findOne({
where: {
id: req.params.id,
},
include: [
{
model: models.Users,
as: 'following',
},
],
})
.then((results) => {
return res.send({
User: results,
});
})
.catch((error) => {
return res.send(String(error));
});
And I get this result:
{
"User": {
"id": 1,
"name": "User1",
"following": [
{
"id": 2,
"name": "User2",
"UsersUsers": {
"UserId": 1,
"followingId": 2
}
},
{
"id": 3,
"name": "User3",
"UsersUsers": {
"UserId": 1,
"followingId": 3
}
},
{
"id": 4,
"name": "User4",
"UsersUsers": {
"UserId": 1,
"followingId": 4
}
}
]
}
}
Now the questions:
In my current query, how do I exclude "UsersUsers" from the result? attributes: { exclude: ['UsersUsers'] } did not work…
How do I create a query to retrieve the current user with the users that follows him instead of the users followed by him?
Thanks!
--
EDIT:
The solution for the question 1. is to add through: { attributes: [] } to the included model:
models.Users.findOne({
where: {
id: req.params.id,
},
include: [
{
model: models.Users,
as: 'following',
through: {
attributes: [],
},
},
],
})
.then((results) => {
return res.send({
User: results,
});
})
.catch((error) => {
return res.send(String(error));
});
Still pending question 2!
Users.findAll({
include: [
{
model: models.Users,
as: 'following',
through: {
attributes: [],
},
},
],
where : {
id : [connection.literal(` write raw sql query to get followingId here`)]
}
})
.then(result => {
res.json(result);
}).catch(error=>{
res.json(error);
});
I'm not sure if this gonna work, still play around this and do let me know if this worked or if you found any solution.

How to return an array of objects in GraphQL, possibly using the same endpoint as the one that returns a single object?

I am making a GraphQL API where I would be able to retrieve a car object by its id or retrieve all the cars when no parameter is provided.
Using the code below, I am successfully able to retrieve a single car object by supplying id as a parameter.
However, in the case where I would expect an array of objects i.e. when I supply no parameter at all, I get no result on GraphiQL.
schema.js
let cars = [
{ name: "Honda", id: "1" },
{ name: "Toyota", id: "2" },
{ name: "BMW", id: "3" }
];
const CarType = new GraphQLObjectType({
name: "Car",
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString }
})
});
const RootQuery = new GraphQLObjectType({
name: "RootQueryType",
fields: {
cars: {
type: CarType,
args: {
id: { type: GraphQLString }
},
resolve(parent, args) {
if (args.id) {
console.log(cars.find(car => car.id == args.id));
return cars.find(car => car.id == args.id);
}
console.log(cars);
//***Problem Here***
return cars;
}
}
}
});
Test queries and their respective results:
Query 1
{
cars(id:"1"){
name
}
}
Query 1 Response (Success)
{
"data": {
"cars": {
"name": "Honda"
}
}
}
Query 2
{
cars{
name
}
}
Query 2 Response (Fail)
{
"data": {
"cars": {
"name": null
}
}
}
Any help would be much appreciated.
A Car and a List of Cars are effectively two separate types. A field cannot resolve to a single Car object one time, and an array of Car object another.
Your query is returning null for the name because you told it the cars field would resolve to a single object, but it resolved to an array instead. As a result, it's looking for a property called name on the array object and since one doesn't exist, it's returning null.
You can handle this in a couple of different ways. To keep things to one query, you can use filter instead of find and change the type of your query to a List.
cars: {
type: new GraphQLList(CarType), // note the change here
args: {
id: {
type: GraphQLString
},
},
resolve: (parent, args) => {
if (args.id) {
return cars.filter(car => car.id === args.id);
}
return cars;
}
}
Alternatively, you could split this into two separate queries:
cars: {
type: new GraphQLList(CarType),
resolve: (parent, args) => cars,
},
car: {
type: CarType,
args: {
id: {
// example of using GraphQLNonNull to make the id required
type: new GraphQLNonNull(GraphQLString)
},
},
resolve: (parent, args) => cars.find(car => car.id === args.id),
}
Check the docs for more examples and options.

append to list if exist or add list in dynamoDB

I have a product table in DynamoDB which has some items. Now I need to add list of buyers to the product which can grow i.e. append to list. It works for if I have an empty list or a list with some items in the table item but for the first addition it throws an error. Is there any way to check if list exists then append else add a list. here is my code
let params = {
TableName: "product",
ExpressionAttributeNames: {
"#Y": "buyer"
},
ExpressionAttributeValues: {
":y": ["PersonXYZ"]
},
Key: {
id: 'Hy2H4Z-lf'
},
UpdateExpression: "SET #Y = list_append(#Y,:y)"
};
updateItemInDDB(params).then((data) => {
res.status(200).send(data);
}, err => {
console.log(err);
res.sendStatus(500);
});
UpdateItemInDDB is just a function which takes a params and run dnamodb code on it. I am using javascript sdk for DynamoDB with Document Client.
var params = {
TableName: "user",
Key: {
"user_id": {
S: user_id
}
},
UpdateExpression: "SET #ri = list_append(if_not_exists(#ri, :empty_list), :vals)",
ExpressionAttributeNames: {
"#ri": "images"
},
ExpressionAttributeValues: {
":vals": {"L": [{
"S": "dummy data"
}]},
":empty_list": {"L": []}
},
ReturnValues: 'ALL_NEW'
};
":empty_list": {"L": []}
this is important if you want to set empty list, otherwise it will give below exception.
"errorMessage": "ExpressionAttributeValues contains invalid value: Supplied AttributeValue is empty, must contain exactly one of the supported datatypes for key :empty_list",
"errorType": "ValidationException"
EDIT: Nest the conditional expressions
You could run SET append_list with a ConditionalExpression that the attribute does exist, then if that fails run SET with a ConditinalExpression that the attribute does not exist.
let params1 = {
TableName: "product",
ExpressionAttributeNames: {
"#Y": "buyer"
},
ExpressionAttributeValues: {
":y": ["PersonXYZ"]
},
Key: {
id: 'Hy2H4Z-lf'
},
ConditionExpression: "attribute_exists(buyer)",
UpdateExpression: "SET #Y = list_append(#Y,:y)"
};
updateItemInDDB(params1).then((data) => {
res.status(200).send(data);
}, err => {
console.log(err);
let params2 = {
TableName: "product",
ExpressionAttributeNames: {
"#Y": "buyer"
},
ExpressionAttributeValues: {
":y": ["PersonXYZ"]
},
Key: {
id: 'Hy2H4Z-lf'
},
ConditionExpression: "attribute_not_exists(buyer)",
UpdateExpression: "SET #Y = (#Y,:y)"
};
updateItemInDDB(params2).then((data) => {
res.status(200).send(data);
}, err => {
console.log(err);
res.sendStatus(500);
});
});

Merge two Arrays using underscore.js in AngularJS

I have two api requests and both gets a result of JSON. The first request is "account" request and second is "Container" request. Now:
Request for all Accounts (accountid: 1, name: account1, blabla)
Request for all Containers (accountid: 1, name: Container1, blabla)
This is the result JSON of Accounts:
account: [
{
accountId: "123456789",
fingerprint: null,
name: "Account2",
path: "accounts/123456789",
},
This is the result JSON of Containers:
{
accountId: "123456789",
containerId: "123****",
domainName: null,
fingerprint: "123*****",
name: "Container23",
path: "accounts/123456789/containers/123******",
publicId: "GTM-****"
},
As you can see the container result contains the accountid so im trying to merge those two results so it becomes this combined (container):
{
accountId: "123456789",
name: "Account2", <------ THIS is what i want to merge
containerId: "123****",
domainName: null,
fingerprint: "123*****",
name: "Container23",
path: "accounts/123456789/containers/123******",
publicId: "GTM-****"
},
Remember there are many containers and accounts not just one block.
What i have tried using underscore:
var mergedList = _.map($scope.GTMcontainers, function(data){
return _.extend(data, _.findWhere($scope.account, { id: data.accountId }));
});
console.log(mergedList);
Here is my AngularJS
function getContainer() {
$http.get("http://******")
.then(function (response) {
$scope.GTMcontainers = response.data;
console.log(response);
$scope.loading = false;
})
.then(function () {
$http.get("http://******")
.then(function (response2) {
$scope.account = response2.data;
console.log(response2);
var mergedList = _.map($scope.GTMcontainers, function(data){
return _.extend(data, _.findWhere($scope.account, { id: data.accountId }));
});
console.log(mergedList);
})
})
}
Using this underscore gives me exactly the same JSON result as i requested (no merge).
Hope some one has experience with this.
Thanks
Updated:
using simple javascript
var accounts = [
{
accountId: "123456789",
fingerprint: null,
name: "Account2",
path: "accounts/123456789",
},{
accountId: "123456780",
fingerprint: null,
name: "Account3",
path: "accounts/123456789",
}]
var containers =[{
accountId: "123456789",
containerId: "123****",
domainName: null,
fingerprint: "123*****",
name: "Container23",
path: "accounts/123456789/containers/123******",
publicId: "GTM-****"
},{
accountId: "123456780",
containerId: "123****",
domainName: null,
fingerprint: "123*****",
name: "Container24",
path: "accounts/123456789/containers/123******",
publicId: "GTM-****"
}]
var result=[];
containers.forEach(function(item){
var temp=accounts.find(function(d){return d.accountId == item.accountId});
if(temp)
item.name = temp.name;
result.push(item);
})
console.log(result);
I have faced that issue, I knew I have got responses, but data was not merged. So I have used callbacks.
function getContainer(callback) {
$http.get("http://******")
.then(function (response) {
$scope.GTMcontainers = response.data;
console.log(response);
$scope.loading = false;
})
.then(function () {
$http.get("http://******")
.then(function (response2) {
$scope.account = response2.data;
console.log(response2);
if(response && response2){
callback(response,response2);
}
})
})
}
At function call time--
getContainer(function(array1,array2){
if(array1 && array2 && array1.length>0 && array2.length>0){
var mergedList = _.map(array1, function(data){
return _.extend(data, _.findWhere(array2, { id: data.accountId }));
});
console.log(mergedList);
}
})

Categories

Resources