Not understanding fixtures in ember - ember.js

I am trying to, for better lack of a term stub out my application's user model using fixtures. I have been following the docs on fixtures, as well as associated links for model development. The issue is that I keep running into cannot GET /user/Administrator I have used yeoman to generate a ember project, but lets walk through what I have:
Set up the app
var EW = window.EW = Ember.Application.create();
Setup the fixture (store.js)
EW.ApplicationAdapter = DS.FixtureAdapter;
/* I need a fixture. */
EW.User.FIXTURES = [
{
"user":{
"id":1,
"first_name":"Adam",
"email":"adamkylebalan#email.com",
"last_name":"Balan",
"user_name": "Administrator",
"role_names":[
"Administrator"
],
"blogs":[
{
"id":2,
"title":"Blog Title",
"posts":[
{
"id":1,
"title":"Something",
"content":"Some Content",
"tag_names":[
"Some Tag",
"Other Tag"
],
"category_names":[
"Some Category",
"Another Category"
],
"blog_id":2,
"comments":[
{
"id":1,
"post_id":1,
"author":"Adam",
"comment":"This is a valid long comment."
}
]
}
]
}
]
}
}
]
Model
var attr = DS.attr;
// Set up the user model and it's relationships.
EW.User = DS.Model.extend({
first_name: attr('string'),
last_name: attr('string'),
user_name: attr('string'),
blogs: DS.hasMany('blog'),
role: DS.hasMany('roles'),
// Create full names for every one.
fullName: function(){
return this.get('first_name') + ' ' + this.get('last_name');
}.property('first_name', 'last_name')
});
Router
EW.Router.map(function(){
this.resource('user', { path: '/user/:user_name' });
});
EW.UserRoute = Ember.Route.extend({
model: function(params){
return jQuery.getJSON("/user/" + params.user_name);
}
serialize: function(model){
return { user_name: model.get('user_name') }
}
});
As far as I can tell I have done everything correctly. throws the error:
Cannot GET /user/Administrator
and I am not sure if I failed to set something up properly. the console gives me no errors, the ember console wont load because of this error and the network tab is giving me a 404 for localhost:9000/user/Administrator
Ideas?

You’ll want to call return this.get('store').find('user', params.user_id) in your model hook:
EW.Router.map(function() {
this.resource('user', { path: '/users/:user_id' });
});
EW.UserRoute = Ember.Route.extend({
model: function(params) {
return this.get('store').find('user', params.user_id);
}
});
Note that it’s user_id rather than user_name. This is because the fixture adapter is hard coded to find records by their id rather than name. I’d suggest using the url-friendly form of the name as the ID.

Related

Ember-Data 2.1.0 hasMany not working (cannot read property 'replace' of undefined)

I searched everywhere but can't seem to find an answer for this simple problem here on SO.
Problem:
I have a hasMany relationship in a model that is loaded by the route in a findAll(). Payload looks fin according to many answers I've seen here, but I get "TypeError: Cannot read property 'replace' of undefined". More details below.
How can I get this hasMany relationship to work? I'm using asynch:false and sending the sideload as recommended.
Using: Ember 2.1.0, Ember-Data 2.1.0.
Stacktrace:
TypeError: Cannot read property 'replace' of undefined
at Object.func (ember.debug.js:36026)
at Object.Cache.get (ember.debug.js:13165)
at decamelize (ember.debug.js:36068)
at Object.func (ember.debug.js:35974)
at Object.Cache.get (ember.debug.js:13165)
at Object.dasherize (ember.debug.js:36072)
at ember$data$lib$system$normalize$model$name$$normalizeModelName (normalize-model-name.js:13)
at ember$data$lib$serializers$json$serializer$$default.extend.modelNameFromPayloadKey (json-api-serializer.js:267)
at ember$data$lib$serializers$json$serializer$$default.extend._extractType (json-api-serializer.js:258)
at ember$data$lib$serializers$json$serializer$$default.extend.normalize (json-api-serializer.js:290)
Route:
app/routes/search.js
export default Ember.Route.extend({
model(params) {
if(params.query){
return this.store.findAll('search-result');
}
return null;
},
actions:{
sendSearch: function(queryString){
this.store.unloadAll('search-result');
this.refresh();
}
}
});
Models:
app/models/search-result.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
url: DS.attr('string'),
tags: DS.hasMany('search-result-tag', {async:false})
});
app/models/search-result-tag.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
});
Adapter (for search-result)
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
host: 'http://localhost:8080',
urlForFindRecord(id, modelName, snapshot) {
let url = this._super(...arguments);
let query = Ember.get(snapshot, 'adapterOptions.query');
if (query) {
url += '?' + Ember.$.param(query); // assumes no query params are present already
}
return url;
},
urlForFindAll(modelName) {
var queryDict = {};
location.search.substr(1).split("&").forEach(function(item) {queryDict[item.split("=")[0]] = item.split("=")[1]})
let url = this._super(...arguments);
let query = queryDict['query'];
if (query) {
url += '?query=' + query; // assumes no query params are present already
}
return url;
}
});
Payload
{
"search-result-tags": [
{
"name": "this-is-tag-#-0",
"id": 0
}
],
"search-results": [
{
"description": "This is description for blabla2",
"id": 0,
"title": "Blabla 2",
"url": "http://blablabla2.com",
"tags": []
},
{
"description": "This is description for blabla",
"id": 1,
"title": "Blabla",
"url": "http://blabla.com",
"tags": [
0
]
}
]
}
You need to use the RESTSerializer in addition to the RESTAdapter. So app/serializers/application.js would be -
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
});
See the docs. You may need to override keyForAttribute, if you need to change cases / underscores of your keys.
Note that if you are using Rails API for the backend you want ActiveModelAdapter and ActiveModelSerializer, which are available as an addon.

Retrieve count (total) from non-default REST API

I would like to query my server for this URL http://localhost:1337/api/posts/count?category=technology using Ember. I'm using the default RESTAdapter like this:
export default DS.RESTAdapter.extend({
coalesceFindRequests: true,
namespace: 'api',
)};
The Post model looks like this:
import DS from 'ember-data';
var Post = DS.Model.extend({
title: DS.attr('string'),
permalink: DS.attr('string'),
body: DS.attr('string')
});
export default Post;
How do I make such a request?
I think you have at least two ways to achieve that when you don't have a control over your backend. Otherwise, you can still use the RESTful API (see the last section of my answer).
Override buildURL
The first one is to use the existing RESTAdapter functionalities and only override the buildURL method:
var PostAdapter = DS.RESTAdapter.extend({
namespace: 'api',
buildURL: function(type, id, record) {
var originalUrl = this._super(type, id, record);
if (this._isCount(type, id, record)) { // _isCount is your custom method
return originalUrl + '/count';
}
return originalUrl;
}
});
where in the _isCount method you decide (according to record property for example) if it's what you want. And then, you can pass the params using the store:
this.store.find('post', {
category: technology
});
Override findQuery
The second way is to override the whole findQuery method. You can either use the aforementioned DS.RESTAdapter or use just DS.Adapter. The code would look as the following:
var PostAdapter = DS.Adapter.extend({
namespace: 'api',
findQuery: function(store, type, query) {
// url be built smarter, I left it for readability
var url = '/api/posts/count';
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.getJSON(url, query).then(function(data) {
Ember.run(null, resolve, data);
}, function(jqXHR) {
jqXHR.then = null;
Ember.run(null, reject, jqXHR);
});
});
},
});
and you use the store as in the previous example as well:
this.store.find('post', {
category: technology
});
Obtaining 'count' from meta
If you have a complete control on your backend, you can leverage the power of metadata.
The server response then would be:
// GET /api/posts/:id
{
"post": {
"id": "my-id",
"title": "My title",
"permalink": "My permalink",
"body": "My body"
},
"meta": {
"total": 100
}
}
and then you can obtain all the meta information from:
this.store.metadataFor("post");
Similarly, you can use this approach when getting all the posts from /api/posts.
I hope it helps!

Ember ActiveModelAdapter customization

My Ember application interacts with an API (through a DS.ActiveModelAdapter adapter) which respond to GET "/api/v1/users?username=mcclure.rocio" with a JSON like:
{
"user": {
"id": 5,
"name": "Rocio McClure",
"username": "mcclure.rocio",
"email": "rocio.mcclure#yahoo.com"
}
}
My router is:
Router.map(function() {
this.route("login");
this.route("user", {path: "user/:username"}, function() {
this.route("profile");
});
});
So I have route like http://localhost:4200/user/mcclure.rocio which is kind of summary of a user.
The problem is loading the correct model in the route:
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(params) {
return this.store.find('user', { username: params.username })
}
});
My Ember inspector states that the loaded model is an empty DS.AdapterPopulatedRecordArray. That's because findQuery (which is actually called as I provide a query object) expect to fetch a JSON array while my API return a single user JSON object, so it translates it to an empty array.
However this.store.find('user', { username: params.username }) build the right request to my API but how can I make the Store accept the API response and serve it as model to my route?
note:
If my API returned an array a could do something like this:
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(params) {
return this.store.find('user', { username: params.username }).then(function(data){
return data.objectAtContent(0);
});
}
});
but, I prefer not to modify it.
You should make use of the normalizePayload function on DS.RestSerializer to modify the response to the format Ember Data expects.

Force ember route to request additional data before rendering

I'm trying to get a nested route to make a request for additional data, update the record, and then render the view. See below:
// models
var attr = DS.attr,
hasMany = DS.hasMany,
belongsTo = DS.belongsTo
App.List = DS.Model.extend({
title: attr('string'),
links: hasMany('link')
})
App.Link = DS.Model.extend({
list: belongsTo('list'),
subtitle: attr('string'),
})
// JSON for index route
{
"list": [
{
"id": 532,
"title": "first list"
},
{
"id": 991,
"title": "second list"
},
{
"id": 382,
"title": "third list"
}
]
}
// JSON for list route - /user/532
{
"list":
{
"id": 532,
"title": "list numero uno",
"links" : [1, 2]
},
"link": [
{
"id": 1,
"subtitle": "this is a subtitle of the firsto listo!"
},
{
"id": 2,
"subtitle": "this is a subtitle of a second part in the list"
}
]
}
// routing
this.resource('user', function(){
this.route('list', {path: ':id'})
})
App.UserRoute = Ember.Route.extend({
model: function(){
return this.store.find('list')
}
})
App.UserListRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('list', params.id)
}
})
I want the index route to display a basic list with just the {{title}}, and clicking on one links it to UserListRoute which then makes another ajax call to update the record with the additional link data. I've tried adding:
afterModel: function(modal){
model.reload()
}
to the UserListRoute but it initially uses the index route model, and then it reloads with the new data. I'm trying to avoid this, and make it send an ajax request immediately and then render the content - so I don't want it to depend on the parent model/data.
create a different record type, and extend from the original, or just use a completely different model.
App.BasicList = DS.Model.extend({
foo: DS.attr()
});
App.FullList = App.BasicList.extend({
//foo
bar: DS.attr()
});
App.UserListRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('fullList', params.id)
}
})
I tried with kingpin2k's answer, and it worked on a page refresh but navigating from the user index to the nested still caused it to use the parent model. I figured out why it wasn't used the correct model. My links on the user index template were:
{{#link-to 'user.list' this}} {{title}} {{/link-to}}
So clicking on link correctly redirects me to /users/:id, but it passes on the data of the item selected. To remedy this I did:
{{#link-to `user.list` id}} {{title}} {{/link-to}}
So by changing this to id, when it transitions to the route, it uses the appropriate model specified in the route.

Ember.js - Error - "Assertion failed: You must include an `id` in a hash passed to `push`"

I'm getting this error after I save a post (title, text) to a mongodb database via a REST API written with Express and refresh the browser. I've already set my primary key to '_id' and have been reading about possibly normalizing the data?
Here is the payload from the server (only 1 post in db):
{
"posts": [
{
"title": "The Title",
"text": "Lorem ipsum",
"_id": "52c22892381e452d1d000010",
"__v": 0
}
]
}
The controller:
App.PostsController = Ember.ArrayController.extend({
actions: {
createPost: function() {
// Dummy content for now
var to_post = this.store.createRecord('post', {
title: 'The Title',
text: 'Lorem ipsum'
});
to_post.save();
}
}
});
The model:
App.Post = DS.Model.extend({
title: DS.attr('string'),
text: DS.attr('string')
});
Serializer:
App.MySerializer = DS.RESTSerializer.extend({
primaryKey: function(type){
return '_id';
}
});
Adapter:
App.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'api'
});
Any help is much appreciated! Please let me know if you need any other info.
Thanks
When using custom adapters/serializers the naming is important. If you want it to apply to the entire application it should be called ApplicationSerializer
App.ApplicationSerializer = DS.RESTSerializer.extend({
primaryKey: '_id'
});
Adapter:
App.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'api'
});
If you just want it to apply to a single model (this applies to adapter's as well)
App.PostSerializer = DS.RESTSerializer.extend({
primaryKey: '_id'
});
I had this same error but after some debugging discovered it was caused by my rest API not returning the saved objects json.