Updating multiple objects with ember-data and non-rest action - ember.js

Lets say I have a Customer who hasMany products
The api has a PUT: /customers/:id/cancel that cancels the customer and also cancels all the products and then returns the customer and the products for sideloading:
{
customer: {
id: 1,
name: "Customer Name",
canceled: true,
products: [1, 2] },
products: [
{id: 1, customer_id: 1, name: "Product 1", canceled: true},
{id: 2, customer_id: 1, name: "Product 2", canceled: true}
]
}
how would I:
Call that action on the customer?
Update all objects in the Ember Store?

For part 1. of your question,
You'll want to find it in the store var customer = store.find('customer', 1);
Then tell it to delete customer.destroyRecord();
And finally commit the delete customer.save();
For part 2. deleting the products, you'll have to do delete them manually in a forEach loop, tucked inside an Ember.run.once., as there is no cascading delete in ember data at this stage of the game.
Here is an example of this for a top level delete, so you would need to follow the same logic but for your products and follow it up with a .then that deletes your connected customer.
Single tier Example: http://jsbin.com/samegiqe/1/edit?js,output
Connected StackOverflow for further reading: How to delete all records associated with an ember model without clearing local Storage?
Complex mixin to solve this: http://jsbin.com/hupabovo/1/edit
Its connected StackOverflow: Delete associated model with ember-data
Hope that gets at what you were asking. Cheers!

Related

How to create register with relation model embeded?

I'm using Loopback 3 to create my application, with Postgres.
I've created a many-to-many usign hasManyThrough relation like this:
Product has many Composition
Ingredient has many Composition
Product has many Ingredient
Composition belongs to Product
Composition belongs to Ingredient
How can I create/edit a Product with a array of Ingredient's id, like:
POST /products
{
name: "Potato Chips",
ingredients: [ 5, 7, 3, 20 ]
}
And how can I get Product with list of Ingredients embeded?
GET /products/1
{
id: 1,
name: "Potato Chpis",
ingredients: [
{ name: "Potato" },
{ name: "Vegetal Oil" }
...
]
}
1/ To create a Product with many Ingredients, I suggest to make a custom remote that takes your product in the body along with the ingredients, and make a loop over the ingredients to link them to your product one by one, using the add method (assuming that Product has many Ingredients through Composition):
Product.ingredients.add(ingredientData, callback);
2/ To get a Product with its embedded list of Ingredients, you need to include the relation property to your GET route (example given in reactjs):
const response = await Axios.get(`/api/products/${productId}`, {
params: {
filter: {
include: [
{ relation: 'ingredients' },
],
},
},
});

EmberJS - How to remove an object from hasMany relationship and then repush it?

I try to remove a model from a relationship and later import it again by using pushPayload.
The relationship is only updated on one side but not an the reverse side.
this.get('store').pushPayload({
folder: {
id: 1,
name: 'My folder'
}
});
this.get('store').pushPayload({
item: {
id: 2,
name: 'My item',
parentFolder: 1
}
});
var folder = this.get('store').peekRecord('folder', 1);
var item = this.get('store').peekRecord('item', 2);
console.log('Parent folder id: ', item.get('parentFolder.id'), 'Items length', folder.get('items.length'));
item.get('parentFolder').get('items').removeObject(item);
console.log('Parent folder id: ', item.get('parentFolder.id'), 'Items length', folder.get('items.length'));
this.get('store').pushPayload({
item: {
id: 2,
name: 'My item',
parentFolder: 1
}
});
console.log('Parent folder id: ', item.get('parentFolder.id'), 'Items length', folder.get('items.length'));
I would expect to get the following output:
Parent folder id: 1 Items length 1
Parent folder id: undefined Items length 0
Parent folder id: 1 Items length 1
But I get this one:
Parent folder id: 1 Items length 1
Parent folder id: undefined Items length 0
Parent folder id: 1 Items length 0
The issue is the last Items length.
https://ember-twiddle.com/d58d95d5be0cc8750282b9cc48db6489
I reworked your twiddle with annotated comments on what happens with each push. The difference between push and pushPayload is that the latter calls the former once it has normalized the data into the format you see here.
https://ember-twiddle.com/c65c33ce0b1810258b75b5679b75d0e4?fileTreeShown=false&openFiles=controllers.application.js%2C
The short answer is that folder has local state (the removal of the item) that must be cleared in order for your server changes to take effect. While unloadRecord is one way to achieve this, that is an accidental side-effect and there are many better ways (one is shown in my twiddle using push, you could also do a pushObject to put the item back that you removed).
You need to unload the record if you are going to use pushPayload to add it back in and you wish the payload to "win" over what you have locally on relationships:
item.get('parentFolder').get('items').removeObject(item);
this.get('store').unloadRecord(item);
this.get('store').pushPayload({
item: {
id: 2,
name: 'My item',
parentFolder: 1
}
});

Apollo: Refetch queries that have multiple variable permutations after mutation

Let's say I have a table that lists a bunch of Posts using a query like:
const PostsQuery = gql`
query posts($name: string) {
posts {
id
name
status
}
}
`;
const query = apolloClient.watchQuery({query: PostsQuery});
query.subscribe({
next: (posts) => console.log(posts) // [ {name: "Post 1", id: '1', status: 'pending' }, { name: "Paul's Post", id: '2', status: 'pending'} ]
});
Then later my user comes along and enters a value in a search field and calls this code:
query.setVariables({name: 'Paul'})
It fetches the filtered posts and logs it out fine.
// [ { name: "Paul's Post", id: '2', status: 'pending'} ]
Now, in my table there is a button that changes the status of a post from 'Pending' to 'Active'. The user clicks that and it calls code like:
const PostsMutation = gql`
mutation activatePost($id: ID!) {
activatePost(id: $id) {
ok
object {
id
name
status
}
}
}
`;
apolloClient.mutate({mutation: PostsMutation});
All is well with the mutation, but now I want to refetch the table data so it has the latest, so I make a change:
apolloClient.mutate({
mutation: PostsMutation,
refetchQueries: [{query: PostsQuery, variables: {name: 'Paul'}]
});
Hurray, it works!
// [ { name: "Paul's Post", id: '2', status: 'active'} ]
But... now my user clears the search query, expecting the results to update.
query.setVariables({});
// [ {name: "Post 1", id: '1', status: 'pending' }, { name: "Paul's Post", id: '2', status: 'pending'} ]
Oh no! Because the data was not refetched in our mutation with our "original" variables (meaning none), we are getting stale data!
So how do you handle a situation where you have a mutation that may affect a query that could have many permutations of variables?
I had a similar issue, I am using Apollo with Angular, so I am not sure if this method will work with React Client, but it should.
If you look closely at refetchQueries properties on the mutate method, you will see that the function can also return a string array of query names to refetch. By returning just the query name as a string, you do not need to worry about the variables. Be advised that this will refetch all the queries matching the name. So if you had a lot queries with different variables it could end up being a large request. But, in my case it is worth the trade off. If this is a problem, you could also get access to the queryManager through apolloClient.queryManager which you could use to do some more fine grained control of what to refetch. I didn't implement it, but it looked very possible. I found the solution below fits my needs fine.
In your code, what you need to do is:
apolloClient.mutate({
mutation: PostsMutation,
refetchQueries: (mutationResult) => ['PostQueries']
});
This will refetch any query with the name 'PostQueries'. Again, it is possible to only refetch a subset of them if you dig into the queryManager and do some filtering on the active watch queries. But, that is another exercise.

Ember data sideloaded models ignored

I'm new to Ember, and am having a problem I'm not seeing duplicated anywhere else so I'm sure I'm doing something silly. I have these models:
App.User = DS.Model.extend({
username: DS.attr("string"),
userId: DS.attr("number"),
modules: DS.hasMany("App.Module")
});
App.Module = DS.Model.extend({
moduleId: DS.attr("number"),
name: DS.attr("string")
});
Note that my Module model is simply a container that a User can have a few of, and many users might share the same modules - they're actually represented by an Enum on the server. As such, the backend does not have a mapping of Module > Users, so I've left the DS.ownedBy or DS.hasMany out of App.Module. I have, however, tried my test with a "users: DS.hasMany('App.User')" in there as well, with the same result. If it turns out to be necessary, I could maintain such a mapping, but I have no other reason for doing so right now, so it would be a bit unfortunate to be forced to add such a table to my database.
I'm using Ember.Auth for authentication, and when my app loads up and I log in, the server requests (as expected):
users/nathan?authToken=<token>
The result is also as I think it should be, according to the ember data docs:
{
"user": {
"username": "nathan",
"user_id": 1,
"module_ids": [1,2]
},
"modules": [
{"module_id": 1, "name": "Account Settings"},
{"module_id": 2, "name": "Manage Websites"}
]
}
I'm then testing in the Chrome console with:
App.Auth.get("user").get("modules");
or
App.User.find("nathan").get("modules");
and in both cases, Ember makes a request to my server to get the data for Modules 1 and 2. If I repeat the same request, it doesn't go back to the server again, so it is storing the result properly that time, it's simply the sideload that it's ignoring.
I'm using ember-1.0.0-rc4 with ember-data-0.13.
In your sideload response, module_id should be id.
(or you can configure ember-data to use module_id, but formatting the server response should be the easier way?)
--- edit for explanation ---
I'm not sure the original REST call is "working perfectly". Without the id. ember-data does see the two modules that your originally sideloaded, but it sees no id, so it does not know that they are modules 1 and 2 respectively. By default (changeable), ember-data expects the id to be called id; your module_id (and user_id) are just treated as regular properties.
On your next API call (to /modules?ids[]=1&ids[]=2 presumably), ember-data silently assumes that, since your requested two modules, and two modules came back, they should be the two that you requested. Try sending back this
{
"modules": [
{"module_id": 3, "name": "Foo module"},
{"module_id": 4, "name": "Bar module"}
]
}
for the request /modules?ids[]=1&ids[]=2, you will still get your "successful" observation.
Try App.Module.find(1).get('name') with the module_id-response - you will get undefined; now try it with the id-response, and you will get Account settings as expected.
Have you configured your RestAdapter to sideload modules?
Like this:
DS.RESTAdapter.configure('App.Module', {
sideloadsAs: 'modules'
});

Ember data: Insert new item at beginning of array instead of at the end

In my application I'm displaying a timeline of messages. We retrieve them from the server in descending chronological order, newest to oldest.
3 - Howdy
2 - Greetings
1 - Mahalo
Our users also have the ability to add a new message which by default gets inserted at the end of the queue like so
3 - Howdy
2 - Greetings
1 - Mahalo
4 - I'm the new message, last as usual
When I submit, I'd like new messages to show up at the top. I've written a function before that reverses the array of items, but that wouldn't work for items already in the array.
4 - I'm the new message, first finally
3 - Howdy
2 - Greetings
1 - Mahalo
What would be the best approach in this case? The ideal would be for Ember Data to prepend to the content array rather than append. Is there another option which might be better?
For most scenarios involving sorting it's recommented to use Ember.SortableMixin, which is baked into Ember.ArrayController.
Please refer to this conceptual example in JSFiddle: http://jsfiddle.net/schawaska/tbbAe/
In this sample the model has a DateTime field named when, which I'm using for filtering:
App.Greeting = DS.Model.extend({
text: DS.attr('string'),
when: DS.attr('date')
});
App.Greeting.FIXTURES = [
{id: 1, text: 'First', when: '3/4/2013 2:44:52 PM'},
{id: 2, text: 'Second', when: '3/4/2013 2:44:52 PM'},
{id: 3, text: 'Third', when: '3/4/2013 2:44:52 PM'},
{id: 4, text: 'Fourth', when: '3/4/2013 3:44:52 PM'}
];
In the controller the only thing I have to do is to set the name of the property and the sorting direction:
App.SortingMixinController = Em.ArrayController.extend({
sortProperties: ['when'],
sortAscending: false
});
Then in my Handlebars template, I can use the {{each}} helper as I would do normally.
Because in this sample, all the dates are the same except for the Forth (which because of sorting appears first), and also because of SortableMixin, these values will be sorted through another property - I'm assuming the Id here.
The other approach I've taken in that fiddle is using a computed property. I'm not really sure about that approach as it seems to consume more resources and the code in App.SortingPropertyController is worthy of laugh, but sort of works to show possibilities.
Can you use basic JavaScript to drop in the new item at a given location in the array? Not sure if this is basic ajax + js objects or full blown ember-data models but this works for the simple js array example
var arr = [];
arr[0] = "Jani";
arr[1] = "Hege";
arr[2] = "Stale";
arr[3] = "Kai Jim";
arr[4] = "Borge";
console.log(arr.join());
arr.splice(2, 0, "Lene");
console.log(arr.join());
The output of the code above will be:
Jani,Hege,Stale,Kai Jim,Borge
Jani,Hege,Lene,Stale,Kai Jim, Borge