sails many to many associations failing to save - save

My sails many to many associations have stopped saving changes, that is, if I deselect one of the items in my checkbox list, or select a new one, the changes are not saved (populated to the join table).
It used to work, although I am not sure how long it's been broken for.
All the other fields on the page save correctly.
So I know the mechanics of most of it are correct, just the many to many associations, ie, updating the list of active records in the join table.
Any hints on what I can be doing wrong in my save?
I have the following models:
/**
* User.js
*/
module.exports = {
attributes: {
projects: {
collection: 'project',
via: 'users'
},
}
/**
* Project.js
*/
module.exports = {
attributes: {
users: {
collection: 'user',
via: 'projects',
dominant: true
},
}
In my form I am returning a checkbox list like such
{ projectname: 'AS Story Database',
userlist: [ '10', '3', '1' ], <-- this line is the many to many line from the check boxs
projecttype: 'Development',
companyid: '1',
startdate: 'Sat Jan 01 2011 00:00:00 GMT+1100 (AUS Eastern Daylight Time)',
enddate: '' }
}
I tried populating the result set
Project.findOne({'id':id})
.populate('users') <--------- heres the populate I added but didnt seem to have effect
.exec(function(err,project){
This is the save dialog in my sails controller
var a=req.param('project',null);
console.log(a); <-- note this is where the json above is output
project.projecttype= a.projecttype,
project.projectname= a.projectname,
project.companyid= a.companyid,
project.users= a.userlist, <-- this is the many to many association that used to work
project.startdate = a.startdate,
project.enddate = a.enddate
project.save(function(err,updated){ <-- here is the save function
if (err) {
req.session.flash = {'err':err};
sails.controllers.project.edit(req,res);
}else{
req.session.flash = {};
res.redirect('project/index');
}
});

#Sangharsh is correct in his comment above; you cannot update a collection by assigning an array to it and calling .save(). You may be thinking of .update(), which in Sails v0.12.x allows you to provide an array of objects to replace the existing set (although this has been removed in Sails 1.0 since it caused a lot of bugs and confusion).
The correct way to update an existing instance's collection in Sails v0.12.x is to use the .add() and .remove() methods. See the many-to-many associations doc page for more info.
In Sails 1.0, the .save() method of individual records has been removed to make things clearer; you always use the .addToCollection(), .removeFromCollection() and .replaceCollection() model class methods to manipulate plural associations.

Ok; I finally figured this out.
The method above (replaceCollection) only works on the older (0.x) versions of sails.
To get it working in the modern version, use
<model>.update('id':<the recordID>},{'<association name>':<list of new records>})
.exec(function(err,updated){
-do something-
});
ie -
Project.update({'id':id},{'users': a.userlist}).exec(function(err,updated){
project.save(function(err,updated){
//console.log(updated);
if (err) {
console.log(err);
req.session.flash = {'err':err};
sails.controllers.project.edit(req,res);
}else{
req.session.flash = {};
res.redirect('project/index');
};
});
});

Related

Airtable returns not all records via API

I do load aittable records via their API:
const base = airtable.base(item.baseId);
base("Dishes")
.select({
})
.eachPage(
function page(records, fetchNextPage) {
tableRecords.push(...records);
// To fetch the next page of records, call `fetchNextPage`.
// If there are more records, `page` will get called again.
// If there are no more records, `done` will get called.
fetchNextPage();
},
function done(err) {
if (err) {
console.error(err);
return;
}
console.log("##Done", tableRecords.length);
}
);
and as a result, I receive 2202 records. But in the table in UI I do see 2271 records. And when I do export to csv - I see the same 2271 as well.
Code is pretty basic, I even remove view setting to ensure, that it's not a presentational issue.
Google did not help me (nothing related). Did anyone face the same issue? Any solution?
NB: for sure I already compared both lists and found items I do miss, but while observing those items I see nothing special there. So it says me what I do miss, but not why
const base = airtable.base(item.baseId);
base("Dishes")
.select({})
.all()

How to refresh previously bound entity every time user visits the page

I just ran into a problem where I am not sure how to solve.
Background: I've got an App with two views:
1st one to input a number,
2nd one to see the details.
After the view switched to the detail view, I would call the bindElement() to get my data from the backend.
_onRoutePatternMatched: function(oEvent) {
// ...
this.getView().bindElement({
path: "/EntitySet('" + id+ "')"
});
},
Problem is that the ID is quite often the same, hence, the method will call the backend only if the ID is different from the last call.
So I tried to solve the problem by using the following:
this.getView().getModel().read("/EntitySet('" + id+ "')",{
success: function(oData, response) {
that.getView().setModel(oData, "");
}
});
By this, the data is always up to date. But now the binding is a bit different.
Binding with bindElement():
{
"id": "1234",
"propety1": "abc",
// ...
}
Binding with setModel() and id = 1234:
{
"EntitySet('1234')": {
"id": "1234",
"propety1": "abc",
// ...
}
}
For the first way, my binding looked like this:
<ObjectHeader title="{id}">
Now, it would have to look like this:
<ObjectHeader title="{/EntitySet('1234')/id}">
And here I have a problem, because the value of id (in this case 1234) will always be different and so the binding won't work. I can't bind directly to the ObjectHeader, because I will need some properties from the model later. That is the reason I am binding to the view so that all that remain available.
My idea was to edit the binding inside the success method of the read method. I would like to delete the surrounding element. Do you have an idea, how to do this? Or even a simpler/better idea to solve my pity?
Edit:
I forgot to mention the refresh method. This would be possible, but where do I have to put it? I don't want to call the backend twice.
Simply call the API myODataModel.invalidateEntry(<key>) before binding the context in order to retrieve the latest data.
// after $metadata loaded..
const model = this.getOwnerComponent().getModel("odata");
const key = model.createKey(/*...*/) //See https://stackoverflow.com/a/47016070/5846045
model.invalidateEntry(key); // <-- before binding
this.getView().bindElement({
path: "odata>/" + key,
// ...
});
From https://embed.plnkr.co/b0bXJK?show=controller/Detail.controller.js,preview
invalidateEntrydoc
Invalidate a single entry in the model data.
Mark the selected entry in the model cache as invalid. Next time a context binding or list binding is done, the entry will be detected as invalid and will be refreshed from the server.

How to seed database once with multiple views for consistent data

I might be thinking about mock APIs incorrectly, but I figured it was worth asking.
I am trying to create an object with a basic view and a detailed view, for example "basic-author" and "detailed-author". But, I would like each object to return consistent data. For example, properties like "author_name" should be the same for basic-author and detailed-author.
I am currently trying to accomplish this by generating a list of authors and extending it:
// mirage/scenarios/default.js
server.createList('author', 20);
// mirage/config.js
this.get('/basic-author', (schema) => {
return schema.basicAuthors.all();
});
// mirage/factories/author.js
import { Factory, faker } from 'ember-cli-mirage';
export default Factory.extend({
// bunch of properties, like author_name
});
// mirage/factories/basic-author.js, mirage/factories/detailed-author.js
import Author from './author';
export default Author.extend({
});
But, obviously, this won't work because getting all basicAuthors returns nothing because there are no "basic-author" in the database. If I try to change the routes to return all authors, my ember models won't work because it is returning a list of 20 authors, which is the incorrect model type.
I have also tried setting the faker seed, but that makes every item the same.
The best way to accomplish this is to use Factory traits. Give the docs a read, then you should be able to set up something like this:
// mirage/factories/author.js
import { Factory, trait, faker } from 'ember-cli-mirage';
export default Factory.extend({
authorName() {
return faker.name.firstName();
},
age: 10,
withDetails: trait({
email() {
return `${this.authorName}#gmail.com`;
},
ssn: 123456789
})
});
Then in tests/dev you can create a basic authors like this
server.create('author');
server.createList('author', 20);
and detailed authors like this:
server.create('author', 'withDetails');
server.createList('author', 20, 'withDetails');

EmberJS, Loading first object on page load

Hopefully, a simple question and must have a simple answer but i have wasted almost 3hrs in getting out of this issue.
I have a user model. I want to load the first user from DB and show it on first page load.
What i am trying to use is:
in my ArrayController,
init: function(){
var user = App.User.find(1)
console.log(user);
this.set('defualtUser',user.get('name'))
}
But i cant get the name of user.
Here is the output of user in console, which indicates that data is being loaded but i can't just get it to use.
Class
__ember1367188634172: "ember270"
__ember1367188634172_meta: Meta
_changesToSync: Object
_data: Object
attributes: Object
***name: "Cafe Alpino"***
__proto__: Object
belongsTo: Object
hasMany: Object
id: null
__proto__: Object
See the name: "Cafe Alpino", i just want to display this name.
Any help???
BTW, i am a newbie with EmberJS
I think the problem here is asynchronousy. This line: var user = App.User.find(1) will result in a user record that is not loaded yet; its properties aren't set until the AJAX call returns in the background. Therefore, user.get('name') will be empty.
There are probably a few ways to solve this. I haven't used Ember Data too much (since it's not very solid at the moment), but according to the docmentation, there should be a didLoad event that you can use:
init: function() {
var user = App.User.find(1);
var _this = this;
user.on('didLoad', function() {
_this.set('defaultUser', user.get('name'));
});
}
Give it a try! Let me know if it doesn't work out.

Dojox Grid pass two fields to formatter

I created a dojox.Grid successfully, but in one case I need to pass two fields to a formatter function instead of just one.
For instance:
{
field: 'id',
name: 'Id',
formatter: formatterFunction,
},
I need to pass to formatterFunction() both 'id' and 'name' for instance. How can I do this?
Thank you.
I know this was already mentioned in the IRC channel, but I'm answering here so others are aware, and also to address your further question that I'm not sure anyone answered.
New in 1.4 If you set the value of the field to "_item", then your formatter will be called with the entire item from the store - instead of just one field value
This makes it possible to do what you want using a formatter as well.
http://www.dojotoolkit.org/reference-guide/dojox/grid/DataGrid.html#usage
In the simplest case, without setting the grid's formatterScope, the grid's store can be accessed from within formatters via this.grid.store, e.g.:
function fmtItem(value) {
var store = this.grid.store;
return store.getValue(value, 'id') + ': ' + store.getValue(value, 'name');
}
Here's a really simple example of the above formatter in action:
http://jsbin.com/upico4/edit
There's also an example of this in one of the test pages, which creates an object to hold and scope the formatters:
http://archive.dojotoolkit.org/nightly/dojotoolkit/dojox/grid/tests/test_grid_formatters.html
As from dojo 1.4 you can also get multiple fields from a store. Should look something like:
var layout = [{
rows: [
{name: 'Title', fields:['Title', 'url'], formatter:formatLink}
]}]
function formatLink(value){
return ''+value[0]+'';
}
That uses the value from the field "url" to point your link at and the displayed title is filled with the data from "Title" field in your store.
Are you sure that you want to format and maybe not use get instead?
When you use a formatter the only value that is passed to the function is the value that field represents.
However, if you were to use get instead, you could use the item to access the other values. (However then you will lose sorting).
So for your column have
{
field: 'id',
name: 'Id',
get: getFunction
},
Then have
getFunction: function(index,row) {
return row.id + row.name;
}
function formatterFunction(val, rowIdx, cell){
var name=this.name,
field=this.field;
}

Resources