I'm developing an Ember-js application where I'd like to link each 'article' to the next and previous article. I want to work according to the JSON API.
I've managed to load/show the article's (blog) author in a decent and to load and show a 'teaser' of the next (related) article. That works fine.
Now, the next piece should be fairly simple: how can I load the 'links' defined in my article? How can I add them to my model or show them in any other way in my Handlebars-template?
Let me give an exerpt from my JSON-repsonse (the payload):
{
"data": {
"type": "blogs",
"id": "5",
"links": {
"previous": "blogs/4",
"self": "blogs/5",
"next": "blogs/6"
},
"attributes": {…}, // title, subtitle, bodytext, etc.
"relationships": {
…
},
The most important issue is how to get this 'self' link from the JSON reply. Can this data be used straight from the 'model'?
My model looks as follows:
//models/blog.js
import DS from 'ember-data';
export default DS.Model.extend({
nid : DS.attr('string'),
title : DS.attr('string'),
subtitle : DS.attr('string'),
intro : DS.attr('string'),
…
author : DS.belongsTo('person'),
next : DS.attr('next'),
previous : DS.belongsTo('person')
});
UPDATE
As #bungee points out, there's a an error (against the JSONAPI specs) in the payload. The 'links' array should be sibling to 'data', not descendent. However, in Ember 2.8, that still won't make Ember 'eat' it. :)
I assume you're using a data adapter? If so, have you created a model that corresponds to the data structure you displayed. To me it looks like that's not a standard ember rest data structure, but it depends on the adapter you're using. Also it looks like you could consider setting previous, self and next as direct parameters to data..?
Based on help from the Ember community (Slackware), I found out that at the moment this is not possible:
https://github.com/mharris717/ember-cli-pagination/issues/144
https://emberigniter.com/pagination-in-ember-with-json-api-backend/
To answer Bungee's comment:the JSON-API (http://jsonapi.org/) specifies that these 'previous','self' and 'next' links should be under the root of the payload but we can‘t
We can indeed solve the problem by moving them under 'data' → 'attributes' in the payload if can live with idea of not adhering to the JSON-API specs.
By the way: quote from the JSON-API's:
Pagination links MUST appear in the links object that corresponds to a collection. To paginate the primary data, supply pagination links in the top-level links object.
[…]
The following keys MUST be used for pagination links:
first: the first page of data
last: the last page of data
prev: the previous page of data
next: the next page of data
Related
I'm still learning ember.js and have run into a roadblock with ember data not resolving lookup relationships in models. I have one model 'site' that will be basically a lookup table for every other model to differentiate data based on location.
At this point, I'm doing something wrong or missing a key concept - probably both... (or maybe it's the wee hour!)
Site Model (i.e. the lookup table)
import DS from 'ember-data';
export default DS.Model.extend({
code: DS.attr(),
name: DS.attr(),
});
The site model would have a hasMany relationship to all my other models (will be about 12 when complete)
Associate Model
import DS from 'ember-data';
import { belongsTo } from 'ember-data/relationships';
export default DS.Model.extend({
site: belongsTo('site'),
last: DS.attr(),
first: DS.attr(),
active: DS.attr('boolean'),
fullName: Ember.computed('first', 'last', function() {
return `${this.get('first')} ${this.get('last')}`;
}),
});
The 'associate model' will also be a lookup along with 'site' in some other models.
I'm providing data via the JSON API spec but I'm not including the relationship data because as I understand it, ember data it should be pulling down the site data using the site id attribute.
{
"links": {
"self": "/maint/associates"
},
"data": [
{
"type": "associate",
"id": "1",
"attributes": {
"site": "6",
"last": "Yoder",
"first": "Steven",
"active": "1"
},
"links": {
"self": "/associates/1"
}
}
]
}
In my template file I'm referencing associate.site which gives me an error.
<(unknown mixin):ember431>
If I use associate.code or .name to match the site model, nothing will show in the template. The code from the 'site' table is the data I really want to displayed in the template.
So the obvious questions:
Am I wrong that Ember Data should be resolving this or do I need to
include the relationship in my API response?
I realize that my belongsTo in the 'associate' model only references
site while I want site.code, so how do I make that relationship
known or access the field in my 'associate' model?
I didn't include hasMany relationship in the 'site' model because
there would be many. Do I need to do an inverse relationship in
other models? Examples I've seen don't all show the hasMany
relationships setup.
When I look at the models in ember inspector the site field is not
included in the model. Even if I wasn't getting the correct data
should it still show up?
I like ember so far, just need to understand and get over this roadblock
Update: My backend JSON library would only generate relationship links based on the current spec which would be
"related": "/streams/1/site"
but ember data does call
"related": "/sites/1"
to resolve the relationship
So #Adam Cooper answer is correct if you generate links as he answered or if you can only generate the links based on the current specification.
If you're using the JSONAPIAdapter, which is the default, you want your response to look this:
{
"links": {
"self": "/maint/associates"
},
"data": [{
"type": "associate",
"id": "1",
"attributes": {
"last": "Yoder",
"first": "Steven",
"active": "1"
},
relationships: {
"site": {
"links": {
related: "/sites/6"
}
}
}
}]
}
That will allow Ember Data to look up the site via its relationship. Right now Ember is trying to access the site model which Ember Data can't populate hence the error you're getting. As an aside you could probably do with returning an actual boolean value for active too.
I have an existing search app I built on the server-side that uses elasticsearch processed by python/flask. On the client side, it's using JS/jQuery and handlebars to parse and present the data.
I'd like to take it one step further and build it in ember, as the "controls" (filters, sorting, pagination, etc) make it a perfect candidate for an SPA.
I understand the ember fundamentals, but I feel like I've hit a wall with ember-data - i.e. how to get my data into the store so I can bind actions. Can somebody point me in the right direction?
My existing JSON API is accessed like this:
http://localhost:8000/search?q=shirt&paging=18&filter-price=100
and it returns JSON like this:
{
"aggregations": {
"breadcrumb": [],
"color": [],
"price": [],
"size_apparel": [],
"size_jewelry": []
},
"meta": {
"agg_list": [],
"default_sort": "",
"key_translations": {},
"paging": 18,
"paging_options": [],
"sort_methods": [],
"from": 0,
"hits": 89,
"pages": 5,
"q": "shirt"
},
"results": [
{
"creation_date": "",
"image": "",
"images": {
"altimg1": "",
"large": "",
"regular": "/",
"small": ""
},
"name": "This Product",
"price": "19.95",
"skuid": "ABC123",
"url": "shirt.html"
},
{...},
{...}
]
}
Is this usable or do I need to rewrite the back-end?? I've tinkered with accessing the data and have something very rough working. I can actually see the 'results' data, but I have NO IDEA how to access the 'meta' and 'aggregations' data. I think this is "multiple models in the sam payload" and RSVP.hash should work...but ember-data seems to have a requirement that each model have an id. That makes sense for the "results" model, but not for aggregations and definitely not for meta.
For now I just want to get it to show up.
For starters, my adapter is:
export default DS.RESTAdapter.extend({
host: 'http://localhost:8000',
pathForType() {
return 'search';
}
});
Test controller is:
export default Ember.Controller.extend({
queryParams: ['q','paging'],
q: 'shirt',
paging: '18'
});
my 'result' model is:
const { attr } = DS;
export default Model.extend({
'creation_date': attr('string'),
'has_pricerange': attr('string'),
'image': attr('string'),
'name': attr('string'),
'price': attr('string'),
'skuid': attr('string'),
'url': attr('string')
});
route:
export default Ember.Route.extend({
model(params) {
return this.store.query('result', {
q: params.q,
paging: params.paging
});
}
});
serializer:
export default DS.RESTSerializer.extend({
primaryKey: 'skuid'
});
This is so easy to do with jquery - you just $.get and then access the data with object notation. I know ember is not jquery, but it seems like it should be easier to access data. Am I missing something? Taking the wrong approach? Should I start from scratch?
Ember Data is a great piece of the Ember puzzle, but by no means required. Whether or not you should use Ember Data really depends on the nature of your data, the control of your API, and your desired futureproofability
Nature of your Data
Ember Data really excels at your typical model/entity type data. "Things" that have attributes, and can relate to each other. If your data follows that pattern then Ember Data could be a great fit.
Control of your API
As mentioned in the other answer, you get a lot for free if you can buy into a JSON API format. This has become the standard for Ember Data, but is not exclusive to Ember. A lot of server-side options will have ways to serialize these resources easily with many existing frameworks. If you can't change the data coming from the server, or if it's easier to just handle it on the front-end, you can go down the path of customizing the adapters/serializers in Ember.
Desired Futureproofability
Ember Data allows you to swap out the adapter/serializer while keeping your models in tact. This is desirable if you want your application to handle different sources in the future, maybe swap from using your own API to using a local storage, or a 3rd-party service like firebase. If don't plan on much changing, you can do your basic ajax calls and return a promise within your model hook and Ember will work more or less the same. I would recommend using ember-network which is spec'ed against the fetch API and will likely become more and more supported as a FastBoot compatible request.
Other things to consider is that Ember Data and using the Ember Object can be heavy depending on the amount of instances of each model you'll be passing in.
Others are tackling this problem, too. Toran Billups has pulled the redux pattern with ember-redux, which is another great way of thinking about how you approach your data.
Your use case
So you could:
Use Ember Data with find
Taking a look at the shape of your data, it looks like you're providing more of a search service than querying for specific models/entities. Ember Data does have a find method, but the amount of metadata you're providing it might be overloaded for the model use case.
Forget Ember Data and use search end point
model: function(params), Use the params from the model hook to construct the url to the search end point, and return a promise (add then as needed to shape the data)
Query the search end point (API refactor)
Similar idea as above, but you would use get the product id's and use those to query the ember data store for the products.
Something similar to:
fetch("service-end-point?with=queryparams")
.then(result => {
return {
products: this.store.findMany(Product, result.ids);
};
});
I think it'd be easiest to get started just handling and shaping the data directly in the controller, and skipping Ember Data. If you want specific computed property bindings there's no reason why you can extend Ember.Object with the shape you want and then use something like .map to take the result from the network request and apply it like payload => Object.create(payload).
Let me know if you have any questions surrounding any of these ideas.
I recommend you read this section of the guide, specifically the bit about $.getJSON. Ember is designed to use a powerful store. It's much easier to use if you're using a standard api (e.g. JSON API); you can just use Ember data code directly. But if not, you will have to write some serializer code so Ember knows how to use your api. Once that's done though, your component code won't be coupled to your data fetching code.
You can use $.getJSON if you are still prototyping, though.
My application backend has several resources. A model is exposed for each resource.
The entry point to all other resources is through the User model. What I mean is, given a User we can find BlogPost. Given a BlogPost we can find Comments etc.
In Ember terminology, we could say:
User hasMany BlogPost
BlogPost hasMany Comment
Comment belongsTo BlogPost
By backend exposes REST APIs of the form:
GET /api/v1/users/1
GET /api/v1/users/1/blog_posts/1
GET /api/v1/users/1/blog_posts/1/comments/1
I'm trying to figure out how to use Ember Data to fetch Comment belonging to a certain BlogPost belonging to a certain User.
From what I see, if I define a typical Ember model for Comment:
App.Comment = DS.Model.extend({
...
blogPost: DS.belongsTo('App.BlogPost')
});
and in the CommentRoute I have the following:
var CommentRoute = MessageRoute.extend({
model: function(params) {
this.store.find('comment')
},
The request is sent to:
/api/v1/comments
I don't even know where to begin in order for Ember Data to use urls of the form:
GET /api/v1/users/1/blog_posts/1/comments/1
I've seen several similar questions asked (see links below), but haven't seen a definitive answer to any of them. Most of them are almost a year old when ember-data, perhaps, did not have such functionality (or so it is claimed in some of these threads).
I ask again to confirm whether ember-data has such functionality or not.
Similar Questions:
Ember Data nested Models
Canonical way to load nested resources
Deep nested routes
The best way to handle it is with links. If you don't want to do it like that, it is far from supported, and difficult to hack in (the pipeline just doesn't easily pass the information around). Personally I'd recommend rolling your own adapter in that case (Ember without Ember Data).
App.Foo = DS.Model.extend({
name: DS.attr('string'),
bars : DS.hasMany('bar', {async:true})
});
App.Bar = DS.Model.extend({
foo: DS.belongsTo('foo'),
});
json:
{
id: 1,
name: "bill",
links: {
bars: '/foo/1/bars'
}
}
Example: http://emberjs.jsbin.com/OxIDiVU/971/edit
I have the following model defined:
App.Post = DS.Model.extend({
title: DS.attr('string'),
comments: DS.hasMany('comment')
});
App.Comment = DS.Model.extend({
message: DS.attr('string')
});
If I create a Post entry with a Comment, the JSON stored in my browsers local storage references the Comments as an array of IDs which works great:
...
"o3duh":{
"id":"o3duh",
"title":"How to write Ember",
"comments":[
"jf0a2"
]
}
...
However, the moment I add another Post, the JSON suddenly changes such that Comments are embedded:
...
"o3duh":{
"id":"o3duh",
"title":"How to write Ember",
"comments":[
{
"message":"First!"
}
]
},
"6kudl":{
"id":"6kudl",
"title":"Learning Ember is painful",
"comments":[
]
}
...
Why is this happening? Can I prevent it? This is causing me problems because once it changes into this embedded format, the data cannot be read by the LSAdapter when reloading the page.
Here is a JSBin so you can see it happen for yourself and see the full JSON etc. To reproduce the problem, just create a post and add a comment then you can refresh the page without problem. Then add another post and try to refresh the page.
I'm not sure if the problem is with ember-data or the localstorage adapter.
I solved the problem by modifying the LocalStorageAdapter so that it only attempts to persist JSON in the expected format.
You can see the pull request I submitted to the original author here: https://github.com/rpflorence/ember-localstorage-adapter/pull/26
Hopefully it will either get folded into the LSAdapter project, or better still, someone will come up with a better solution ;)
I was able to fix the JSON issue by defining the inverse relationship on Comment:
App.Comment = DS.Model.extend({
message: DS.attr('string'),
post: DS.belongsTo('post')
});
There are new issues now, but hopefully this will help.
EmberJS has removed hasOne in the earlier revision. Whats the way to create such a nested object relation where I want to have hasOne
Removal of hasOne has been done in favor of belongsTo, can anyone share a thought on how to write {embedded : always} relation between nested JSON.
I know that this question is old and answered, but since it is one of the top
search results for "ember hasone" i wanted to share my findings on the subject.
I've read the link in the first answer but the example is kinda outdated.
The "embedded" flag is obsolete, "DS.RESTAdapter.map" is not a function and the "DS.hasOne" method deprecated.
The current 1.0.0-beta.2 solution for emulating the "hasOne relationship" is simply using "DS.belongsTo". They are not very different and you just need to add the hasOne foreignKeys to your result-set just like you would with belongsTo.
Source: https://github.com/emberjs/data/commit/a466741a36731c5d382df33461268024627325ef
Here's an example server response from a complex model.
{"users": [{
"id": 1,
"name": "John Doe",
"profile": 27, // merged hasone
"image": 3, // merged hasone
"account_id": 64 // an actual belongsTo
}]}
And then as model
App.User = DS.Model.extend({
name: DS.attr('string'),
profile: DS.belongsTo('profile'),
image: DS.belongsTo('image'),
account_id: DS.belongsTo('account')
});
Hope this helps anyone looking for info on how to model a hasOne
You have to set the mapping on the adapter, please see this answer for a working example.