Save associated record in Ember - javascript

I am a beginner in Ember and trying to implement a simple post and comment app.
I have a rails background and hence i'm using Rails API for this.
I have followed a tutorial and i'm able to save a post, fetch all its comments and delete the post. However i'm having issues in saving comment related to the post.
Following is the code for models
post.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
body: DS.attr('string'),
comments: DS.hasMany('comment')
});
comment.js
import DS from 'ember-data';
export default DS.Model.extend({
author: DS.attr('string'),
body: DS.attr('string'),
post: DS.belongsTo('post')
});
routes/post/comment/new.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return {};
},
renderTemplate() {
this.render('post.comment.new', { into: 'application' });
},
actions: {
save() {
const post = this.modelFor('post');
const newComment = this.get('store').createRecord('comment', this.currentModel);
newComment.set('post', post);
newComment.save().then(() => {
this.transitionTo('post', post);
});
},
cancel() {
this.transitionTo('post', this.modelFor('post'));
}
}
});
router.js
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
this.route('posts');
this.route('post.new', { path: 'posts/new' });
this.resource('post', { path: 'posts/:post_id' }, function() {
this.route('comment.new', { path: 'comments/new' });
});
});
export default Router;
Saving the comment is where i'm facing an issue. This is really strange but while saving the comment, the params passed to the server looks like
Parameters: {"comment"=>{"author"=>"dsa", "body"=>"asd", "post"=>"9"}}
Unpermitted parameter: post
From what i understand, the parameter should be post_id and not post. If post is being passed, then it should be object. I may be wrong of course because i don't have a clear understanding in Ember yet.
On randomly fiddling with the code, i found that if i replace the relationship in comments model from
post: DS.belongsTo('post')
to
post_id: DS.belongsTo('post')
the params passed to server are
Parameters: {"comment"=>{"author"=>"fg", "body"=>"dfs", "post_id"=>nil}}
This however doesn't actually pass the post_id as its nil.
This might be absolutely wrong and not how its supposed to work but i'm clueless.
Thanks for any help.

Create comment serializer and override keyForRelationship method like below :
keyForRelationship(key/*, relationship, method*/) {
if(key === 'post') return 'post_id';
return this._super(...arguments);
}
and the post relation should be :
post: DS.belongsTo('post')

Related

Cannot Access Ember child route via url and redirects me to the parent route

I want to access a child route via url eg:https://my-app.com/dashboard/library. When i click this, Ember redirects me to https://my-app.com/dashboard and populates this route's model data correctly, but i want to go to https://my-app.com/dashboard/library, with its new model data.
From the other hand i can access https://my-app.com/login via url, that has no model data btw.
At environment.js i have locationType: "auto", and my router.js is like:
Router.map(function() {
this.route('login');
this.route('dashboard', function() {
this.route('child', {path: '/child/:child_id'});
this.route('library');
});
});
My Routes:
// Route: dashboard
import Ember from 'ember';
import RouteHistoryMixin from 'ember-route-history/mixins/routes/route-history';
export default Ember.Route.extend(RouteHistoryMixin, {
model: function() {
let userId = this.authentication.getPlayerId();
let actions = this.store.findAll('action');
return Ember.RSVP.hash({
actions: actions,
tasks: Ember.RSVP.all([actions]).then((actions) => {
return this.store.findAll('task')
}),
rank: this.store.findAll('rank')
});
},
afterModel: function(){
this.transitionTo('dashboard.index'); // needless
},
setupController(controller, model) {
this._super(...arguments);
Ember.set(controller, 'tasks', model.tasks);
Ember.set(controller, 'rank', model.rank);
}
// . . .
And
// Route: dashboard/library
import Route from '#ember/routing/route';
import Ember from 'ember';
import RouteHistoryMixin from 'ember-route-history/mixins/routes/route-history';
export default Route.extend(RouteHistoryMixin, {
complete: Ember.inject.service(),
queryParams: {
taskId: {}
},
model(params) {
if(params.taskId) {
return Ember.RSVP.hash({
article: this.store.query('article', { filter: '{"inSyllabus": true}'}),
task: this.store.query('task', { filter: '{"id": ' + params.taskId + '}'})
});
}
else {
return Ember.RSVP.hash({
article: this.store.query('article', { filter: '{"inSyllabus": true}'})
});
}
},
setupController(controller) {
this._super(...arguments);
if (controller.taskId)
this.store.findRecord('task', controller.taskId).then((task) => {
controller.set('libraryQueryParam', task.points);
// Notify Task completion
let payload = {
startDate: new Date(),
endDate: new Date(),
points: task.points,
entries: 1
};
// PUT HTTP COMMAND FOR PLAYER
let playerTask = this.store.createRecord('playTaskTest', payload);
playerTask.save();
});
}
// . . .
May be a configuration flag or a Router config issue ?
How can i access this child route via url or has something like that happened to any of you?
I think the issue is in the dashboard. at the afterModel hook:
afterModel: function(){
this.transitionTo('dashboard.index'); // needless
}
This part redirects to dashboard.index every time you call dashboard route. Remember dashboard.index is a child route same as child and library so you will never reach them.

Ember: No model was found for 'user' and Duplicate POSTs created when executing the save promise

UPDATE:
Can anyone help? I have been pursuing this without luck for the better half of this week. I do notice that the client is generating two POSTs. I have added code for the adapter. Is there anywhere else I should be looking?
I am going through the video tutorial provided below and am unable to resolve two errors when I click the submit button to save data to the database.
No model was found for 'user'
Two POSTs are being generated. This results in an Assertion Failed error, which I suspect is because the ID returned from the server does not match the current ID on the front-end.
I see that the database has two new records. When I click on the submit button again then the application takes me back to the todo-items page where it shows the two records. Can anyone advise what I am doing wrong?
Current versions:
Ember : 3.2.2
Ember Data : 3.2.0
jQuery : 3.3.1
Ember Simple Auth : 1.7.0
Video tutorial (the error occurs at the 11:30 mark): https://www.youtube.com/watch?v=bZ1D_aYGJnU. Note: the author of the video seems to have gotten the duplicate POST issue to go away right at the end of the video, but I do not see how.
Component/forms/todo-item-form/component.js
import Component from '#ember/component';
export default Component.extend({
actions:{
save(){
this.get('submit')();
}
}
});
Component/forms/todo-item-form/template.hbs
<form {{action "save" on="submit"}}>
{{input placeholder="description" value=todoItem.description}}
<br />
{{#if todoItem.validations.isValid}}
<button type="submit">Add</button>
{{else}}
<button type="submit" disabled>Add</button>
{{/if}}
</form>
templates/s/todo-items/add.hbs
{{forms/todo-item-form
todoItem=model
submit=(route-action "submitAction")
}}
{{outlet}}
models/todo-item.js
import DS from 'ember-data';
import { validator, buildValidations } from 'ember-cp-validations';
const { attr, belongsTo } = DS;
const Validations = buildValidations({
description: [
validator('presence', true),
validator('length', {
min: 4
})
]
});
export default DS.Model.extend(Validations, {
description: attr('string'),
owner: belongsTo('person')
});
adapter/Application.js
import DS from 'ember-data';
import ENV from 'todo-list-client/config/environment';
const {computed, inject :{service} } = Ember;
export default DS.JSONAPIAdapter.extend({
session: service(),
namespace: ENV.APP.namespace,
host: ENV.APP.host,
headers: computed('session.data.authenticated.token', function() {
let token = this.get('session.data.authenticated.access_token');
return { Authorization: `Bearer ${token}` };
}),
})
routes/s/todo-items/add.js
import Route from '#ember/routing/route';
export default Route.extend({
model(){
return this.store.createRecord('todo-item');
},
actions: {
submitAction() {
this.get('controller.model')
.save()
.then(() => {
this.transitionTo('s.todo-items');
});
}
},
});
The author adds Ember-Data-Route at about 15m5s for the add.js route as a mixin. This cleans up after the model.
He starts the explanation at that point, adds it in over the next minute or two in the video:
https://youtu.be/bZ1D_aYGJnU?t=15m5s
import Ember from 'ember';
import DataRoute from 'ember-data-route';
export default Ember.Route.extend(DataRoute, {
model() {
return this.store.createRecord('todo-item');
},
actions: {
save() {
this.get('controller.model')
.save()
.then(() => {
this.transitionTo('s.todo-items');
});
}
},
});

ember js params is exists

So I need to do so that clicking on the store would stop the page with the goods of this store. When passing the store's id through the link URL changes but in the product's route model(params) the params is empty
Model name (product?)
import DS from 'ember-data';
import { empty } from '#ember/object/computed';
export default DS.Model.extend({
name: DS.attr('string'),
quantity: DS.attr('string'),
price: DS.attr('string'),
shops: DS.belongsTo('shop', {asynq: true}),
isNotValid: empty('name'),
});
Model name (shop?)
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
address: DS.attr('string'),
phone: DS.attr('string'),
products: DS.hasMany('product',{asynq: true}),
});
Controller name (?)
import Controller from '#ember/controller';
export default Controller.extend({
isNew: false,
actions: {
newProduct() {
this.toggleProperty('isNew');
},
cancelNewProducts() {
this.set('isNew', false);
},
addNewProduct() {
const name = this.get('name');
const quantity = this.get('quantity');
const price = this.get('price');
let shop = this.get('store').peekRecord('shop', );
let product = this.get('store').createRecord('product', { name,quantity,price });
shop.get('products').pushObject(product);
product.save().then( function() {
shop.save();
});
product.save().then( ()=> this.set('isNew',false));
},
},
});
Route name (?)
import Route from '#ember/routing/route';
export default Route.extend({
model(params) {
console.log(params.shop_id);
return this.store.query('product', {shops:params.shop_id});
},
actions: {
deleteProduct(product) {
let confirmation = confirm('Are you sure?');
if (confirmation) {
product.destroyRecord();
}
},
editProducts(product) {
console.log(id)
product.set('isEditing', true);
},
cancelProductsEdit(product) {
product.set('isEditing', false);
product.rollbackAttributes();
},
saveProducts(product) {
if (product.get('isNotValid')) {
return;
}
product.set('isEditing', false);
product.save();
},
},
});
Router
import EmberRouter from '#ember/routing/router';
import config from './config/environment';
const Router = EmberRouter.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
this.route('shops', function() {
this.route('new');
this.route('edit', { path: '/:shop_id/edit' });
});
this.route('products', { path: '/:shop_id/products' }); // this should be in the map, right?
}); // ?
export default Router;
My problem is that I can not make a request to the server and get the goods only from the store with the available id.But when I click on any store displays all the goods. AND i can't access params(shop_id) from shops in my controller,
I just did a pretty invasive reformatting of your program. Take note of the router. Looks like there are some mistakes in there. Also, you don't provide an id to 'peek' with. In your controller. We also don't know how those files are connected or what their names are. You should edit on top of my formatting to help clarify. {async: true} is spelled with a q in your program.
As far as your question... I'll try and reword it. The word 'store' is confusing... because of the data 'store' that we are used to talking about. I think you mean that you have a 'shop' resource and that you want to 'click' on the shop... (likely a component in an each loop) and then you say you want to 'stop' the page with the goods(products) - so... maybe you mean 'stock' or 'show' the products for that shop. This could be in the component (if only a few) - or you could shoot over to a 'detail' page for the shop - that displayed all of the products for that shop. Because you mention params and ID, I think you mean to go the detail route.
This is my best guess at your question: "I have resources for 'shop' and 'product.' I'm building a UI where shops are listed. I would like to make the shops clickable and when clicked, transition to the shop detail page - where I can list all associated products. My link-to helper(not shown here) takes in a shop ID - but the transition is not successful and the params isn't recognized. What am I doing wrong?"
For this question, you could likely create a more simplified version in an ember-twiddle to get to the bottom of things. We don't really need most of those actions to get to the source of the confusion.
It's admitedly hard to show these things / when you have a server - or a mirage server or whatever your setup is. Here's an example of the routing I would suggest - with some basic dummy data - in an embertwiddle. The data isn't real ember objects / but see the link-to and the shop detail route for what you'd likely use. Good luck!
Other notes:
ember-data uses an attribute called isNew for records - so, you may want to think of a different name for what you're doing /

Define custom root json node on ember serializer

I'm trying to get brands items from my REST API with ember; but my API response does not match with ember-data expected. for example:
My model:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
isActive: DS.attr('boolean')
});
My API url: http://localhost:3000/api/brands
its response with:
{"success":true,
"data":[
{"id":1,"name":"Mine","isActive":true,"createdAt":"2017-04-23T20:36:49.000Z","updatedAt":"2017-04-23T20:44:32.000Z"},
{"id":2,"name":"forever","isActive":true,"createdAt":"2017-04-23T20:41:14.000Z","updatedAt":"2017-04-23T20:43:57.000Z"}
]
}
but, Ember is expecting some like this:
"brands": [{
"id": 1,
"name": "foo",
"isActive": "foo"
}]
I'm trying to change the root json node in serializer called brand.js, but I can not make it work. :(
here my serializer/brand.js
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
});
and my adapters/application.js
import DS from 'ember-data';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';
import config from '../config/environment';
export default DS.RESTAdapter.extend(DataAdapterMixin, {
host: `${config.host}`,
namespace: `${config.namespace}`,
authorizer: 'authorizer:custom'
});
on browser console this message appears:
WARNING: Encountered "success" in payload, but no model was found for model name "success" (resolved model name using vanely-web#serializer:brand:.modelNameFromPayloadKey("success"))
WARNING: Encountered "data" in payload, but no model was found for model name "datum" (resolved model name using vanely-web#serializer:brand:.modelNameFromPayloadKey("data"))
How can I say to ember where the correct data is?.Some help is appreciated.
Sorry if my English is not well.
As you already did, you can override the RESTSerializer for every model.
What you want to achieve is response normalization. You can normalize your response by overriding normalizeResponse in your serializer (see the docs):
import Ember from 'ember';
import DS from 'ember-data';
const {
RESTSerializer
} = DS;
const {
get
} = Ember;
export default RESTSerializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
// Only do normalization for reading:
if(requestType !== 'createRecord' && requestType !== 'updateRecord' && requestType !== 'deleteRecord') {
// Do your normalization here. For example (not tested):
payload = {
brands: get(payload, 'data')
};
}
return this._super(store, primaryModelClass, payload, id, requestType);
}
});
Instead of overriding normalizeResponse, you could also override the other normalization methods.

How do I access the :id parameter in the route file in ember.js?

I have these routes in my Ember app:
Router.map(function() {
this.resource('posts', function () {
this.route('show', {path: '/:id'});
});
});
Let's focus on the show route with url /posts/:id
It uses the PostsShowController which looks like this:
import Ember from "ember";
export default Ember.ObjectController.extend({});
And the route file /routes/posts/show.js which looks like this:
import Ember from 'ember';
var PostsIndexRoute = Ember.Route.extend({
model: function() {
return posts[variable];
}
});
var posts = ...
On the fifth line, where it says posts[variable] I want to replace variable with the :id parameter that gets passed in the url. So if I enter localhost:4200/posts/3 I want it to be 3. How do I do this?
Make sure you think through your routes. "/posts" implies that you're looking at all posts while "/post/:id" implies that you're looking at a post by a given id. So you likely want something like:
this.resource('posts');
this.resource('post', { path: 'post/:id' });
This would give you two routes:
/posts
/post/:id
You can then provide corresponding Ember Route objects as follows:
App.PostsRoute = Ember.Route.extend({
model: function () {
// return all posts
}
});
App.PostRoute = Ember.Route.extend({
model: function (params) {
// params.id will be the :id value from the route
// return post by id
}
});

Categories