ember data not saving foreign key, sent as null - ember.js

My ember app is not sending my foreign key to the back-end.
I have a table called issues which is has a related table called categories
My model is:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
category_id: DS.belongsTo('category'),
description: DS.attr('string')
});
My route is:
import Ember from 'ember';
export default Ember.Route.extend({
model: function(){
return this.store.findAll('issue');
},
actions: {
create: function(){
var issue = this.store.createRecord('issue');
issue.name = this.get('controller').get('newName');
issue.description = this.get('controller').get('newDescription');
issue.category_id = parseInt(this.get('controller').get('newCategory'));
//debugger;
console.log(issue);
issue.save();
},
...
other actions
...
}
}
});
the console.log from above looks like the category_id is getting set correctly:
category_id: 3
description: "foobar"
name: "test"
However my JSON payload that gets sent to the backend looks like:
{"issue":{"name":"test","description":"foobar","category_id":null}}
I tried stepping through by adding a custom serialiser in app/serializers/application.js
export default DS.RESTSerializer.extend({
...
serialize: function(snapshot,options){
console.debug('options='+options);
debugger;
var json = this._super(snapshot, options);;
return json;
}
...
});
But I got lost in all the super calling super indirection.
The snapshot.record has category_id: 3, but the json coming back from the this._super() call has category_id: null
options has includeID:true
Any clues will be much appreciated ...
Ember : 2.0.2
Ember Data : 2.0.0

Your model definition is wrong, when dealing with relationships you define them just as you would define any other attribute, there is no need to use _id.
export default DS.Model.extend({
name: DS.attr('string'),
category: DS.belongsTo('category'),
description: DS.attr('string')
});
As for the creation you should always use setters/getters when dealing with ember objects:
create: function() {
var issue = this.store.createRecord('issue', {
name: this.get('controller').get('newName'),
description: this.get('controller').get('newDescription'),
category: this.get('controller').get('newCategory') // assuming new category is a DS.Model instance of category
});
issue.save();
}
If you wish to stick to the syntax you have you would use issue.set('name', this.get('controller').get('newName')), from the looks of your code it seems you are going about this in the wrong way.
You should have a this.route('new') nested under your issues route, that way you wouldn't have to use the controller to store information.
You would simply set the model of the new route to:
model: function() {
return this.store.createRecord('issue');
}
Your template would make use of the input helpers like so:
{{input value=model.name}} and your action would just get the currentModel and call .save().

Related

Add column to existing ember model

Wow this is hard to find.
I have an existing model in ember and I would like to add a new column. I have't been able to see how to generate this from the CLI, so have manually added it to my component.js and models/post.js. I've added the field to my form and the handlebar to my view. Checking Firebase I can confirm I'm not updating the field.
In Rails I would simply run rails generate migration add_snippet_to_posts snippet:string but doing this in Ember just creates a new model.
model/post.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
author: DS.attr('string'),
createdDate: DS.attr('date'),
text: DS.attr('string'),
snippet: DS.attr('string') #<=manually added this.
});
component.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
createPost: function (model) {
this.sendAction('createPost', model);
// Clear each input field
this.set('newPost.title', null);
this.set('newPost.author', null);
this.set('newPost.text', null);
this.set('newPost.snippet', null); #<= manually added this
}
}
});
How do I do this?
Solved
Needed to update routes/index.js too:
import Ember from 'ember';
export default Ember.Route.extend({
model: function () {
return this.store.findAll('post');
},
actions: {
createPost: function (model) {
let post = this.store.createRecord('post', {
title: model.title,
text: model.text,
author: model.author,
snippet: model.snippet, # <= add this too
createdDate: new Date()
});
post.save();
}
}
});
The official answer would be that you cannot just add an attribute with ember-CLI to a model that has already been created - and at the same time, update everything it may effect throughout your app. You have to manually write the attributes and how they are used in routes/components/templates etc.
That sounds awesome that Rails can just know all that stuff. : )

Ember: model not being set by store in model hook route

So what I am doing is extremely basic: rendering model data to the template.
Upon setting the model hook, the {{model}} object doesn't show data in the corresponding template.
Here's my code:
contact (route):
user: Ember.inject.service('current-user'),
model: function()
{
// var that = this;
// console.log('whats being returned bitch: ', this.store.findRecord('contact', this.get('user').contactID));
//return this.store.findRecord('contact', this.get('user').contactID);
var records = this.store.findRecord('contact', this.get('user').contactID);
var promise = Ember.RSVP.defer();
// console.log('promise', promise.resolve());
// records.addObserver('isLoaded', function() {
// // console.log('records.getv', records);
promise.resolve(records);
//});
return promise;
},
setupController: function(controller)
{
// Get the parameters for the current route every time as they might change from one record to another
var params = this.paramsFor('dashboard.contact');
console.log('params', params);
// Set the data in the current instance of the object, this is required. Unless this is done the route will display the same data every time
this.module = Ember.String.capitalize(params.module);
this.id = params.id;
this.data = this.store.find(this.module,this.id);
// Set the data in the controller so that any data bound in the view can get re-rendered
controller.set('id',this.id);
controller.set('model',this.data);
controller.set('module',this.module);
}
});
First i was trying just this but it was not displaying data, then i tried deferring the promise and resolving it (like this) and finally i tried setting up the controller (setupController function) but that didn't work either since params is empty for some reason :/
contact(template):
<h1> Contact! </h1>
{{#each model as |contact|}}
<h3>{{contact.name}}</h3>
<h3>{{contact.password_c}}</h3>
{{/each}}
contact(model):
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
password_c: DS.attr('string'),
birthdate: DS.attr('string'),
assistant: DS.attr('string'),
account_name: DS.attr('string'),
email1: DS.attr('string'),
facebook: DS.attr('string'),
phone_home:DS.attr('string')
// address: Ember.computed('primary_address_street', 'primary_address_state',
// 'primary_address_city', 'primary_address_country', function() {
// return '${this.get('primary_address_street')} ${this.get('primary_address_state')} ${this.get('primary_address_city')} ${this.get('primary_address_country')}';
// })
});
Please help!
Let's assume this is your router
// app/router.js
import Ember from 'ember';
var Router = Ember.Router.extend({
});
Router.map(function() {
this.route('contacts', {path: '/contacts/:contact_id'});
});
export default Router;
and your model
// app/models/contact.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
password_c: DS.attr('string'),
});
then this is would be your contacts.js route it will have a very important role and We'll be using Ember Data's findRecord to retrieve an individual record from the data store.
// app/routes/contacts.js
import Ember from 'ember';
export default Ember.Route.extend({
model(param){
return this.store.findRecord('contact',param.contact_id);
}
});
note: this param is very important.The param is passed from the URL into the model. This posts model has an id that can be accessed via contact_id. It uses that id to look up the record so it can be returned. By default the template with the same name, contacts, will have access to this model.
Here we use Ember Data's findAll. This simply returns back all the records in the post data store.
// app/routes/application.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('contact');
}
});
now
// app/templates/application.hbs
{{#each model as |contact|}}
<h3>{{contact.name}}</h3>
<h3>{{contact.password_c}}</h3>
{{/each}}
As I don't have access to see your service and all your code I tried to simplify the way you can return all contact and get that and also how you can pass Param easily.
for more information : https://guides.emberjs.com/v2.7.0/tutorial/ember-data/
You can follow this codes and customize as you would like, I hope it will resolve your problem.
UPDATE:
If you have already your user data and it's ok, then remove {{#each}}
and let's have {{contact.name}}, that should work, you just need #each
while you have all contact like this.store.findAll('contact'); or if
you are in you must have this {{model.name}}, then model would be
contact !

What array name does Ember Data expect for sub directory models?

I recently started learning Ember and using Ember-CLI so I'm not quite well educated about Ember Data and what array names it expects for relationships that are in sub directories in my app.
// models/server.js
import DS from 'ember-data';
export default DS.Model.extend({
serverHistory: DS.hasMany("history/server", { async: true })
});
// models/history/server.js
import DS from 'ember-data';
export default DS.Model.extend({
server: DS.belongsTo("server", { async: true })
});
I've tried returning these names from my API
server_historys_ids
server_histories_ids
history_server_ids
history_servers_ids
But I don't see an XHR request for Server history in my application. The servers itself are fetched fine.
Update
I changed my relationship name and the API is returning history ids but I'm still not getting an history json request even though I'm trying to each in the template. The game relationship data is accessible in the template and a request is successfully made.
// models/server.js
import DS from 'ember-data';
export default DS.Model.extend({
// attr's here.. not relevant
// Relationships
game: DS.belongsTo("game", { async: true }), // works
serverHistories: DS.hasMany("history/server", { async: true }) // doesn't make a request like game does.
});
I also have an adapter/history/server.js but it's only telling what namespace to use - "api".
Update 2
I think the problem may be in the way I'm calling the data to the model.
// routes/server/view/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var parentModel = this.modelFor("server.view");
return this.store.query("server", { server_address: parentModel.server_address });
// return this.store.find("server", 1);
}
});
How come when I use find with an id it updates the template data and when I use query with parameters it doesn't?
Update 3
So I got my find and query problem sorted out, here's the way I got it to work: https://stackoverflow.com/a/31831667/1814027
The relationship problem still persists. I see no serverHistory data in my Ember toolbar nor a request being made to the API for it.
I beleive serverHistory is anti-conventional name for hasMany and serverHistories should be instead.
export default DS.Model.extend({
serverHistories: DS.hasMany("history/server", { async: true })
});
Then in case of ActiveModelAdapter expected server payload is:
{"server": {"id": 1, "server_history_ids": [1,2,3]}}
It doesn't depend on the fact that serverHistory is namespaced model, it depends on relation name only.
For example for model:
// models/server.js
import DS from 'ember-data';
export default DS.Model.extend({
bars: DS.hasMany("history/server", { async: true })
});
expected payload is:
{"server": {"id": 1, "bar_ids": [1,2,3]}}
Update
Working ember-cli example: https://github.com/artych/so_ember_data_subdir
Artych's answer helped me on the right path but Ember didn't want to recognise server_history_ids so I just renamed the hasMany relation to histories and returned histories: [] from my API. Now it works.. don't know why but it works.

How should I filter items of a user?

I am using Ember 1.13.2 and Ember Data 1.13.4. The API conforms to JSON API format (http://jsonapi.org/format).
A user has many items. Doing {{model.items}} in the template will return ALL items of the user.
What if I also need to display ONLY blue items from the user. How should I go about this?
// Route
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
// Executes: http://localhost:3099/api/v1/users/5
return this.store.findRecord('user', params.user_id);
}
})
// Template
firstName: {{model.firstName}} - works
<br>items: {{model.items}} - works
<br>blue items: {{model.items}} - what do we do about this?
// app/models/user.js
import DS from 'ember-data';
export default DS.Model.extend({
items: DS.hasMany('item', { async: true }),
firstName: DS.attr('string')
});
// app/models/item.js
import DS from 'ember-data';
export default DS.Model.extend({
user: DS.belongsTo('user', { async: true }),
name: DS.attr('string')
});
I misunderstood the original question. It seems as if you want to fetch only the items where the color is blue (and avoid fetching the rest). For this, you'll need to query the server, which requires server-side code. But, once you have the server-side code done, you can do something like this:
blueItems: Ember.computed('items.#each.color', {
get() {
const query = {
user: this.get('id'),
color: 'blue'
};
return this.get('store').find('item', query);
}
})
But again, you'll need your server to support querying for that data. (The JSON API states how you need to return the data, but you'll need to implement the query yourself.)
Old answer that filters the items after fetching for display (just for reference):
I would use a computed property:
blueItems: Ember.computed('items.#each.color', {
get() {
return this.get('items').filter((item) => {
return item.get('color') === 'blue';
});
}
})
Or the shorthand ;)
blueItems: Ember.computed.filterBy('items', 'color', 'blue')
Not every operation has an Ember shorthand which is why I gave the full example first.
Using computed properties with promises is sometimes tricky, but this computed property should update whenever your items array updates.

How to overwrite ember-data reader method? or convert DS.attr to computed property? similar to backbone parse

I have an endpoint that returns id, name, properties, and properties is a damn string, with comma separated attribute key/value pairs. Yes you read that correctly.
properties = "id:1,name:foobar,phone:123456789,taco:true"
Current Model
import DS from 'ember-data';
export default DS.Model.extend({
contact: DS.belongsTo("contact"),
name: DS.attr(),
phone: DS.attr(),
properties: DS.attr(),
eachProperties: function () {
return this.get('properties').split(",").map(function(propertyKeyValuePair) {
return propertyKeyValuePair.split(":");
});
}.property('properties')
});
properties requires some computation to be helpful on the front-end.
Can you re-use the property named properties instead of creating a new one called eachProperties?
Something like:
export default DS.Model.extend({
// ... other stuff
properties: DS.attr(function(model, obj){
return obj.split(",").map(function(propertyKeyValuePair) {
return propertyKeyValuePair.split(":");
});
});
});
OR how does one change the name of an api property name to something else like rawProperties and then have the computed property called properties?
I know you're thinking "Just fix your damn API" but you know that isn't always possible :)
After some research, I found that i basically wanted to utilize Transforms (docs) to handle the deserializing. Its super easy and rad.
As per Ember-CLI module-directory-naming-structure, you add transforms inside the app/transforms/ folder.
I created a file called properties.js. (Whatever you name the file will be the name of attribute/transform.)
//*inside app/transforms/properties.js*
import DS from 'ember-data';
export default DS.Transform.extend({
deserialize: function (serialized) {
return serialized.split(",").map(function (keyValuePair) {
return keyValuePair.split(":");
});
},
serialize: function (deserialized) {
return deserialized.map(function (keyValuePair) {
return keyValuePair.join(":");
}).join(",");
}
});
This transform will be used when you receive json, and also when you send the JSON back out! super cool.
Updated Model:
import DS from 'ember-data';
export default DS.Model.extend({
contact: DS.belongsTo("contact"),
name: DS.attr('string'),
phone: DS.attr(),
properties: DS.attr('properties')
});
the DS.attr('properties') is where the magic happens!