Open BryanHunt opened 7 years ago
Just discovered that if I make a second change to the "deliverables" field, I don't get a change in the "subscribers"
{deliverables: Array(2), subscribers: true}
{subscribers: true}
{}
{deliverables: Array(2)}
I can't tell you why this is happening because I don't know what you are doing. I would need to see the whole picture of what is going on, from start to finish.
I also experience a similar issue. Assume a model person
with a hasMany relation to roles
.
When adding one role to the model person.changed()
yields { roles: true }
but hasDirtyRelations
equals false
. When adding two roles hasDirtyRelations
equals true
?!
oooo .. this sounds like a bug.
can you do me a huge favour and make a test to show this ( broken behaviour ) and slap it in a PR so I can fix it / see the breaking thing / check it out that way?
I have an example project that demonstrates this issue.
git clone https://github.com/BryanHunt/auto-save-test.git
cd auto-save-test
npm install
ember server
Open your browser http://localhost:4200 Click on one of the two task links that are displayed.
The first problem you will notice is that isDirty
is true when the model hasn't been modified.
To replicate the problem described in this issue:
Thanks Bryan for writeup and this repo with bug example. It would be nice to have a failing test in change tracker repo, and hopefully I can make one on my own with your setup in this repo you made.
what you are doing is:
loading all tasks ( in application route ) the tests are loaded with no comments then you load the task again asking for comments ( in the task route )
the task already exists so ember data just takes the comments and slaps them on the existing task, meaning now the comments are added and the relationship is dirty, since you just added comments to a task.
this would be no big deal IF ember data called the didUpdate hook for this kind of update ( new payload info ). But it does NOT and I think it is a bug.
Change tracker will call saveChanges for you when/if the didUpdate hook is called. ( Problem solved )
So, ember-data does NOT fire the didUpdate hook when it updates a model with pushPayload ( which is happening when you loading task again with comments included ) since all it is really doing is pushing more data to one of the existing tasks that you loaded from the application route.
to solve your problem you simply have to do this:
// routes/task.js
// make a custom setupController method. once the task model is ready and loaded.
// it would be nice if ember-data fired the didUpdate method, but since it does not,
// you have to saveChanges() on the model manually and here is the perfect place to do it.
setupController(controller, model) {
model.saveChanges();
controller.setProperties({model});
}
make sense?
@danielspaniel Thx for clarifying this! But one question remains to me: This works for a 'standard' model - how would this work for an RSVP.hash? E.g.
model(params) {
return RSVP.hash({
modelA: this.store.findRecord('model-a', params.id, {include: 'model-bs.model-c'}),
modelG: this.store.query('model-c', {sort: 'whatever'}
});
},
modelA is the one with the dirty relationship. I did try
setupController(controller, model) {
model.modelA.saveChanges();
controller.setProperties({model});
},
but this did not bring the expected result...
You have to be more specific about what result your talking about .. I can guess, but I have to be super sure. If you can put this example in your repo and update it and tell me what you expect ( exactly ) then I can tell you what I think can be done
I'm using ember-data-change-tracker inside a custom adapter linking ember data with Spring data REST. It's solved my issue of observing changes to relationships and automatically making the correct API calls on model.save()
.
However, I've seen this issue in my code. When an async belongsTo or hasMany is loaded and auto tracking is true, that relationship is considered to be 'changed', i.e. model.modelChanges()
returns { myBelongsTo: true }
. This appears to be the same problem described in this Issue.
I've written some code to reset the relationship tracking on load
Here is a unit test snippet that illustrates the problem
import Application from '@ember/application';
import { initialize } from 'dummy/initializers/relationship-tracking';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { run } from '@ember/runloop';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
module('Unit | Initializer | relationship-tracking', function(hooks) {
setupTest(hooks);
setupMirage(hooks);
hooks.beforeEach(function() {
this.TestApplication = Application.extend();
this.TestApplication.initializer({
name: 'initializer under test',
initialize
});
this.application = this.TestApplication.create({ autoboot: false });
});
hooks.afterEach(function() {
run(this.application, 'destroy');
});
test('loading a belongsTo relationship should not mark this relationship as \'changed\'', async function (assert) {
await this.application.boot();
let office = server.create('office', {
name: 'Leeds'
});
let person = server.create('person', {
name: 'Fred',
office
});
const store = this.owner.lookup('service:store');
let model = await store.findRecord('person', person.id);
assert.deepEqual(model.modelChanges(), {}, 'model should have no changes');
model.set('name', 'Bob');
await model.get('office');
assert.deepEqual(model.modelChanges(), {
'name': [
"Fred",
"Bob"
]
}, 'model should not think belongsTo relationship has changed after loading relationship');
});
test('loading a hasMany relationship should not mark this relationship as \'changed\'', async function (assert) {
await this.application.boot();
let desk = server.create('desk', {
colour: 'blue'
});
let office = server.create('office', {
name: 'Leeds',
desks: [desk]
});
const store = this.owner.lookup('service:store');
let model = await store.findRecord('office', office.id);
assert.deepEqual(model.modelChanges(), {}, 'model should have no changes');
model.set('name', 'London');
await model.get('desks');
assert.deepEqual(model.modelChanges(), {
'name': [
"Leeds",
"London"
]
}, 'model should not think hasMany relationship has changed after loading relationship');
});
});
Here is an initializer that reopens the Store and makes the fix
import Store from 'ember-data/store';
import Tracker from 'ember-data-change-tracker/tracker';
export function initialize(/* application */) {
Store.reopen({
findBelongsTo(internalModel, link, relationship /*, options */) {
return this._super(...arguments)
.then(data => {
this._resetChangeTrackingOfRelationship(internalModel.getRecord(), relationship.name);
return data;
});
},
findHasMany(internalModel, link, relationship /*, options */) {
return this._super(...arguments)
.then(data => {
this._resetChangeTrackingOfRelationship(internalModel.getRecord(), relationship.name);
return data;
});
},
_resetChangeTrackingOfRelationship(model, relationshipName) {
Tracker.saveKey(model, relationshipName);
}
});
}
export default {
initialize
};
It seems to work, but I'm not entirely sure I'm happy making modifications to behaviour in 2 libraries simultaneously (i.e. both ember-data and ember-data-change-tracker). It would be great if this feature was offered by default inside the addon.
wow .. this is blast from the past. but i would suggest you make a PR and write a test or make the fix in the change tracker repo, that way I can check it out and see if it makes sense. I still don't follow the test and for sure you can't do a fix where you open the ember-data store. that not going to make anyone happy. but if you can do a better fix or explain the issue in a test ( in change tracker ) then I happy to check it out
I've done some digging and found the route cause. Details and tests in #66
The behaviour of ember-data-change-tracker is closely linked to the chosen serialization format between the UI and the server. When resource linkage for associations is present in the response the association is not dirty on load, but when resource linkage is missing then the association becomes dirty on load
I'm working on some code to autosave my model. It looks like:
I have the model configured with:
changeTracker: { trackHasMany: true, auto: true, enableIsDirty: true }
When I change a string "deliverables" attribute in the model, I get on the console:
Why does it think subscribers changed? subscribers is modeled as:
subscribers: hasMany('user', {inverse: null})