I'm trying a simple post retrieval app with ember. Everything works fine as long as i'm not passing arguments to the find() function:
This works great:
return this.store.find('post')
But not these:
return this.store.find('post', { title: 'title1' })
return this.store.find('post', {})
The server returns exactly the same JSON regardless of the parameters, but Ember doesn't seem to process it when there's arguments. The store stays empty.
Here's the complete code:
App = Ember.Application.create({
LOG_TRANSITIONS: true
});
App.Router.map(function(){
this.resource('myPosts');
});
App.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'api/v1/'
});
App.Post = DS.Model.extend({
title : DS.attr( 'string' ),
body : DS.attr( 'string' )
});
App.MyPostsRoute = Ember.Route.extend({
model : function() {
return this.store.find('post', { title: 'title1' })
},
setupController: function (controller, model) {
controller.set('model', model);
}
});
In all cases I never get any error, the queries are processed just fine and I've checked in chrome DevTools that the JSON returned by the server is the same.
Here's the JSON returned.
{"post": [{"body": "a body", "title": "title1"}]}
The handelbars template is simply:
<script type="text/x-handlebars" id="myPosts">
<div class = "postsWrapper">
{{#each}}
<div>{{this}}</div>
<div>title: {{title}}</div>
{{/each}}
</div>
</script>
Here's the output i get with this.store.find('post', { title: 'title1' }):
<App.Post:ember382:null>
title:
Here's the one with i get with no parameters:
<App.Post:ember294:null>
title: title1
Thanks for you help.
Ok after further conversation, I think your issue is linked to the API.
When you call this.store.find('post') Ember should make a GET request to the api/v1/posts endpoint, which should return the following JSON:
Note: this is a plural posts object with an array of post objects.
{
"posts": [
{ "body": "body1", "title": "title1" },
{ "body": "body2", "title": "title2" }
]
}
When you call this.store.find('post', { title: 'title1' }) Ember should make a GET request to the api/v1/posts?title=title1 endpoint, which should return this JSON:
Note: this is a singular post object with a single post object.
{
"post": { "body": "body1", "title": "title1" }
}
The format the JSON comes back from the server is really important, the API needs to do the filtering, Ember doesn't filter unless you ask it to.
Related
As I'm going deeper and deeper into Ember.js application building process I hit another wall.
Before I was using mirage with great success - I just copy output from API that I wanted build around to mirage fixtures and it was working great.
Now I have problem with making it work with real API.
I first disabled mirage in config/environment.js
ember g adapter filter
import ApplicationAdapter from './application';
export default ApplicationAdapter.extend({
findAll: function(store, type, label) {
var url = `${this.host}/${this.namespace}/${type.modelName}`;
console.log(`${url}`);
return this.ajax(url, 'GET');
},
});
The application adapter looks like this
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
host: 'http://127.0.0.1:1234',
namespace: 'api',
headers: Ember.computed(function(){
return {"secret": "1234"};
})
});
And that way when I enter /filter
app/routes/filter/index
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('filter');
}
});
I can see that url is build ok as http://127.0.0.1:1234/api/filter and there is no 404 but I get error
Error while processing route: filter.index The adapter operation was aborted EmberError#http://127.0.0.1:4200/assets/vendor.js:29616:15
and as I don't fully grasp the know-how of Ember Inspector im trying to figure this out somehow
My filter model that worked before (with mirage) looks like this:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr(),
url: DS.attr()
});
The api returns list [{"id":1, "name": "namex", "url": "http://"},{"id":2, "name": "namey", "url": "http://"}]
I'm sure If its the way api return data as its not "filters" as I had problem with plurals before.
Edit: THIS RESOLVE THe problem
import DS from 'ember-data';
export default DS.RESTSerializer.extend(
{
normalizeFindAllResponse(store, type, payload)
{
var data = [];
payload.forEach(
function(item, index, enumerable)
{
var ob = {};
Ember.set(ob, 'id', item.id);
Ember.set(ob, 'type', 'filter');
Ember.set(ob, 'attributes', item);
data[index]=ob;
console.log(data[index]);
}
);
return {
data: data
};
}
});
This is almost the fix but not quite.
I can access some of model attributes like name, but there is a array object with 3 arrays inside, 2 of them are array the one in middle is Getter (what ever is that) and I cannot access it as its not array anymore. So im not sure if it binding to object correctly this way. Also I wasn't able to do anything with "data" because no matter what RESTAdapter here and there I put it would ignore it and ask for object with data/meta/errors attribute... dunno if its a bug or not.
As you are using JSONAPIAdapter your api response should follow the JSON format specification.
{
"data": [{
"type": "filter",
"id": "1",
"attributes": {
"name": "namex",
"url": "http://"
}
}, {
"type": "filter",
"id": "2",
"attributes": {
"name": "namey",
"url": "http://"
}
}]
}
or if you are not following JSONAPI in that case either you can change application adapter to extend RESTAdapter.
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
host: 'http://127.0.0.1:1234',
namespace: 'api',
headers: Ember.computed(function(){
return {"secret": "1234"};
})
});
RESTAdapter response would be like,
{"filters":[{"id":1, "name": "namex", "url": "http://"},{"id":2, "name": "namey", "url": "http://"}]}
either you need to send it like above format or manipulate it to produce the required format.(override normalizeFindAllResponse)
I am using MongoDB in my Sails setup to store my collections.
I am making a POST request through an API, whose request body looks like this :
**THE REQUEST BODY**
{
"name":"Martin",
"phone":"5447874787",
"comment":"This is a comment",
"campaign":{
"campaign_id":123454587,
"name":"Digital Marketing",
"category":"Marketing",
"sub_category":"Digital",
"product_name":"no product",
"product_id":5417
}
}
This record is to be stored in my mongodb collection named "leads". My "Leads" model looks like this:
module.exports = {
schema: true,
attributes:{
name:{
required:true,
type:"string",
unique: true
},
phone:{
type:"string"
},
comment:{
type:"string"
},
campaign:{
model: 'lead_campaign_detail',
// via: "lead_model"
}
}
};
The associated "Lead_campaign_detail" model is as following
module.exports = {
attributes:{
campaign_id:{
required:true,
type:'integer'
},
name:{
type:"string"
},
category:{
type:"string"
},
sub_category:{
type:"string"
},
product_name:{
type:"string"
},
product_id:{
type:"integer"
}
}
};
And My controller handler is:
create_lead_api:function(req, res){
Leads.create(params, function(err, resp){
if(err) return res.negotiate(err);
else return res.json(resp);
})
},
WHAT I HAVE TRIED
1> I tried using .native(). It creates a new record BUT it does not care about the attributes in the model "Leads" or the other one. It just saves as the request is.
2> Tried creating using the .create(). It does not stores the nested values of index "campaign" in the request: the stored value looks like this:
{ **IN MY DB it looks like**
"name": "martin",
"phone": "5447874787",
"comment": "This is a comment",
"campaign": "579083f049cb6ad522a6dd3c",
"id": "579083f049cb6ad522a6dd3d"
}
I WANT TO
1> Store the record in the same format as the requested am sending.
2> Make use of the waterline ORM function .create() to achieve the same if possible.
3> .native() is also in option if my Model-attributes:{} can be used to bring in between before storing the record. I want to store only values defined in attributes:{ }
Any help is appreciated, Google is failing me.
Thanks
If you want the document in 'leads' collection to look like your request body, then you should use:
campaign: {
type: "json"
}
in your 'Leads' model, rather than linking to 'lead_campaign_detail' collection. You can then do away with the 'lead_campaign_detail' model.
By saying model: 'lead_campaign_detail' you are essentially linking the "Leads" document to "lead_campaign_detail", See Waterline One-way association and populate() for more details.
It seems that ember data have many changing up to version 1.0 beta. All works great with version 0.13. now I want update to higher version. In my case we have an embedded model 'user'
App.Post = DS.Model.extend({
subject: DS.attr('string'),
created: DS.attr('number'),
fcreated: function(){
debugger;
var d = new Date(this.get('created'));
return d.toLocaleDateString();
}.property('created'),
reporter: DS.belongsTo('user')
}
App.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'restws'
});
The Json from server looks like this.
{
"posts": [
{
"id": "5226f2670364e70ae7d77266",
"subject": "Text",
"created": 1325410935048,
"reporter": {
"id": "5226f2660364e70ae7d771e2",
"firstName": "Doris",
"lastName": "Baumertr"
}
}
I get the following error code 'Uncaught TypeError: Cannot call method 'toString' of undefined'. In the ember source code I see, that in ember-data.js line 2236 the function throw the error 'buildRecord: function(type, id, data) .. ' After debugging I see that the properties type is undefined id is set with the correct id and data is undefined?
What is the mistake? How I can map the embedded data?
Here's actually the exact extractSingle method that you need to implement
App.PostSerializer = DS.RESTSerializer.extend({
extractSingle: function(store, type, payload, id, requestType) {
if(typeof payload.post.reporter !== "undefined") {
var reporter_id = payload.post.reporter.id;
payload.users = [payload.post.reporter];
payload.post.reporter = reporter_id;
}
return this._super.apply(this, arguments);
}
});
Here's a jsbin http://jsbin.com/EKItexU/1/edit?html,js,output
Note, that I had to redefine ajax method in RESTAdapter to emulate the server returning your JSON.
Also, if you are sideloading users in your JSON, than you'll have to update this method so it doesn't overwrite sideloaded users in your payload (payload.users property)
Support for embedded records is gone (for now).
You can handle embedded records yourself by implementing extractSingle and reorganizing your JSON payload.
Please read here for more info about the transition: https://github.com/emberjs/data/blob/master/TRANSITION.md#embedded-records
Hope it helps.
This is driving me nuts. I have a simple data model set up (using Padrino); I'm long past the stage of actually getting any error messages but adding 'App.Repo' models to an 'App.Stack' model just…doesn't work.
App.Store = DS.Store.extend({
revision: 10
adapter: DS.RESTAdapter.create({
bulkCommits: false,
mappings: {
stars: App.Stars,
stacks: App.Stacks
}
})
});
App.Stack = DS.Model.extend({
url: DS.attr('string'),
repos: DS.hasMany('App.Repo')
});
App.Repo = DS.Model.extend({
name: DS.attr('string'),
url: DS.attr('string'),
description: DS.attr('string'),
language: DS.attr('string'),
watchers: DS.attr('number'),
stack: DS.belongsTo('App.Stack'),
stackId: DS.attr('number')
});
var store = App.get('router.store');
newStack = store.createRecord(App.Stack);
console.log(newStack.serialize())
-> Object {url: null} // no mention of a repos array like I was expecting?
newStack.set('url', 'http://google.com');
console.log(newStack.serialize());
-> Object {url: "http://google.com"} // this works though
var repo = App.Repo.find().objectAt(0);
console.log(repo.serialize());
-> Object {name: "floere/james", url: "https://github.com/floere/james", description: "Voice commanded servant for OSX", language: "Ruby", watchers: 97…}
// so this exists too…
repos = newStack.get('repos');
repos.pushObject(repo);
newStack.get('repos.length'); // 1 (repos.toArray() etc etc all work too)
// but then…
console.log(newStack.serialize())
-> Object {url: null}
// and so then I try to save the relationship on the server anyway…
store.commit()
=> {"stack"=>{"url"=>nil}} // in my Ruby server logos
The store is all set up fine talking to my back end (for example submitting a POST to /repo.json sends the correct request); it just doesn't recognise that App.Stack has any relation.
No idea what's going wrong or what to look at for help :(
Also
I tried making the relations in my Ruby console and then accessing them in a view. This is what happens
// in the router
router.get('applicationController').connectOutlet('body', 'stacks', router.get('store').findAll(App.Stack));
// in the view
<script type="text/x-handlebars" data-template-name="stacks">
{{#each stack in controller }}
{{stack.id}} // this works
{{stack.url}} // this works
{{stack.repos.length}} // this returns the correct count
{{#each repo in stack.repos}}
// this loops the right number of times. so there *is* something there. somehow.
{{repo}} // prints out <App.Repo:ember490>
{{repo.id}} // prints out [object Object]
{{/each}}
{{/each}}
On that last note - maybe a clue in the [object Object]?
I'm so lost :(
More Info:
I'm using Padrino with Mongoid, using RABL to give me JSON. As I said, I can query for & template out my Stack & Repo records. Here's a JSON sample for the /stacks.json endpoint
{
"stacks": [
{
"account_id": null,
"id": "50c127ff6f094144ed000001",
"stars": [
{
"description": "Voice commanded servant for OSX",
"id": "50c128996f0941cfe8000001",
"name": "floere/james"
}
]
}
]
}
I think you'll have to add hasMany relationships to your json object manually by looping through the repos array. I'm doing this in my adapter's createRecord method.
createRecord: (store, type, record) ->
data = {}
data[root] = #toData(record, { includeId: true })
repos = []
stack.get("repos").forEach (repo) ->
repos.pushObject repo.serialize()
data[root]["repos"] = repos
...
I've found a way to get embedded related objects in the JSON to load properly. Basically you have to subclass the serializer and then in its initializer you tell it to register a map for the relationship. Here's an example for a model class called Category that has a to-many relationship 'resourceTypes':
App.WOSerializer = DS.Serializer.extend({
init: function(){
this._super();
this.map(App.Category, {
resourceTypes: { embedded: 'load' }
});
}
});
My solution is further explained here.
I'm currently trying to put something together with ember + emberdata + router + asp.net web api. Most of it seem to work, however I stuck in an error message I get when ember-data tries to findAll through the adapter for my models.
In my backend I have a model like this (C#):
public class Genre {
[Key]
public int Id { get; set; }
[Required]
[StringLength(50, MinimumLength=3)]
public string Name { get; set; }
}
Which in my app I represent it like this using ember-data:
App.Genre = DS.Model.extend({
id: DS.attr("number"),
name: DS.attr("string")
}).reopenClass({
url: 'api/genre'
});
I have also a Store defined in my App using the RESTAdapter like so:
App.store = DS.Store.create({
revision: 4,
adapter: DS.RESTAdapter.create({
bulkCommit: false
})
});
And the store is used in my controller as below:
App.GenreController = Ember.ArrayController.extend({
content: App.store.findAll(App.Genre),
selectedGenre: null
});
The router is defined as
App.router = Em.Router.create({
enableLogging: true,
location: 'hash',
root: Ember.Route.extend({
//...
genre: Em.Route.extend({
route: '/genre',
index: Ember.Route.extend({
connectOutlets: function (router, context) {
router.get('applicationController').connectOutlet('genre');
}
})
}),
//...
})
})
When I run my application, I get the following message for every object that has this same structure:
Uncaught Error: assertion failed: Your server returned a hash with the
key 0 but you have no mappings
For reference, here's the json the service is returning:
[
{
"id": 1,
"name": "Action"
},
{
"id": 2,
"name": "Drama"
},
{
"id": 3,
"name": "Comedy"
},
{
"id": 4,
"name": "Romance"
}
]
I cannot tell exactly what the problem is and since the assertion is mentioning that I need mapping, I'd like to know:
What this mapping is and how to use it.
Since the returned json is an array, should I be using a different type of controller in my app ,or is there anything I should know about when working with this type of json in ember-data? or should I change the JsonFormatter options in the server?
Any help is welcome.
I can definitely add more information if you feel this isn't enough to understand the problem.
EDIT: I've changed a few things in my backend and now my findAll() equivalent action in the server serializes the the output as the following json:
{
"genres": [
{ "id": 1, "name": "Action" },
{ "id": 2, "name": "Drama" },
{ "id": 3, "name": "Comedy" },
{ "id": 4, "name": "Romance" }
]
}
But I still can't get it to populate my models in the client and my error message has changed to this:
Uncaught Error: assertion failed: Your server returned a hash with the
key genres but you have no mappings
Not sure what else I might be doing wrong.
The method that throws this exception is sideload and checks for the mappings like this:
sideload: function (store, type, json, root) {
var sideloadedType, mappings, loaded = {};
loaded[root] = true;
for (var prop in json) {
if (!json.hasOwnProperty(prop)) { continue; }
if (prop === root) { continue; }
sideloadedType = type.typeForAssociation(prop);
if (!sideloadedType) {
mappings = get(this, 'mappings');
Ember.assert("Your server returned a hash with the key " + prop + " but you have no mappings", !!mappings);
//...
This call sideloadedType = type.typeForAssociation(prop); returns undefined and then I get the error message. The method typeForAssociation() checks for the for 'associationsByName' key which returns an empty Ember.Map.
Still no solution for this at the moment.
By the way...
My action is now like this:
// GET api/genres
public object GetGenres() {
return new { genres = context.Genres.AsQueryable() };
}
// GET api/genres
//[Queryable]
//public IQueryable<Genre> GetGenres()
//{
// return context.Genres.AsQueryable();
//}
I had to remove the original implementation which gets serialized by json.NET as I could not find config options to produce a json output as Ember-Data expects ( as in {resource_name : [json, json,...]}). Side effect of this is that I've lost built-in OData support, but I'd like to keep it. Does anyone know how could I configure it to produce different json for a collection?
The mapping can be defined in the DS.RESTAdapter. I think you could try to define something like this:
App.Store = DS.Store.extend({
adapter: DS.RESTAdapter.create({
bulkCommit: true,
mappings: {
genres: App.Genre
},
// you can also define plurals, if there is a unregular plural
// usually, RESTAdapter simply add a 's' for plurals.
// for example at work we have to define something like this
plurals: {
business_process: 'business_processes'
//else it tries to fetch business_processs
}
}),
revision: 4
});
Hope this resolves your problem.
Update:
At this time, this is not well documented, I don't remember if we found it by ourself reading the code, or perhaps Tom Dale pointed on it.
Anyway, here is the point for plurals
For the mappings, I think we were driven by the same error as you, and either we tried, either Tom teached us about this.
The RESTAdapter expects the returned JSON to be of the form:
{
"genres": [{
"id": 1,
"name": "action"
},{
"id": 2,
"name": "Drama"
}]
}
The tests are a good source of documentation, see https://github.com/emberjs/data/blob/master/packages/ember-data/tests/unit/rest_adapter_test.js#L315-329
I'm using Ember Data rev. 11 and it seems that the plurals config in DS.RESTAdapter.create never works. I looked into the codes and found a solution as following:
App.Adapter = DS.RESTAdapter.extend({
bulkCommit: false
})
App.Adapter.configure('plurals', {
series: 'series'
})