How to get specific columns from join table using withRelated bookshelf - bookshelf.js

for example i got 2 tables like;
table1 {Id, Name, Description}
table2 {Id, Table1Id, Name, Amount}
With bookshelfJS when i using withRelated something like;
new table1({Id: 1})
.fetchAll({
withRelated: ['Childs']})
.then(function(rows) {
callback(null, rows);
});
I expected my result something like;
{results: [{Id: '', Name: '', Description: '', Childs: [{Id: '', Name: '', Amount: 123}]}]}
I don't want to get Table1Id in the Childs list. How can I specify what columns in my output?
UPDATE
My models;
table1 = bookshelf.Model.extend({
tableName: 'table1',
Childs: function() {
return this.hasMany(table2, 'Table1Id');
}
});
table2 = bookshelf.Model.extend({
tableName: 'table2',
Parent: function() {
return this.belongsTo(Table1);
}
});
If I'm not select Table1Id
new table1({Id: 1})
.fetchAll({
withRelated: ['Childs':function(qb) {
qb.select('Id', 'Name', 'Description');
}]})
.then(function(rows) {
callback(null, rows);
});
then return empty for Childs[].
Should be;
new table1({Id: 1})
.fetchAll({
withRelated: ['Childs':function(qb) {
qb.select('Id', 'Table1Id', 'Name', 'Description');
}]})
.then(function(rows) {
callback(null, rows);
});

well here's the thing: this can be solved pretty easily, but you NEED to select the primary ID of the table in question, otherwise Bookshelf won't know how to tie the data together. The idea is that you get the query builder from the Knex.js and use the select method (http://knexjs.org/#Builder-select).
Here's the solution for your case:
new table1({
Id: 1
})
.fetchAll({
withRelated: [{
'Childs': function(qb) {
//always select the primary Id of the table, otherwise there will be no relations between the tables
qb.select('Id', 'Name', 'Amount'); //Table1Id is omitted!
}
}]
})
.then(function(rows) {
callback(null, rows);
});
Let me know if this solves your problem.

in your bookshelf.js file, add the visibility plugin as below
bookshelf.plugin('visibility');
in your table2 model, hide the unwanted field(s) as below
table2 = bookshelf.Model.extend({
tableName: 'table2',
hidden: ['Table1Id'],
Parent: function() {
return this.belongsTo(Table1);
}
});
you can learn more about the visibility plugin from here
https://github.com/tgriesser/bookshelf/wiki/Plugin:-Visibility

Related

Problems structuring and querying a table properly - DynamoDB

I am new to dynamoDB. I am having difficulty developing a table structure. I have data that can best be thought of as a folder structure. There are folders which are nested in parent folders. Most of the time, I will be querying for all folders with a given parent folder, however, there are times when I will be querying individual folders.
If I use the parent_id (parent folder) as the partition key and the id of the individual folder as the sort key, I believe that this creates a table where all related files are stored together and I can query them efficiently. However, I have questions.
First, the query "works" in that it returns the data, but is it written so that it queries the data correctly and is not merely scrolling through the whole table?
router.get("/api/children_folders/:parent_id", (req, res, next) => {
let parent_id = req.params.parent_id;
let params = {
TableName: tableName,
KeyConditionExpression: "parent_id = :pid",
ExpressionAttributeValues: {
":pid": parent_id,
},
ScanIndexForward: false,
};
docClient.query(params, (err, data) => {
if (err) {
console.log(err);
return res.status(err.statusCode).send({
message: err.message,
status: err.statusCode,
});
} else {
return res.status(200).send(data);
}
});
});
Second, if I want to query for individual tags, do I need to pass in a combination of the parent folder ID and the actual ID, or is this OK?
router.get("/api/folder/:folder_id", (req, res, next) => {
let tag_id = req.params.folder_id;
let params = {
TableName: tableName,
KeyConditionExpression: "folder_id = :fid",
ExpressionAttributeValues: {
":fid": folder_id,
},
Limit: 1,
};
docClient.query(params, (err, data) => {
if (err) {
console.log(err);
return res.status(err.statusCode).send({
message: err.message,
status: err.statusCode,
});
} else {
if (!_.isEmpty(data.Items)) {
return res.status(200).send(data.Items[0]);
} else {
return res.status(404).send();
}
}
});
});
I just feel like I am missing some thing here and I want to make sure that I am grabbing the data correctly.
The PK, should be something that would divide the load equally (ideally). I don't the fully picture of your problem but assuming you can chose a good parent folder as a partition key, then you can insert every file/dir with a sort key representing its full path
For example:
PK SK
/home /username/pictures/cat.jpg
This way if you want to get a specific item you can use the get item request
var params = {
Key: {
"PK": { "S": "/home" },
"SK": { "S": "/username/pictures/cat.jpg" }
},
TableName: tableName
};
var result = await dynamodb.getItem(params).promise()
Now if you want to list all the files in "/home/username/pictures" you can use begins with query
const params = {
TableName: 'tablenName',
KeyConditionExpression: '#PK = :root_path and begins_with(#SK, :sub_path)',
ExpressionAttributeNames:{
"#user_id": "root_path",
"#user_relation": 'sub_path'
},
ExpressionAttributeValues: {
":root_path": "/home",
":sub_path": "/username/pictures"
}
}

Meteor collections: upsert w/ array

Forms of this question have been asked a few times, but I've been unable to find a solution:
I have a schema like this (simplified):
StatusObject = new SimpleSchema({
statusArray: [statusSchema]
});
where statusSchema is
{
topicId:{
type: String,
optional: true
},
someInfo:{
type: Number,
optional: true,
decimal: true
},
otherInfo:{
type: Number,
optional: true
}
}
I am trying to upsert - with the following meteor method code:
var upsertResult = BasicInfo.update({
userId: this.userId,
statusArray: {
$elemMatch: { topicId : newStatus.topicId }
}
}, {
$set: {
"statusArray.$.topicId": newStatus.topicId,
"statusArray.$.someInfo": newStatus.someInfo,
"statusArray.$.otherInfo": newStatus.otherInfo
}
}, {
multi: true,
upsert: true
});
But I keep getting an error: statusArray must be an array
I thought by adding the $, I was making sure it is recognized as an array? What am I missing?
It seems (after your clarification comments), that you want to find a document with particular userId and modify its statusArray array using one of these scenarios:
Update existing object with particular topicId value;
Add a new object if the array doens't have one with particular topicId value.
Unfortunately, you can't make it work using just one DB query, so it should be like this:
// try to update record
const updateResult = BasicInfo.update({
userId: this.userId,
'statusArray.topicId': newStatus.topicId
}, {
$set: {
"statusArray.$": newStatus
}
});
if (!updateResult) {
// insert new record to array or create new document
BasicInfo.update({
userId: this.userId
}, {
$push: {
statusArray: newStatus
},
$setOnInsert: {
// other needed fields
}
}, {
upsert: true
});
}
Your code is treating StatusArray as an object,
Before you do the upsert, build the status array first, assuming that your current value is currentRecord
newStatusArray = currentRecord.statusArray
newStatusArray.push({
topicId: newStatus.topicId,
someInfo : newStatus.someInfo,
otherInfo: newStatus.otherInfo
})
and in the upsert, simply refer to it like this
$set: { statusArray: newStatusArray}

1-1 relation load with bookshelf

I have 2 tables something likes;
Property {id, name, address_id}
Address {id, city, country, add_line_1, ...}
I linked Property to Address via address_id.
How can I config Bookshelf model to load Property with associated Address via address_id.
I'm trying something likes;
PROPERTY = bookshelf.Model.extend({
tableName: 'PROPERTY',
address: function() {
return this.hasOne(ADDRESS, 'address_id');
}
});
ADDRESS = bookshelf.Model.extend({
tableName: 'ADDRESS',
property: function() {
return this.belongsTo(PROPERTY);
}
});
The error: "Unknown ADDRESS.address_id in where clause"
From the documentation the hasOne() expects the foreign key to be ON THE OTHER table. So try changing your models to:
PROPERTY = bookshelf.Model.extend({
tableName: 'PROPERTY',
address: function() {
return this.belongsTo(ADDRESS, 'address_id');
}
});
ADDRESS = bookshelf.Model.extend({
tableName: 'ADDRESS',
property: function() {
return this.hasOne(PROPERTY);
}
});
And query using, by example:
PROPERTY.fetchAll({withRelated:'address'}).then((a)=>
{console.log(a.toJSON())});

How do I get translated column headers with Meteor and aldeed:tabular?

I'm running into the same problem as issue #53 of aldeed:tabular. When defining the table as suggested in the documentation, it is too soon to invoke a translation function (TAPi18n.__ or other), since the I18N variables are not yet set.
What is the nice, reactive way of feeding the translated column titles into DataTables, either directly as suggested by aldeed himself upon closing the issue, or through aldeed:tabular?
With .tabular.options
There is a way with the template's .tabular.options reactive
variable, but it is quirky. Here is a variation of the library
example using
tap-i18n to translate the
column headers:
function __(key) {
if (Meteor.isServer) {
return key;
} else {
return TAPi18n.__(key);
}
}
Books = new Meteor.Collection("Books");
TabularTables = {};
TabularTables.Books = new Tabular.Table({
name: "Books",
collection: Books,
columns: [] // Initially empty, reactively updated below
});
var getTranslatedColumns = function() {
return [
{data: "title", title: __("Title")},
{data: "author", title: __("Author")},
{data: "copies", title: __("Copies Available")},
{
data: "lastCheckedOut",
title: __("Last Checkout"),
render: function (val, type, doc) {
if (val instanceof Date) {
return moment(val).calendar();
} else {
return "Never";
}
}
},
{data: "summary", title: __("Summary")},
{
tmpl: Meteor.isClient && Template.bookCheckOutCell
}
];
}
if (Meteor.isClient) {
Template.tabular.onRendered(function() {
var self = this;
self.autorun(function() {
var options = _.clone(self.tabular.options.get());
options.columns = getTranslatedColumns();
self.tabular.options.set(_.clone(options));
});
});
}
With a forked version
I created a pull request against branch devel of meteor-tabular to enable the straightforward, reactive-based approach like so:
<template name="MyTemplateWithATable">
{{> tabular table=makeTable class="table table-editable table-striped table-bordered table-condensed"}}
</template>
var MyColumns = ["title", "author"];
// Assume translations are set up for "MyTable.column.title", "MyTable.column.author"
// in other source files; see TAPi18n documentation for how to do that
function makeTable() {
return new Tabular.Table({
name: "MyTable",
collection: MyCollection,
columns: _.map(MyColumns,
function(colSymbol) {
return {
data: colSymbol,
title: TAPi18n.__("MyTable.column." + colSymbol)
};
})
});
}
if (Meteor.isServer) {
// Called only once
makeTable();
} else if (Meteor.isClient) {
// Reactively called multiple times e.g. when switching languages
Template.MyTemplateWithATable.helpers({makeTable: makeTable});
}
Recent versions of aldeed:tabular allow to specify a function for setting the column titles.
import {TAPi18n} from 'meteor/tap:i18n';
TabularTables = {};
TabularTables.Departments= new Tabular.Table({
name: 'Departments',
collection: Departments,
responsive: true,
autoWidth: true,
stateSave: false,
columns: [
{data: "name", titleFn: function() {
return TAPi18n.__("name");
}},
{data: "description", titleFn: function() {
return TAPi18n.__("description");
}}
]
});
The language change is reactive. If you have translations you can switch and columns will be translated.
TAPi18n.setLanguage("en");
TAPi18n.setLanguage("de");
Word of warning:
This currently does not work when you include invisible columns in your table data. The offset is wrong and you get wrong column titles.

withRelated binding is undefined

I have two model:
var Book = bookshelf.Model.extend({
tableName: 'books',
chapters: function(){
var chapters = require('./chapter').Chapter;
return this.hasMany(chapters, 'bookId');
}
});
module.exports = {
Book: Book
};
var Chapter = bookshelf.Model.extend({
tableName: 'chapters',
hasTimestamps: ['dateCreated', 'dateUpdated'],
//relationship
book: function(){
var book = require('./user').Book;
return this.belongsTo(book, 'bookId');
}
});
module.exports = {
Chapter: Chapter
}
I do this in one of the controller:
new Book.Book()
.fetch({withRelated:['chapters']})
.then(function(books){
resolve(books);
}).catch(function(err){
reject(err);
});
The "chapters": [] because the debug log give me this, the bindings is undefined:
{ method: 'select',
options: {},
bindings: [ 1 ],
sql: 'select `books`.* from `books` limit ?' }
{ method: 'select',
options: {},
bindings: [ undefined ],
sql: 'select `chapters`.* from `chapters` where `chapters`.`bookId` in (?)' }
Bookshelf assumes that you have a column called 'id' in each table which uniquely identifies each row. If you are using a primary key which is not called 'id' you need to specify what it is called to be able to use the model in a relation. This is done by the 'idAttribute' member e.g.
var ModelA = bookshelf.Model.extend({
tableName: "ModelA",
idAttribute: "modelA_id"
...
}
});
http://bookshelfjs.org/#Model-instance-idAttribute
Solved it with idAttribute: 'cardId' specified.

Resources