I have a many to many relationship table with payload (additional field) coming from .Net WebAPI that I have modelled in ember-data. When I add a record into this table/relationship ember is creating an additional record that is held in memory until the user performs a browser page refresh. My models are:
// student.js
export default DS.Model.extend({
name: DS.attr('string'),
studentsClasses: DS.hasMany('student-class')
})
// class.js
export default DS.Model.extend({
desc: DS.attr('string'),
studentsClasses: DS.hasMany('student-class')
})
// student-class
export default DS.Model.extend({
studentId: DS.attr('string'),
student: DS.belongsTo('student'),
class: DS.belongsTo('class'),
grade: DS.attr('number') // payload
})
Here is the code I use to create and add the many to many record.
let newRecord = this.get('store').createRecord('student-class');
newRecord.studentId = 1;
newRecord.grade = 3;
class.get('studentsClasses').pushObject(newRecord);
The new record gets created and added and everything looks good on screen, until I come back to the same page and there is an extra record in the class.studentClasses array.
Any idea why ember-data is creating an extra record in memory and how I can stop it doing it please?
Thanks
As you said, ember-data keeps records in memory. And you must keep in mind that ember-data will not remove those records by it own. It can only be removed from memory by yourself, page refresh or replaced by new payload if has same id property. You can observe that behavior by using ember debug plugin for browsers like chrome and firefox.
In your case, you've created a new record by store.createRecord(). In this moment, it had added this record to your memory already, and it was pushed to your class record. If you didn't save these models successfully, it will keep in a status called 'dirty', and if you never clean your store memory (using something like store.unloadRecord() which has some side effects, or remove this unsaved new record from your related model), the next time you use store.findRecord() to find a record, useless you force record to be reloaded like store.findRecord('class', 1, {reload: true}), it will use the existing data in your memory as first priority.
So my suggestion for this is to force reload this class model when entering this class page.
Related
I've started using mobx-state-tree recently and I have a practical question.
I have a model that has a types.identifier field, this is the database id of the resource and when I query for existing stuff it gets populated.
When I am creating a new instance, though, following the example that Michel has on egghead, I need to pass an initial id to my MyModel.create() on initial state, however, this ID will only be known once I post the creation to the API and get the resulting created resource.
I have searched around for a simple crud example using mobx-state-tree but couldn't find one (suggestions?).
What is the best practice here? Should I do a `MyModel.create({ id: 'foobar' }) and weed it out when I post to the API (and update the instance once I get the response from the API)?
This is a limitation of mobx-state-tree's current design. Identifiers are immutable.
One strategy I've seen to get around this issue is to store your persistence layer's id in a separate field from your types.identifier field. You would then use a library like uuid to generate the types.identifier value:
import { v4 } from "node-uuid"
const Box = types
.model("Box", {
id: types.identifier,
name: "hal",
x: 0,
y: 0
})
const box = Box.create({ 'hal', 10, 10, id: v4() })
I guess this could be applied to any Redux-backed system, but imagine we are building simple React Native app that supports two actions:
fetching a list of messages from a remote API
the ability to mark those messages as having been read
At the moment I have a messagesReducer that defines its state as...
const INITIAL_STATE = {
messages: [],
read: []
};
The messages array stores the objects from the remote API, for example...
messages: [
{ messageId: 1234, title: 'Hello', body: 'Example' },
{ messageId: 5678, title: 'Goodbye', body: 'Example' }
];
The read array stores the numerical IDs of the messages that have been read plus some other meta data, for example...
read: [
{ messageId: 1234, meta: 'Something' },
{ messageId: 5678, meta: 'Time etc' }
];
In the React component that displays a message in a list, I run this test to see if the message should be shown as being read...
const isRead = this.props.read.filter(m => m.messageId == this.props.currentMessage.messageId).length > 0;
This is working great at the moment. Obviously I could have put a boolean isRead property on the message object but the main advantage of the above arrangement is that the entire contents of the messages array can be overwritten by what comes from the remote API.
My concern is about how well this will scale, and how expensive the array.filter method is when the array gets large. Also keep in mind that the app displays a list of messages that could be hundreds of messages, so the filtering is happening for each message in the list. It works on my modern iPhone, but it might not work so well on less powerful phones.
I'm also thinking I might be missing some well established best practice pattern for this sort of thing.
Let's call the current approach Option 1. I can think of two other approaches...
Option 2 is to put isRead and readMeta properties on the message object. This would make rendering the message list super quick. However when we get the list of messages from the remote API, instead of just overwriting the current array we would need to step through the JSON returned by the API and carefully update and delete the messages in the local store.
Option 3 is keep the current read array but also to add isRead and readMeta properties on the message object. When we get the list of messages from the remote API we can overwrite the entire messages array, and then loop through the read array and copy the data into the corresponding message objects. This would also need to happen whenever the user reads a message – data would be duplicated in two places. This makes me feel uncomfortable, but maybe it's ok.
I've struggled to find many other examples of this type of store, but it could be that I'm just Googling the wrong thing. I'm quite new to Redux and some of my terminology is probably incorrect.
I'd really value any thoughts on this.
Using reselect you can memorize the results of the array.filter to prevent the array from being filtered when neither the messages or read arrays have changed, which will allow you to use Option 1.
In this way, you can easily store the raw data in your reducers, and also access the computed data efficiently for display. A benefit from this is that you are decoupling the requirements for data structure and storage from the requirements for the way the data is displayed.
You can learn more about efficiently computing derived data in the redux docs
How about using a lookup table object, where the id's are the keys.
This way you don't need to filter nor loop to see if a certain message id is there. just check if the object holds a key with the corresponding id:
So in your case it will be:
const isRead = !!this.props.read[this.props.currentMessage.messageId];
Small running example:
const read = {
1234: {
meta: 'Something'
},
5678: {
meta: 'Time etc'
}
};
const someMessage = {id: 5678};
const someOtherMessage = {id: 999};
const isRead = id => !!read[id];
console.log('someMessage is ',isRead(someMessage.id));
console.log('someOtherMessage is ',isRead(someOtherMessage.id));
Edit
I recommend reading about Normalizing State Shape from the redux documentations.
There are great examples of designing and organizing the data and state.
I am building a web application and I have a Grid Panel A who has Store A that uses a Model A. When the user clicks a certain entry E with an ID and clicks the delete button, what I want to happen is to get Store B then remove the entry with the same ID as the selected entry E.
Basically, what I'm trying to do is some sort of "cross-store" model deletion. The model from Store A gets selected, but the entry from Store B gets deleted.
Here's what I've done so far:
var userStore = Ext.getStore('borrowerListStore'); //this is Store B
var model = Ext.ModelManager.create({
}, 'myAppLicationName.model.borrowerList'); //this is Model B
model.set("ID", personID); //person id here is the ID of Entry E selected earlier
Ext.getBody().mask('Starting Client Delete...');
userStore.remove(model); //I remove the model from the store
//then I sync the store
userStore.sync({
success: function(batch){
Ext.getBody().unmask();
console.log('delete user details success');
},
failure: function(batch){
Ext.getBody().unmask();
console.log('delete user details failure');
}
});
However, I am stuck on the masking screen.
I also tried loading the store first as such before I remove then sync the store:
userStore.load({
callback: function() {
userStore.remove(model);
}
});
However, I still got stuck on the loading screen.
Is there any way to do a cross-store model deletion based on a model property? I know that I can get Store B then iterate through the models and then remove the one whose ID matches the ID of what the user selected. My issue with that is if I have a lot of records in my store, it would take a lot of time to search through those.
Okay I'm an idiot.
I did something like:
userStore.getProxy().extraParams = {
selectedUserID: personID
};
and I had a if-else handling part in the php if selectedUserID was passed, I'd query the database using that in my Where clause so I'd end up with 1 entry.
I can't figure out how Ember determines if it should update or create a record. I would assume its based on the ID or on the Store entry, but it seems to be something else. The code example clarifies:
// this returns the user without making an api call
currentUser.get('store').find('user_detail', '49')
// this returns 49
currentUser.get('id')
// this returns true
currentUser.get('store').hasRecordForId('user_detail', 49)
// this issues a create to api/userDetails instead
// of updating /api/userDetails/49
currentUser.save()
// maybe this is a lead, not the 48 at the end
currentUser.toString()
// <EmberApp.UserDetail:ember461:48>
// it looks as though currentState is involved here
// http://emberjs.com/api/data/classes/DS.RootState.html
currentUser.currentState
// returns root.loaded.created.uncommitted
currentUser.get('currentState.stateName');
// also isNew is wrong and returns true
currentUser.get('isNew');
Let me explain why I have this issue. My app has a current user. If you logout I update the current user. So I set Ember.currentUser.setProperties(newUserData). I update the currentUser object so that ember automatically triggers updates throughout my app. If I would replace the currentUser Ember.currentUser = newUser; Nothing would update. If I cant solve the above problem an alternative solution for the swapping of the user object would also work.
This is how I handle the global user state
container.register('user:current', Ember.currentUser);
// and handle updates via Ember.currentUser.setProperties()
application.inject('controller', 'user', 'user:current');
application.inject('route', 'user', 'user:current');
A proper solution would replace Ember.currentUser, however doing that doesnt trigger updates.
A new model will have the isNew and isDirty properties set to true, an existing record that needs to be updated will only have isDirty set to true.
I'd recommend pushing your user one level deeper and not storing it on the Ember namespace, that way you can set it from anywhere else, yet still inject it during injection
var users = Em.Object.create({
current: currentUser
});
container.register('users:current', users, {instantiate: false});
// and handle updates via Ember.currentUser.setProperties()
application.inject('controller', 'users', 'users:current');
application.inject('route', 'users', 'users:current');
Then from any controller you can access/watch it on users.current, yet you can also set it using this.users.set('current', newUser) which would effect anyone watching that property on any controller or route.
Example: http://emberjs.jsbin.com/OxIDiVU/1145/edit
Additionally a lot of things you are doing are async calls and should use the promise pattern for viewing properties etc.
I use datatables on the client to allow speedy live sorting/filtering of around 10,000 rows of data. It is much faster to supply an array of rows to a DataTable during table creation than to add the rows individually. I can use the onReady function in subscribe to achieve this.
If I then call observe to pick up changes, I get the data already supplied in subscribe again.
While I can hack around this, I presume I am just not using meteor correctly and appreciate any advice.
Here is some sample code:
Meteor.subscribe("books", function(){
// Runs when subscription is complete
var mData = Books.find().fetch();
MyTable = $('#testTable').dataTable( {
'aoColumns': [
{ sTitle: 'title', sClass: 'alignRight', mDataProp: 'title'},
],
'aaData' : mData
});
// Add any new books.
Books.find().observe({added: function(item){
// ERR: Adds the books already fetched into mData as well as any new books.
MyTable.fnAddData([item]);
}});
});
There's a hidden option to observe ({_suppress_initial: true}) that avoids this behaviour. I'm not sure if it's a good idea to use it, but it is there.
As for advice around how to structure your code; it's not as easy as it should be, but I think you want to something like the following:
Wrap your table in a {{#constant}} helper so it never gets re-rendered.
Make sure the table doesn't get rendered the one-and-only time until the data is ready (this could help: https://github.com/oortcloud/unofficial-meteor-faq#how-do-i-know-when-my-subscription-is-ready-and-not-still-loading)
Do your code above in the table's Template.table.rendered callback.
That approach seems more modular.