Ember Data and object graphs - ember.js

I'm a completely new to emberjs but excited to get in and play around. One thing I'm struggling with is ... can you load a JSON object that has an object graph depth of more than 1. What I mean is is it seems relatively straight forward to set a Model to a series of name/value pairs where the value is a simple type (string, integer, date, etc.) but what if it's an object?
I imagine it might be connected to the relationship model but from what I see in the documentation this only allows for a FK->PK relationship and not an inline object. I'm probably doing a terrible job of explaining what I mean so let me give you an example:
Let's assume that I have a REST call at the endpoint of: http://my.url.com/place/[place_id] and the GET call returns:
{
place: {
name: "string",
desc: "string",
location: {
longitude: float,
latitude: float,
radius: int
}
}
In the above example I am struggling to understand how to model location. Any help on how to extend this would be greatly appreciated:
App.Place = DS.Model.extend({
name: "string",
desc: "string",
location: "?????"
});

You can introduce a new data transform to handle raw JSON.
DS.RESTAdapter.registerTransform('raw', {
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
Now you cab define your model with raw as the data type for location.
App.Place = DS.Model.extend({
name: DS.attr('string'),
desc: DS.attr('string'),
location: DS.attr('raw')
});
If your data from the server has the form
place: {
id: 'place',
name: 'foo',
desc: 'bar',
location: {
longitude: 1,
latitude: 2,
radius: 3
}
}
then you can bind to location.longitude, etc. in your template. Here is is fleshed out in a jsfiddle.

Related

Inject Relationships into Ember Payload

I have a component that shows and creates comments for a post, this component has a form for creating the new comments and sends them via POST to the backend, the normal payload would be:
{
data: {
attributes: {
created_at: "foo",
autor: "foo",
text: "foo"
},
relationships: {
post: {
data: { type: "posts", id: 1234 },
id: "1234",
type: "loans"
}
},
type: "comment"
}
}
The problem comes when you need to use the component in another view and more importantly when the name of the model is different, to say posts_breakdown, in this case, the payload would be:
{ data: {
attributes: {
created_at: "foo",
autor: "foo",
text: "foo"
},
relationships: {
post: {
data: null
}
},
type: "comment"
}
}
Clearly, in comments, there is no relation posts_breakdown, the first thing that I tried to add this relation to the model with posts_breakdown: belongsTo (posts_breakdown).
The problem is, that the backend can't recognize it and is not possible to modify it.
The backend is taking the values on the relationships to relate the comment with the post (post_id field into comment table)
My question: Is there some way to "trick" the backend and/or modify the payload, so think that the post_breakdown model is posted?
Below is a representation of how I have the defined models:
comment.js:
    export default DS.Model.extend ({
        author: DS.attr (),
        text: DS.attr (),
        created_at: DS.attr (),
        post: DS.belongsTo ('post'),
        posts_breakdown: DS.belongsTo ('posts_breakdown'),
    });
posts.js:
    export default DS.Model.extend ({
        text: DS.attr (),
        created_at: DS.attr (),
        author: DS.attr (),
        comments: DS.hasMany ('comments'),
    });
post_breakdown.js
    export default DS.Model.extend ({
        most_commented_post: DS.attr (),
        last_commented_post: DS.attr (),
        frequent_users: DS.attr (),
        comments: DS.hasMany ('comments'),
    });
Ok, i already figured out the way to modify the payload send it to the backend.
Ember have Serializers!
Following this guide, i can modify the data into the payload, erase it, add it or whatever i need:
https://guides.emberjs.com/release/models/customizing-serializers/
I my case, firs i need to add the relationship into the comment's model, in this line:
`posts_breakdown: DS.belongsTo ('posts_breakdown')`
then generate a serializer for comment's model with ember-cli:
`ember generate serializer comment`
finally, into the serializer if the payload contains data into the post_breakdown relationship, delete it and pass it to post relationship, in this way, the payload was the same:
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
/*
This two functions, are necesary because Ember Data changes the underscore
between variable names by dashes. In fact, it's a Ember suggestion.
*/
keyForAttribute: function (key) {
return key;
},
keyForRelationship: function (key) {
return key;
},
serialize(snapshot, options) {
let json = this._super(...arguments);
/* This makes possible to store comments when the comments-panel-loan component is used
in loan_breakdown view with post_breakdown model:
*/
if (json.data.relationships.post_breakdown.data) {
json.data.relationships.loan = {
data: {
type: "posts",
id: json.data.relationships.post_breakdown.data.id }
};
delete json.data.relationships.post_breakdown;
}
return json;
},
});

How Do I Construct my Ember Models and API Data to represent data on the relationship?

I'm new to jsonapi and how the structure works and trying to get a relationship to load properly. I'm expecting ember-data to follow provided url's in the relationship.links of my objects to fetch the required information but I'm getting unexpected results.
I have Users, Territories, and a User/Territory relationship defined like this:
// User Model
const UserModel = DS.Model.extend({
username: DS.attr('string'),
territories: DS.hasMany('user-territories')
}
// Territory Model
const TerritoryModel = DS.Model.extend({
name: DS.attr('string')
}
// User-Territory Model
const UserTerritoryModel = DS.Model.extend({
notes: DS.attr('string'),
startDate: DS.attr('date'),
user: DS.belongsTo('user'),
territory: DS.belongsTo('territory')
}
I then have mock data (using http-mock) that looks like this:
// api/users/
data: {
type: "users",
id: 1,
attributes: {
username: "thisIsMyUsername"
}
},
relationships: {
territories: {
links: {
self: "http://localhost:4200/api/users/1/territories"
}
}
}
// api/users/1/territories
data: {
type: "user-territories",
id: 1,
attributes: {
notes: "This is a note",
startDate: "2017-01-01"
}
},
relationships: {
user: {
link: {
self: "http://localhost:4200/api/users/1"
}
},
territory: {
link: {
self: "http://localhost:4200/api/territories/1"
}
}
}
// api/territories/1
data: {
type: "territories",
id: 1,
attributes: {
name: "Territory #1"
}
}
In my User route, I want to request the UserModel and have access to the UserTerritory Relationship data and the Territory itself. The api calls are not what I expect though:
this.get('store').findRecord('user', 1, { include: "territories" });
EXPECTED:
api/users/1
api/users/1/territories
ACTUAL:
api/users/1
api/users/1?include=territories
If I call the user-territories model I get this:
EXPECTED:
api/users/1/territories
ACTUAL:
api/user-territories/1
If you use included, ember-data basically thinks you want to tell the server to side-load data. If you return a links, just resolve the relationship. However the relationships have to be inside the data. Also the self link is for the relationship itself, to return the data use related.
So first you do something like user = store.findRecord('user', '1'), this will fetch to api/users/. Then you should return something like this:
// api/users/
{
data: {
type: "users",
id: 1,
attributes: {
username: "thisIsMyUsername"
}
relationships: {
territories: {
links: {
related: "http://localhost:4200/api/users/1/territories"
}
}
}
}
}
Next you do user.get('territories'). This will return a promise, and fetch http://localhost:4200/api/users/1/territories, or whatever was inside that related link. However know, what ember-data will expect you to return user-territories here, because thats what you specified with territories: DS.hasMany('user-territories'). You should know what you directly can model an many-to-many relationship in ember-data without a third table.

EmbeddedRecordsMixin not working as expected, what am I missing?

I'm trying to use embedded records in ember data and I think I'm missing something fundamental.
I have two models
app/models/video.js:
export default DS.Model.extend({
title: DS.attr('string'),
transcriptions: DS.hasMany('transcription', { embedded: 'always' })
});
app/models/transcription.js:
export default DS.Model.extend({
video: DS.belongsTo('video')
});
I also have a custom serializer app/serializers/video.js:
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs:{
transcriptions: { embedded: 'always' }
},
extractSingle: function (store, type, payload, id) {
var data = payload.data;
return {
id: data._id,
title: data.Title,
transcriptions: [{ id: "1" }]
}
}
});
I would expect that this would result in my video model being populated with transcriptions being an array of transcription object but instead I get the following error:
"Error while processing route: videos.show" "Assertion Failed: Ember
Data expected a number or string to represent the record(s) in the
transcriptions relationship instead it found an object. If this is a
polymorphic relationship please specify a type key. If this is an
embedded relationship please include the DS.EmbeddedRecordsMixin and
specify the transcriptions property in your serializer's attrs
object."
Any suggestions of what I'm doing wrong here would be greatly appreciated.
UPDATE: The solution was to modify my custom serializer to the following:
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs:{
transcriptions: { embedded: 'always' }
},
extractSingle: function (store, type, payload, id) {
var data = payload.data;
var videoPayload = {
id: data._id,
title: data.Title,
transcriptions: [{ id: "1" }]
};
return this._super(store, type, videoPayload, id);
}
}
The problem is the fact you're reimplementing extractSingle yourself.
You should call this.super if you're doing this..
In extractSingle on the REST Serializer it calls the normalize function - this normalise function is where the EmbeddedRecordsMixin does all it's work.
Because you're not calling either this.super or manually calling this.normalize you miss out on what the mixin is doing.

How do I set a dynamic model attribute in the fixture?

As the title describes, I am running into trouble making a dynamic attribute on the Fixture layer.
Here is an example model:
App.Pokeball = DS.Model.extend({
name: DS.attr('string'),
ballRate: DS.attr('number'),
battleAttributes: DS.belongsTo('battleAttributes')
});
And my Fixture:
App.Pokeball.reopenClass({
FIXTURES : [
{
id: 1,
name: 'PokeBall',
ballRate: 1
},
{
id: 23,
name: 'Dusk Ball',
ballRate: function() {
// Some logic that applies only model #23
return 2;
}.property('battleAttributes')
}
]
});
I scoured online trying to find out the right way to do this, but have instead ran into a dead end. :(
This is an invalid use of fixtures. They’re meant to represent JSON (or whatever) on the server that is passed to your application and turned into Ember Data models. JSON cannot represent the concept of a computed property, it’s for pure data.
I don’t understand your use case so I could be way off, it seems like you should use a computed property on the model instead:
App.Pokeball = DS.Model.extend({
name: DS.attr('string'),
ballRate: DS.attr('number'),
battleAttributes: DS.belongsTo('battleAttributes'),
adjustedBallRate: function() {
if (this.get('battleAttributes.whateverPropertyCausesThisToChange') == 'special value') {
return 2;
}
else {
return this.get('ballRate');
}
}.property('battleAttributes.whateverPropertyCausesThisToChange')
});

List all attributes for a specific DS.Model in Ember.js

How can I list all the attributes defined in a model?
For example, if we have a variant for some imaginary blog application:
App.Post = DS.Model.extend({
title: DS.attr('string'),
text: DS.attr('string'),
comments: DS.hasMany('App.Comment')
});
Then, I am looking for a possibility to iterate over the attributes without having an instance of the App.Post model:
# imaginary function
listAttributes(App.Post)
Such a function could yield an array providing name and type of the model attributes:
[{
attribute: "title",
type: "string"
},
{
attribute: "text",
type: "string"
}]
How to achieve that with Ember?
As of Nov 2016 (Ember v2.9.0), the best way to approach this is to use the eachAttribute iterator.
API Reference = http://emberjs.com/api/data/classes/DS.Model.html#method_eachAttribute
modelObj.eachAttribute((name, meta) => {
console.log('key =' + name);
console.log('value =' + modelObj.get(name));
})
Try this:
var attributes = Ember.get(App.Post, 'attributes');
// For an array of attribute objects:
var attrs = attributes.keys.toArray().map(function(key) {return attributes.get(key);} );
// To print the each attributes name and type:
attrs.forEach(function(attr) {console.log(attr.name, attr.type)});
Update for current Ember users
Currently the Ember.Map keys and values are private*, so #Mike Grassotti's answer is no longer applicable.
The listAttributes function should look something like this if you don't want to use private objects:
listAttributes(model) {
const attributes = Ember.get(App.Post, 'attributes'),
tempArr = [];
Ember.get(model.constructor, 'attributes').forEach( (meta, key) =>
temp.push({attribute: key, type: meta.type})
);
return tempArr;
}
* See commit Make Ember.Map keys and values private.