How to set mapping on Ember-Data - ember.js

I'm beginning to work with ember-data and I have a problem to map the data
Here an exemple of my code ( i've place a jsonTest as an exemple of the data received from de backend , I don't work on the backend and I cannot modify the response from the server )
Clive = Ember.Application.create();
// MODEL
Clive.Deadline = DS.Model.extend({
title : DS.attr('string'),
});
jsonTest = '{"items":[{"id":93,"title":"title","date":"14-11-2012"}]}';
// Adapter
Clive.adapter = DS.Adapter.create({
findAll : function(store,type){
var self = this;
self.didFindAll(store, type, jsonTest);
}
});
Clive.store = DS.Store.create({
revision: 11,
adapter: 'Clive.adapter'
});
Clive.deadlineList = Clive.Deadline.find().get('items');
When I run the code I have this error :
Uncaught Error: assertion failed: Your server returned a hash with the key 0 but you have no mapping for it
Here a jsfidlle with the exemple : http://jsfiddle.net/gilles/6D5BC/

The "server returned a hash with key 0" thing is because didFindAll() expects a javascript object not a string. So trying again with:
json = {"items":[{"id":93,"title":"title","date":"14-11-2012"}]};
// -> "Your server returned a hash with the key items but you have no mapping for it"
Next step is to transform the object to have naming conventions that ember is expecting. Since your model is named Deadline, use the following:
jsonTransformed = '{"deadlines": [{"id":93,"title":"title 1","date":"14-11-2012"},{"id":94,"title":"title 2","date":"14-11-2012"}]}';
I've added a second record, but you get the idea. Finally you need to change how the Clive.deadlineList variable is being set: Clive.Deadline.find() returns a collection of Clive.Deadline models, so just:
Clive.deadlineList = Clive.Deadline.find()
console.log(Clive.deadlineList.getEach('title'));
// -> title1, title2
Here is an updated jsfiddle with working example:
http://jsfiddle.net/mgrassotti/6D5BC/9/

Another simple solution is to use the following Gem. It will just make your life easier. You wont need to generate or structure the json manually.
gem 'active_model_serializers'
For more help, have a look on this free screencast

Related

Update Ember model attribute in component

I have the following bit of code in an Ember component:
markResolved() {
let reportingFailure = this.get('reportingFailure');
reportingFailure.resolvedAt = '2017-01-01';
reportingFailure.save();
}
The HTTP request is firing as expected but the resolved_at attribute is not getting set.
What do I have to do differently to get the attribute to set?
How is reportingFailuredefined? Due to the savemethod call, I assume it's an ember-data model.
Try to use .set() to change the resolvedAt attribute?:
markResolved() {
let reportingFailure = this.get('reportingFailure');
reportingFailure.set('resolvedAt', '2017-01-01');
reportingFailure.save();
}

ember-data with Spring/Hibernate Backend

I am wondering how I can use ember-data working with a Spring/Hibernate Java backend. I would usually use Jackson to return JSON but that does not appear support the specifications required by jsonapi.org.
Currently beans are returned in a hierarchical nature like this.
{
"id" : 1,
"name" : "Bill",
"surname" : "Smith",
"address" : {
"id" : 23,
"number" : 21,
"street" : "Vincent st",
"state" : {
"id" : 44,
"name" : "Victoria"
"abbreviation" : "VIC"
}
"postcode" : 9000
}
}
I am not stuck to this structure and it can be modified to suite jsonapi, however, I can't find any jackson plugin that will serialize/deserialize my objects to the jsonapi specification.
What are my options here? I know that I am able to write my own serializer/deserializer for ember-data but that would be a huge pain, surely there are other people using ember with a java backend.
This looks familiar to an issue I had parsing json with DataFX (using javaFX), json was also generated by Spring...
I ended up returning a Page which wraps your Person and creates some json that is parseable.
Just my 2cts... I added the following requestmapping to my PersonController:
#RequestMapping(value="/persons", method=RequestMethod.GET, headers="Accept=application/json, application/xml")
public #ResponseBody Page<Person> getPersons(
#RequestParam(value="page",required=false,defaultValue="0") String page,
#RequestParam(value="size",required=false,defaultValue="20") String size,
#RequestParam(value="orderby",required=false,defaultValue="name") String orderby){
PersonRepository repository = context.getBean(PersonRepository.class);
final PageRequest pr = new PageRequest( Integer.parseInt(page), Integer.parseInt(size), Direction.ASC, orderby);
Page<Person> persons = (Page<Person>) repository.findAll(pr);
return persons;
}
I'm new to ember and still in the design stages with php as a backend, but I believe I have the same problem and found what looks like a fix. You're right that messing with jackson would be a difficult approach. It seems like it's much easier to make the change on the ember side. This guide (http://lab.empirio.no/emberjs-say-hello-to-apigility.html) discusses creating our own serializer in js based on ember data's ActiveModelSerializer and then modify the RestAdapter. The example is discussing building the standard ToDo app that I'm sure you've seen used as an example already.
The problem is the backend uses this format:
{"name":"Testing","is_completed":false}
While ember uses:
{"todo":{"name":"Testing","is_completed":false}}
Here's some same code:
App.ApplicationSerializer = DS.ActiveModelSerializer.extend({
primaryKey: 'todos_id',
extract: function(store, type, payload, id, requestType) {
this.extractMeta(store, type, payload);
if(payload._embedded)
payload = payload._embedded;
if(requestType == 'updateRecord' || requestType == 'createRecord'){
var data = {};
data[type.typeKey] = payload;
payload = data;
}
var specificExtract = "extract" + requestType.charAt(0).toUpperCase() + requestType.substr(1);
return this[specificExtract](store, type, payload, id, requestType);
}
});
... "we manipulate the payload by extending (copying from the RESTAdapter).
the createRecord-function changes the behavior on the adapter like this: "
createRecord: function(store, type, record) {
var data = {};
var serializer = store.serializerFor(type.typeKey);
serializer.serializeIntoHash(data, type, record, { includeId: true });
return this.ajax(this.buildURL(type.typeKey), "POST", { data: data[type.typeKey] });
},
I'm sure I'm missing a lot here and probably missing some steps since I haven't even tried to build the app yet, but this was a problem I knew I needed to address before I committed to ember and this seems like a viable solution. I hope its a step in the right direction for you anyway.
edit: I know you didn't want to change the format for the sake of sticking to the standard, but there really isn't a standard for JSON APIs and content types yet, not one that's accepted anyway. This guide uses HAL + JSON which doesn't look any different from what I see at jsonapi.org if that's what you were talking about. Regardless, everyone seems to be having this issue regardless of backend language or frameworks.I think the ember team recognizes this and are trying to be flexible. Also, since ember-data is still in beta, I'd be more apt to make the changes there instead of writing the java side to support a changing library. Who knows? Maybe ember-data will have better support for different backends before its 1.0 release, although I haven't heard any mention of it on the roadmap.

How to get store name of Ember Data model

How can I determine the "store name" (not sure what the proper terminology is) for a given ED Model? Say I have App.Payment, is there a store method that let's me look up its corresponding name, i.e. payment (for example to use in find queries)?
For Ember Data 1.0 (and later)
modelName is a dasherized string. It stored as a class property, so if you have an instance of a model:
var model = SuperUser.create();
console.log(model.constructor.modelName); // 'super-user'
For Ember Data Pre 1.0
typeKey is the string name of the model. It gets stored as a class property of the model, so if you have an instance of a model:
var model = App.Name.create({});
console.log(model.constructor.typeKey); // 'name'
You might be looking for Ember's string dasherize method:
var fullClassName = "App.SomeKindOfPayment";
var className = fullClassName.replace(/.*\./, ""); // => "SomeKindOfPayment"
var dasherizedName = Ember.String.dasherize(className); // "some-kind-of-payment"
There might be a built-in way to do this in Ember, but I haven't found it after spending some time looking.
EDIT: Ember Data might also let you get away with passing "App.SomeKindOfPayment" when a model name is needed - it usually checks the format of the model name and updates it to the required format by itself.
store.find, store.createRecord, and other persistence methods, use the store.modelFor('myModel'). After some setup it call container.lookupFactory('model:' + key); where key is the 'myModel'. So any valid factory lookup syntax is applicable. For example:
Given a model called OrderItems you can use: order.items, order_items, order-items, orderItems.
It turns out there was no need to do this after all, and here's why:
I was trying to the the string representation of the model ("payment" for App.Payment) in order to call store.findAll("payment"). However, looking at the ED source for store, the findQuery function calls modelFor to look up the factory (App.Payment) from the string (payment), unless a factory is already provided. And the factory is easily accessible from the controller by calling this.get('model').type. There's no need to convert it to a string (and back).
Here's the relevant code from the Ember Data source.
modelFor: function(key) {
var factory;
if (typeof key === 'string') {
factory = this.container.lookupFactory('model:' + key);
Ember.assert("No model was found for '" + key + "'", factory);
factory.typeKey = key;
} else {
// A factory already supplied.
factory = key;
}
factory.store = this;
return factory;
},

Twitter Typeahead remote

I am trying to use Twitter typeahead but I am facing a problem. I don't know how typeahead passes the string to the server. Is it through a GET parameter? If so, what is the name of the parameter?
Easiest through a GET parameter, you can choose whatever parameter you want.
In JS:
$('#search').typeahead({
name: 'Search',
remote: '/search.php?query=%QUERY' // you can change anything but %QUERY, it's Typeahead default for the string to pass to backend
});
In PHP (or whatever backend you have):
$query = $_GET['query'];
Hope you get the basic idea.
You might want to consider something like this, it is a very basic remote datasource example. The get parameter in this example is 'q'
// Get your data source
var dataSource = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: 'path/to/your/url/json/datasource/?q=%QUERYSTRING',
wildcard: '%QUERYSTRING'
}
});
// initialize your element
var $typehead = $('#form input').typeahead(null, {
source: dataSource
});
// fire a select event, what you want once a user has selected an item
$typehead.on('typeahead:select', function(obj, datum, name) {
//your code here
});
////////////////////////////////////
# in python (django) we get a query string using the request object passed through a view like this
query = request.GET.get('q') or ""
//the caveat [or ""] is just to prevent null exceptions
///////////////////////////////////
# using php
$query = ($_GET['q']) ? $_GET['q'] : "";

Adding item to filtered result from ember-data

I have a DS.Store which uses the DS.RESTAdapter and a ChatMessage object defined as such:
App.ChatMessage = DS.Model.extend({
contents: DS.attr('string'),
roomId: DS.attr('string')
});
Note that a chat message exists in a room (not shown for simplicity), so in my chat messages controller (which extends Ember.ArrayController) I only want to load messages for the room the user is currently in:
loadMessages: function(){
var room_id = App.getPath("current_room.id");
this.set("content", App.store.find(App.ChatMessage, {room_id: room_id});
}
This sets the content to a DS.AdapterPopulatedModelArray and my view happily displays all the returned chat messages in an {{#each}} block.
Now it comes to adding a new message, I have the following in the same controller:
postMessage: function(contents) {
var room_id = App.getPath("current_room.id");
App.store.createRecord(App.ChatMessage, {
contents: contents,
room_id: room_id
});
App.store.commit();
}
This initiates an ajax request to save the message on the server, all good so far, but it doesn't update the view. This pretty much makes sense as it's a filtered result and if I remove the room_id filter on App.store.find then it updates as expected.
Trying this.pushObject(message) with the message record returned from App.store.createRecord raises an error.
How do I manually add the item to the results? There doesn't seem to be a way as far as I can tell as both DS.AdapterPopulatedModelArray and DS.FilteredModelArray are immutable.
so couple of thoughts:
(reference: https://github.com/emberjs/data/issues/190)
how to listen for new records in the datastore
a normal Model.find()/findQuery() will return you an AdapterPopulatedModelArray, but that array will stand on its own... it wont know that anything new has been loaded into the database
a Model.find() with no params (or store.findAll()) will return you ALL records a FilteredModelArray, and ember-data will "register" it into a list, and any new records loaded into the database will be added to this array.
calling Model.filter(func) will give you back a FilteredModelArray, which is also registered with the store... and any new records in the store will cause ember-data to "updateModelArrays", meaning it will call your filter function with the new record, and if you return true, then it will stick it into your existing array.
SO WHAT I ENDED UP DOING: was immediately after creating the store, I call store.findAll(), which gives me back an array of all models for a type... and I attach that to the store... then anywhere else in the code, I can addArrayObservers to those lists.. something like:
App.MyModel = DS.Model.extend()
App.store = DS.Store.create()
App.store.allMyModels = App.store.findAll(App.MyModel)
//some other place in the app... a list controller perhaps
App.store.allMyModels.addArrayObserver({
arrayWillChange: function(arr, start, removeCount, addCount) {}
arrayDidChange: function(arr, start, removeCount, addCount) {}
})
how to push a model into one of those "immutable" arrays:
First to note: all Ember-Data Model instances (records) have a clientId property... which is a unique integer that identifies the model in the datastore cache whether or not it has a real server-id yet (example: right after doing a Model.createRecord).
so the AdapterPopulatedModelArray itself has a "content" property... which is an array of these clientId's... and when you iterate over the AdapterPopulatedModelArray, the iterator loops over these clientId's and hands you back the full model instances (records) that map to each clientId.
SO WHAT I HAVE DONE
(this doesn't mean it's "right"!) is to watch those findAll arrays, and push new clientId's into the content property of the AdapterPopulatedModelArray... SOMETHING LIKE:
arrayDidChange:function(arr, start, removeCount, addCount){
if (addCount == 0) {return;} //only care about adds right now... not removes...
arr.slice(start, start+addCount).forEach(function(item) {
//push clientId of this item into AdapterPopulatedModelArray content list
self.getPath('list.content').pushObject(item.get('clientId'));
});
}
what I can say is: "its working for me" :) will it break on the next ember-data update? totally possible
For those still struggling with this, you can get yourself a dynamic DS.FilteredArray instead of a static DS.AdapterPopulatedRecordArray by using the store.filter method. It takes 3 parameters: type, query and finally a filter callback.
loadMessages: function() {
var self = this,
room_id = App.getPath('current_room.id');
this.store.filter(App.ChatMessage, {room_id: room_id}, function (msg) {
return msg.get('roomId') === room_id;
})
// set content only after promise has resolved
.then(function (messages) {
self.set('content', messages);
});
}
You could also do this in the model hook without the extra clutter, because the model hook will accept a promise directly:
model: function() {
var self = this,
room_id = App.getPath("current_room.id");
return this.store.filter(App.ChatMessage, {room_id: room_id}, function (msg) {
return msg.get('roomId') === room_id;
});
}
My reading of the source (DS.Store.find) shows that what you'd actually be receiving in this instance is an AdapterPopulatedModelArray. A FilteredModelArray would auto-update as you create records. There are passing tests for this behaviour.
As of ember.data 1.13 store.filter was marked for removal, see the following ember blog post.
The feature was made available as a mixin. The GitHub page contains the following note
We recommend that you refactor away from using this addon. Below is a short guide for the three filter use scenarios and how to best refactor each.
Why? Simply put, it's far more performant (and not a memory leak) for you to manage filtering yourself via a specialized computed property tailored specifically for your needs