I'm trying to remove a field from a changeset (or set it to undefined) so that when the changeset is applied the field will be removed (or set to undefined) on the model. How can this be achieved?
Cheers
ember-changeset does not support setting a value to undefined. Calling changeset.set() with undefined as value does not set the value.
let obj = Ember.Object.create({
foo: 'a',
bar: 'b'
});
let changeset = new Changeset(user);
changeset.set('foo', null);
changeset.set('bar', undefined);
changeset.get('bar'); // b
changeset.get('change'); // { foo: null }
I'm quite surprised about that myself. Since it's not expected behavior and it does not seem to be documented, I think it should be considered a bug and get fixed.
I've opened a pull request for ember-changeset adding a failing test: https://github.com/DockYard/ember-changeset/pull/191
Related
I am doing a store query in the controller and then passing the result down to a child component.
// Controller
#tracked comment;
#action
async fetchComment() {
const comment = await this.store.query('note', {
filter: {
listing: this.listing.id,
},
});
if (comment && comment.length > 0) {
this.comment = comment.firstObject;
}
}
// Template
<MyComponent #comment={{this.comment}} />
I want to use the arg to populate a tracked property in the child component.
// Component
#tracked comment = this.args.comment;
I found this does not work but works well as a getter. However with a getter I am unable to set this to null when deleting that record.
I have also tried to set the tracked property in the Constructor but that didnt work either.
I suspect this has something to do with passing in a promise or store object in the args because this works fine with static data.
why your code does not work
this code can not work:
#tracked comment = this.args.comment;
this is because comment on the controller is initially undefined but will later bet set to comment.firstObject when the network request is done and the await in your fetchComment function returns.
Generally everythings on args basically always behaves like its #tracked (while more accurate you would describe it as getters). So this usually will just update fine. But the assignment #tracked comment = this.args.comment; only happens once when you create the component, so you no longer depend on updates on args.
why you can not set this.args.comment to null
If you use a getter or directly always use this.args.comment you can not change this reference. This is because this.args is always readonly. you can change objects on this.args.something, but you never can change the reference or primitive value on this.args.
Sidenote: this is only true if the component was called with <AngleBracket /> syntax. For the older {{curly-component}} syntax this is not true. So this does not depend on the component itself but how the component gets called.
you could notify the controller to remove the reference
one good thing to do is to pass down a deleteComment action to the component that basically does something like this.comment = null on the controller. then you use this.args.comment directly or by a getter and you can call this.args.deleteComment() to set comment on the controller to null, which will update anything that uses this.args.comment or a getter that returns this.args.comment.
this is essentially because in your architecture the controller owns the data (because it loads it). so the controller is also responsible to delete it.
if you use ember-data you can check isDeleted
if its a ember-data model (which it probably is if you call this.store) then it has a isDeleted property. you can use this to check if the record is deleted, since ember-data records dont disappear if they get deleted. which is exactly because of problems like this.
how you use another property to shadow a argument
you could do something like this to shadow an argument in your component:
#tracked commentIsDeleted = false;
get comment() {
return this.commentIsDeleted
? null
: this.args.comment;
}
this way at first this.comment will work like a normal getter, but you can shadow delete it by setting this.commentIsDeleted = true;. From that on this.comment will be null.
I'm trying to use DS.hasMany.addArrayObserver to monitor when new elements are inserted (either created client side or loaded from the server).
However, the arguments passed to the arrayWillChange and arrayDidChange callbacks show inconsistent behavior between observing changes to a "plain" Ember.A vs a DS.hasMany relationship.
For the Ember.A array, the proper addCount and removeCount are passed, but with the DS.hasMany the removeCount is always equal to the old length of the array and addCount is always equal to the new length of the array.
In other words, it looks like the DS.hasMany "array" is emptied and all new records are added every time. I have two questions:
Is this the expected behavior?
If so, what is the best way to monitor just added/removed elements on a DS.hasMany relationship?
I have set up a demo of the behavior: DEMO
EDIT:
I have also opened an issue on the ember-data github: https://github.com/emberjs/data/issues/2981
EDIT 2:
This looks like it is expected behavior. Ember-data is generating a new array by filtering out deleted items and concating new items. This happens in flushCanonical: https://github.com/emberjs/data/blob/339b79aa3ea82e9be1a8f39db36ec1133d0b65a2/packages/ember-data/lib/system/many-array.js#L67
I have solved my problem, in a somewhat non-intuitive way. Instead of adding an ArrayObserver directly to the model's HasMany relationship, I am using DS.Store.filter() to get the relevant records, and using addArrayObserver to the results.
Relevant code:
var collection = this.store.filter('child', function(record){ return record.get('parent.id') === "1"; });
collection.then(function(records){
records.addArrayObserver({
arrayWillChange: function(obj, start, removeCount, addCount) {
console.log("Filter", start, removeCount, addCount);
},
arrayDidChange: Ember.K
});
});
DEMO
once a certain process is done I need to set a boolean to true in order to update the template.
I can easily get the object, but setting a property seems to be more difficult. What I use to get the object is
var found = self.get('content').findProperty('id', self.datasetid);
If I do that in the chrome console I can clearly see that I get an ember object back:
Object {id: 1, active: true}
__ember1364221685101_meta: Meta
active: true
get data_set: function () {
id: 1
set data_set: function (value) {
__proto__: Object
When I do:
found.set('data_set.fully_geocoded', true);
I do get the error mentioned in title. I've tried as many different flavours as I could think of, but all with the same result.
Could somebody shine a light on this?
An Object isn't an instance of Ember.Object, but the base Javascript class Object, so it won't have a get and set method.
You can get much of the same functionality by using Ember.get and Ember.set directly, passing in the object, as such:
Ember.set(found, 'data_set.fully_geocoded', true)
Ember.get(found, 'data_set.fully_geocoded')
Computed properties and observers can also fire based on using Ember.set this way.
I've kinda been struggling with this for some time; let's see if somebody can help me out.
Although it's not explicitly said in the Readme, ember-data provides somewhat validations support. You can see that on some parts of the code and documentation:
https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/model/states.js#L411
https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/model/states.js#L529
The REST adapter doesn't add validations support on itself, but I found out that if I add something like this in the ajax calls, I can put the model on a "invalid" state with the errors object that came from the server side:
error: function(xhr){
var data = Ember.$.parseJSON(xhr.responseText);
store.recordWasInvalid(record, data.errors);
}
So I can easily to the following:
var transaction = App.store.transaction();
var record = transaction.createRecord(App.Post);
record.set('someProperty', 'invalid value');
transaction.commit()
// This makes the validation fail
record.set('someProperty', 'a valid value');
transaction.commit();
// This doesn't trigger the commit again.
The thing is: As you see, transactions don't try to recommit. This is explained here and here.
So the thing is: If I can't reuse a commit, how should I handle this? I kinda suspect that has something to do to the fact I'm asyncronously putting the model to the invalid state - by reading the documentation, it seems like is something meant for client-side validations. In this case, how should I use them?
I have a pending pull request that should fix this
https://github.com/emberjs/data/pull/539
I tried Javier's answer, but I get "Invalid Path" when doing any record.set(...) with the record in invalid state. What I found worked was:
// with the record in invalid state
record.send('becameValid');
record.set('someProperty', 'a valid value');
App.store.commit();
Alternatively, it seems that if I call record.get(...) first then subsequent record.set(...) calls work. This is probably a bug. But the above work-around will work in general for being able to re-commit the same record even without changing any properties. (Of course, if the properties are still invalid it will just fail again.)
this may seem to be an overly simple answer, but why not create a new transaction and add the pre-existing record to it? i'm also trying to figure out an error handling approach.
also you should probably consider writing this at the store level rather than the adapter level for the sake of re-use.
For some unknown reason, the record becomes part of the store default transaction. This code works for me:
var transaction = App.store.transaction();
var record = transaction.createRecord(App.Post);
record.set('someProperty', 'invalid value');
transaction.commit()
record.set('someProperty', 'a valid value');
App.store.commit(); // The record is created in backend
The problem is that after the first failure, you must always use the App.store.commit() with the problems it has.
Give a look at this gist. Its the pattern that i use in my projects.
https://gist.github.com/danielgatis/5550982
#josepjaume
Take a look at https://github.com/esbanarango/ember-model-validator.
Example:
import Model, { attr } from '#ember-data/model';
import { modelValidator } from 'ember-model-validator';
#modelValidator
export default class MyModel extends Model {
#attr('string') fullName;
#attr('string') fruit;
#attr('string') favoriteColor;
validations = {
fullName: {
presence: true
},
fruit: {
presence: true
},
favoriteColor: {
color: true
}
};
}
I have an Ember.Object that I'm updating with a property like below, but if I change primaryDemo twice in a row, it doesn't fire, yet if I change primaryDemo, then Rate, it does change. I'm puzzled as to why this is and how I can fix it.
dependantChanged: function() {
console.log('Firing change');
this.get('_update')(this);
}.observes('primaryDemo', 'Rate', 'Totals'),
UPDATE: So the first answer and fiddle got me thinking as to what the problem was, and it's due to changing a property on an object and not the object itself. I think ember does a hash check to see if there is a difference. In my case I'm already using underscorejs, so I just change the property, then use _.clone(demo) before doing the set. I'd rather not do that, so will wait to see if there is a more elegant solution before closing this.
You don't need to set primaryDemo again. In the example that does nothing. You need to force tell Ember to notify your observer. See this fiddle...
var demo = { Imps: 1, Demo: { Id: 2 } }
var obj = Ember.Object.create({
dependantChanged: function() {
console.log('Firing change');
}.observes('primaryDemo', 'Rate', 'Totals'),
});
obj.set('primaryDemo', demo);
demo.Imps = 2;
obj.set('primaryDemo', demo);
// Notify observers on obj#primaryDemo
Ember.notifyObservers(obj, 'primaryDemo');
Can you give more details? I created a simple JSFiddle http://jsfiddle.net/JjbXb/ from your description but changing the same property in a row, as you say, works.
Are you sure the value of primaryDemo is different in your 2 consecutive calls?