Embedded data from RestApi - ember.js

My data comes from REST API like this:
customers:[
id:3,
name:"Joue",
currency:{
id:5
iso_code:"BDT"
}
]
My model:
App.Customer = DS.Model.extend({
name: DS.attr('string'),
currency: DS.attr('string')
});
i populated a select box with the availabe currencies and now i want to select by "id" 5.
Since currency is embedded and its interpreted as string i cant access it.
As far as i know embedded records are no longer supported in ember-data 1.0.
do i have to rewrite my REST Api and get rid of the relationships or there is a workaround .

You can just create a custom serializer for the data.
Using your data (slightly modified, since the json isn't valid, and I'm guessing that's just cause it was hand written?)
{
customers:[
{
id:3,
name:"Joue",
currency:{
id:5,
iso_code:"BDT"
}
}
]
}
Here's a serializer for that particular response type (read more about it here https://github.com/emberjs/data/blob/master/TRANSITION.md)
App.CustomerSerializer = DS.RESTSerializer.extend({
extractArray: function(store, type, payload, id, requestType) {
var customers = payload.customers,
currencies = [];
customers.forEach(function(cust) {
var currency = cust.currency;
delete cust.currency;
if(currency){
currencies.push(currency);
cust.currency = currency.id;
}
});
payload = { customers:customers, currencies: currencies };
return this._super(store, type, payload, id, requestType);
}
});
And your models defined with a relationship
App.Customer = DS.Model.extend({
name: DS.attr('string'),
currency: DS.belongsTo('currency')
});
App.Currency = DS.Model.extend({
iso_code: DS.attr('string')
});
Example:
http://emberjs.jsbin.com/OxIDiVU/535/edit

currency is not "embedded", it's just an object. Don't declare it as a string in your model:
currency: DS.attr()
You say you want to "select" by id--what do you actually want to do? You can access the properties of currency directly:
{{! handlebars }}
Your currency id is {{currency.id}}.
// Javascript
id = model.get('currency.id');
No need for additional complexity involving serializers or additional models. However, you need to be careful when changing currency id, since
model.set('currency.id', 6)
will not dirty the model and it won't save. You'll need to also incant
model.notifyPropertyChange('currency')

Related

Decorating data from related model in Ember.js

I have a Property model and a Pricing Summary model, which relate to each other and are shown below:
App.Property = DS.Model.extend({
totalRoomCount: DS.attr(),
name: DS.attr(),
address: DS.attr(),
city: DS.attr(),
state: DS.attr(),
zip: DS.attr(),
pricingSummaries: DS.hasMany('pricingSummary', {async: true})
});
App.PricingSummary = DS.Model.extend({
startDate: DS.attr(),
endDate: DS.attr(),
days: DS.hasMany('day', {async: true}),
property: DS.belongsTo('property', {async: true})
});
Inside of my Property route I set the model to a Property, and then in the template, I want to output a list of the PricingSummary's that are related to that Property, as follows:
{{#each pricingSummary in pricingSummaries}}
{{render 'summaryRow' pricingSummary}}
{{/each}}
This works, and I'm able to output the attributes of each particular PricingSummary inside of the summaryRow template, like its startDate and endDate, for example. But what I REALLY want to do here is modify/format the startDate and output this formatted version. Basically I think I want a controller at this point, but I don't know how to tie a controller to the specific Pricing Summary model being output.
How do I do this? And furthermore, you can see that a PricingSummary also has a relationship to my Day model, so I'm going to want to do this again, another level deep.
Please help!
There are several ways to accomplish this, and all of them are relatively simple.
In relation to actually decorating a model, the easiest method would be to create a computed property on the model itself. Some people don't like this because they believe the models should be skinny and decorators should be in controllers/components, but it's all up to your preference. You could accomplish it this way:
App.YourModel = DS.Model.extend({
date: attr('date'),
formattedDate: function() {
var date = this.get('date');
return date ? this.get('date').toDateString() : null ; // Use your own format :-)
}.property('date')
});
Alternatively, I like to use a getter/setter pattern so you can use two-way bindings and it will marshal the value to a date on set, or to a string on get. In the following example, I'm using moment.js to parse/format:
App.YourModel = DS.Model.extend({
date: attr('date'),
dateMarshal: function(key, value) {
if (arguments.length > 1) {
var parsed = moment(value);
this.set('date', parsed.isValid() ? parsed.toDate() : null);
}
return this.get('date') ? moment(this.get('date')).format('MM/DD/YYYY') : null ;
}.property('date'),
});
Another option would be to provide an itemController property to the {{#each}} helper, but that's effectively the same as using render without having to use a custom view.
If you're using more properties and perhaps some actions on the pricing summary row (to delete it, for instance), my preference would be to use a component:
{{#each pricingSummary in pricingSummaries}}
{{pricing-summary-item content=pricingSummary}}
{{/each}}
And your component:
App.PricingSummaryItem = Ember.Component.extend({
content: null,
dateFormatted: function() {
var formattedDate = this.get('content.date');
// Format your date
return formattedDate;
}.property('content.date')
actions: {
'delete': function() {
this.get('content').deleteRecord();
},
markRead: function() {
this.set('content.isRead', true);
this.get('content').save();
}
}
});
Finally, to address JUST the date issue and not decoration, I would make a bound helper. Again, this example uses moment.js (and I'm using ember-cli as well, so pardon the ES6 syntax):
import Ember from 'ember';
export function formatDate(input, options) {
input = moment(input);
if (options.hashContexts.fromNow) {
return input.fromNow();
} else {
return input.format(options.hash.format || 'LL');
}
}
export default Ember.Handlebars.makeBoundHelper(formatDate);
Then you just use {{format-date yourDateProperty}} in your template.

Loading Ember Data hasMany relationships using links

I'm having some trouble getting hasMany relationships to auto load (default or async) - I'm using the "links" attribute so i can use a custom url for children, and using a custom serializer to put the links attribute in since the server doesn't provide it - is this not supported? (using ember data 1 beta 6 and ember 1.3.2)
App.Bag = DS.Model.extend({
elements: DS.hasMany('element')
});
App.Element = DS.Model.extend({
name: DS.attr('string')
});
App.BagSerializer = DS.RESTSerializer.extend({
extractSingle: function(store, type, payload, id, requestType) {
payload.links = {"elements": "/bags/" + id + "/elements"};
return this._super(store, type, payload, id, requestType);
}
});
I'm able to load a Bag fine, but the elements array is never populated, I never see a call to the /bags/id/elements url. Am I doing something wrong?
Thanks!
How about if you specify the elements relationship is asyc? Like this:
App.Bag = DS.Model.extend({
elements: DS.hasMany('element', {async: true})
});

Having trouble setting Ember objects to both contain references to each other

Answered
I've solved the problem, it has to do with my adapter, and I'll post more in an answer,
but here's a link to a problem I'm having with the fix Ember get not getting certain attribute
Original Question
I have the following code:
customerSignUp: function () {
var customer = this.get('store').createRecord('customer', {
description: 'Why hello sir'
});
var model = this.get('model');
model.set('customer', customer);
customer.save().then(function() {
model.save().then(function() {
customer.set('user', model);
customer.save();
});
});
}
With the following backing models:
App.User = App.Person.extend({
name: DS.attr('string'), // Actually their primary email.
customer: DS.belongsTo('customer', {async: true })
});
App.Customer = DS.Model.extend({
user: DS.belongsTo('user', {async: true}),
description: DS.attr('string')
});
(App.Person just passes along some naming conventions)
The customerSignUp function is trying to get both objects to refer to the other, so that either one could get attributes from the other as needed (the user model is planned to have more relationships like this in the future, so that one user can have multiple "roles" on the site).
The problem is that I can't get both to stably refer to each other. With this current implementation the model (user) points to the customer just fine, but the customer for some reason simply has it's user field set to <computed> in the Ember Debugger, and the record saved in the database doesn't even have a user field. It seems to me like some of my saves are overwriting the values or changing the underlying objects so they're no longer true? Honestly I'm just confused.
I've tried all kinds of different orderings of the saves and sets, but so far only one at a time works, or if both work, it's because I haven't saved one of them to the database. Any advice? Is this even necessary? Will the customer object have access to it's user even if there isn't an id explicitly stored in the customer?
Could this have to do with the adapter I'm using?
Update
With this implementation of customerSignUp:
customerSignUp: function () {
var model = this.get('model');
var customer = this.get('store').createRecord('customer', {
description: 'Why hello sir',
user: model
});
customer.save().then(function() {
model.set('customer', customer);
model.save();
});
}
The customer relationship on the user is set for a moment, but then resets to null. The user relationship is just <computed> and I don't think was ever set.
It had to do with my adapter.
This function:
serializeBelongsTo: function(record, json, relationship) {
console.log("serializeBelongsTo");
var attribute, belongsTo, key;
attribute = relationship.options.attribute || "id";
console.log(attribute);
key = relationship.key;
console.log(key);
belongsTo = Ember.get(record, key);
console.log(belongsTo);
if (Ember.isNone(belongsTo)) {
return;
}
var content = belongsTo.content;
console.log(content);
var temp = Ember.get(belongsTo, attribute);
console.log(temp);
json[key] = temp;
console.log(json);
if (relationship.options.polymorphic) {
return json[key + "_type"] = belongsTo.constructor.typeKey;
}
else {
return json;
}
returns a value of undefined from Ember.get(belongsTo, attribute) even though belongsTo and attribute are correctly set, although belongsTo has it's data buried in a content object, which this SO post details.

Accessing controller properties within Ember Data REST Adapter

I need to access a controller property to build custom URLs in my RESTAdapter instances, but I can't find a way to access the controller within the context of an adapter. Here's what I have:
I have a simple model that looks like this:
App.Customer = DS.Model.extend(
{
first_name: DS.attr('string'),
last_name: DS.attr('string'),
date_of_birth: DS.attr('string'),
created_at: DS.attr('string'),
updated_at: DS.attr('string')
});
The resource REST URL for this model looks something like this:
https://api.server.com/v1/accounts/:account_id/customers/:customer_id
I'm extending the RESTAdapter in Ember Data for most of my models so I can customize resource URLs individually. Like this:
App.CustomerAdapter = DS.RESTAdapter.extend(
{
buildURL: function(type, id)
{
// I need access to an account_id here:
return "new_url";
}
});
As you can see, in this example I need an account ID in the URL to be able to query a customer object. The account ID is something that the user would have had to supply by logging-in and is stored in an AccountController which is an instance of Ember.Controller.
My question is, how can I access a property from my AccountController within my CustomerAdapter? Here are the things I've tried, none have worked:
App.CustomerAdapter = DS.RESTAdapter.extend(
{
buildURL: function(type, id)
{
var account_id = this.controllerFor('account').get('activeAccount').get('id');
return "new_url";
}
});
,
App.CustomerAdapter = DS.RESTAdapter.extend(
{
needs: ['account'],
accountController: Ember.computed.alias("controllers.account"),
buildURL: function(type, id)
{
var account_id = this.get('accountController').get('activeAccount').get('id');
return "new_url";
}
});
,
App.CustomerAdapter = DS.RESTAdapter.extend(
{
activeAccountBinding = Ember.Binding.oneWay('App.AccountController.activeAccount');
buildURL: function(type, id)
{
var account_id = this.get('activeAccount').get('id');
return "new_url";
}
});
At this point, the only hack I can think of, is to put the account ID in a global variable outside of Ember and access it from there within the Adapter.
Other suggestions?
We have a similar problem, and essentially we do a global variable, and feel guilty about it. Ours is in Ember Model, but the same concept and problem exists. Another solution is to use findQuery, but this returns a collection, so then you have to pull the item out of the collection.
App.CustomerAdapter = DS.RESTAdapter.extend(
{
buildURL: function(type, id)
{
var params = type.params;
return "new_url" + params.account_id;
}
});
In Some Route:
App.BlahRoute = Em.Route.extend({
model: function(params){
App.Customer.params = {account_id:123};
this.get('store').find('customer', 3);
}
});
I know that you can access controller's properties in context of another controller.
I see that you tried somewhat similar, but anyway maybe this would work for adapter:
App.YourController = Ember.ObjectController.extend({
needs: ['theOtherController'],
someFunction: function () {
var con = this.get('controllers.theOtherController');
return con.get('propertyYouNeed');
},
});
Also, have you thought about adding AccountId property to your Customer model?
Maybe automatic URL can be achieved with proper routing?

Ember Data 1.0.0: what is expected format for belongsTo relationship

I have the following models:
App.Publication = DS.Model.extend({
title: DS.attr('string'),
bodytext: DS.attr('string'),
author: DS.belongsTo('author')
});
App.Author = DS.Model.extend({
name: DS.attr('string')
});
And the folowing json data:
{
"publications": [
{
id: '1',
title: 'first title',
bodytext: 'first body',
author_id: 100
},
{
id: '2',
title: 'second title',
bodytext: 'second post',
author_id: 200
}
];
}
In Ember Data RC12 this worked (you could specify author_id OR author in the json and the publication would always have the correct author linked).
In Ember Data 1.0.0 this no longer works; author is always null.
In some documents I found that - since I am using "author_id" in the json data (and not simply author) - I need to specify the key in the model; thus:
author: DS.belongsTo('author', { key: 'author_id' })
This however does not work; the author in the publication remains null.
The only solution I see for now is to implement a custom serializer and override the author_id to author (via normailzeId); I cannot change my backend data structure ... thus:
App.MySerializer = DS.RESTSerializer.extend({
//Custom serializer used for all models
normalizeId: function (hash) {
hash.author = hash.author_id;
delete hash.author_id;
return hash;
}
});
Is the above the correct way ?
Ember Data 1.0 no longer does any payload normalization by default. The key configuration for DS.belongsTo has been removed as well so you will have to implement a custom serializer.
normalizeId is an internal serializer function used for converting primary keys to always be available at id. You shouldn't override this.
Instead, you can override the keyForRelationship method which is provided for this purpose.
You could use something like the following:
App.ApplicationSerializer = DS.RESTSerializer.extend({
keyForRelationship: function(rel, kind) {
if (kind === 'belongsTo') {
var underscored = rel.underscore();
return underscored + "_id";
} else {
var singular = rel.singularize();
var underscored = singular.underscore();
return underscored + "_ids";
}
}
});
Note: I've also renamed the serializer to App.ApplicationSerializer so that it will be used as the default serializer for your application.
Finally, if you haven't already found it, please take a look at the transition notes here: https://github.com/emberjs/data/blob/master/TRANSITION.md
If you read through the transition document shortly after the initial 1.0.0.beta.1 release I would recommend taking a look again as there have been a number of additions, particularly regarding serialization.
From the Ember 1.0.0 Transition Guide:
Underscored Keys, _id and _ids
In 0.13, the REST Adapter automatically camelized incoming keys for you. It also expected belongsTo relationships to be listed under name_id and hasMany relationships to be listed under name_ids.
If your application returns json with underscored attributes and _id or _ids for relation, you can extend ActiveModelSerializer and all will work out of the box.
App.ApplicationSerializer = DS.ActiveModelSerializer.extend({});
Note: DS.ActiveModelSerializer is not to be confused with the ActiveModelSerializer gem that is part of Rails API project. A conventional Rails API project with produce underscored output and the DS.ActiveModelSerializer will perform the expected normalization behavior such as camelizing property keys in your JSON.