How ember data (store) generates ID - ember.js

How exactly ID build? Data stored in store by ID, but how the ID is generated?
//Code just to pass requirements
normalize(model, hash, prop) {
hash.id = 'someId';
return this._super(...arguments);
}

If you ask about new records ( which creates on client side ) :
https://github.com/emberjs/data/blob/v2.5.3/addon/-private/system/store.js#L329
/**
If possible, this method asks the adapter to generate an ID for
a newly created record.
#method _generateId
#private
#param {String} modelName
#param {Object} properties from the new record
#return {String} if the adapter can generate one, an ID
*/
_generateId(modelName, properties) {
var adapter = this.adapterFor(modelName);
if (adapter && adapter.generateIdForRecord) {
return adapter.generateIdForRecord(this, modelName, properties);
}
return null;
},
Also check documentation for generateIdForRecord http://emberjs.com/api/data/classes/DS.Adapter.html#method_generateIdForRecord

Related

RESTSerializer extend not affecting Ember data objects

JSON from Sails responses don't have root elements. Ember data requires that the JSON be wrapped in a root element with the same name as the object. The plan is to extend RESTSerializer to munge the JSON on the client side, because I don't have similar control over the server that I'm getting this data from. I'm not sure what I'm doing wrong, but it doesn't seem like my Accounts model is using my AccountSerializer...
This is my Serializer:
var AccountSerializer = DS.RESTSerializer.extend({
/**
The current ID index of generated IDs
#property
#private
*/
_generatedIds: 0,
/**
Sideload a JSON object to the payload
#method sideloadItem
#param {Object} payload JSON object representing the payload
#param {subclass of DS.Model} type The DS.Model class of the item to be sideloaded
#param {Object} item JSON object representing the record to sideload to the payload
*/
sideloadItem: function(payload, type, item){
var sideloadKey = type.typeKey.pluralize(), // The key for the sideload array
sideloadArr = payload[sideloadKey] || [], // The sideload array for this item
primaryKey = Ember.get(this, 'primaryKey'), // the key to this record's ID
id = item[primaryKey];
// Missing an ID, generate one
if (typeof id == 'undefined') {
id = 'generated-'+ (++this._generatedIds);
item[primaryKey] = id;
}
// Don't add if already side loaded
if (sideloadArr.findBy("id", id) != undefined){
return payload;
}
// Add to sideloaded array
sideloadArr.push(item);
payload[sideloadKey] = sideloadArr;
return payload;
},
/**
Extract relationships from the payload and sideload them. This function recursively
walks down the JSON tree
#method sideloadItem
#param {Object} payload JSON object representing the payload
#paraam {Object} recordJSON JSON object representing the current record in the payload to look for relationships
#param {Object} recordType The DS.Model class of the record object
*/
extractRelationships: function(payload, recordJSON, recordType){
// Loop through each relationship in this record type
recordType.eachRelationship(function(key, relationship) {
var related = recordJSON[key], // The record at this relationship
type = relationship.type; // belongsTo or hasMany
if (typeof related === "object" && related !== null){
// One-to-one
if (relationship.kind == "belongsTo") {
// TODO: figure out when we need to only sideload 1 item we don't need to pluralize
// Sideload the object to the payload
this.sideloadItem(payload, type, related);
// Replace object with ID
recordJSON[key] = related.id;
// Find relationships in this record
this.extractRelationships(payload, related, type);
}
// Many
else if (relationship.kind == "hasMany") {
// Loop through each object
related.forEach(function(item, index){
// Sideload the object to the payload
this.sideloadItem(payload, type, item);
// Replace object with ID
related[index] = item.id;
// Find relationships in this record
this.extractRelationships(payload, item, type);
}, this);
}
}
}, this);
return payload;
},
/**
Overrided method
*/
extractArray: function(store, type, payload, id, requestType) {
var typeKey = type.typeKey,
typeKeyPlural = typeKey.pluralize(),
newPayload = {};
newPayload[typeKeyPlural] = payload;
payload = newPayload;
console.log(payload);
// Many items (findMany, findAll)
if (typeof payload[typeKeyPlural] != "undefined"){
payload[typeKeyPlural].forEach(function(item, index){
this.extractRelationships(payload, item, type);
}, this);
}
for(var key in payload) {
if(key === typeKeyPlural) {
for(var i =0; i < payload[key].length; i++) {
if(typeof payload[key][i] !== 'object') {
delete payload[key][i];
}
}
}
}
return this._super(store, type, payload, id, requestType);
},
extractSingle: function (store, type, payload, id, requestType) {
console.log('what is happening');
var typeKey = type.typeKey,
typeKeyPlural = typeKey.pluralize(),
newPayload = {};
if(typeof payload[typeKey] !== "object") {
newPayload[typeKey] = payload;
payload = newPayload;
if(payload[typeKey] instanceof Array) {
payload[typeKey] = payload[typeKey][0];
}
}
if (typeof payload[typeKey] === "object"){
this.extractRelationships(payload, payload[typeKey], type);
delete payload[typeKeyPlural];
}
console.log(payload);
return this._super(store, type, payload, id, requestType);
}
});
export default AccountSerializer;
In my adapters/account.js I have the following:
import DS from "ember-data";
var AccountAdapter = DS.RESTAdapter.extend({
namespace: 'api/v1',
host: 'http://localhost:5000',
pathForType: function(type) {
return type + '.json';
},
serializer: AccountSerializer
});
export default AccountAdapter;
I'm not 100% sure where you got the serializer property from, but I'm not sure that actually exists. There's a defaultSerializer property on the adapter which would work, but that comes with a bit of weird precedence rules. If I were you, I would declare the serializer in the Ember CLI way by putting it in serializers/account.js and remove the serializer property from your adapter.

How to pass multiple (dynamic) Parameter to store.find() method // avoid duplicate entrys

we have a search formular with multiple (dynamic generated) input fields.
Our server rest api is Spring MVC powered and accepts a call like this:
/rest/search/1?identnummer=0000000&parameter2=xy&parameter3=zy
In the search method of the formular (action: search)
i collect all parameters and build up a string like this:
queryparams = 1?identnummer=0000000&parameter2=xy&parameter3=zy
and then call the store.find method:
that.store.find('trefferliste', queryparams).then(function(response) {
console.log("Success loading hit list...", response);
},
function(error) {
console.error(error);
that.transitionTo('trefferliste');
}
});
I overwrote the buildURL Method (removed encodeURIComponent()
if (id && !Ember.isArray(id)) { url.push(encodeURIComponent(id)); }
for the TrefferlisteAdapter
App.TrefferlisteAdapter = App.ApplicationAdapter.extend({
buildURL: function(type, id) {
var url = [];
var host = helicDevServer;
var prefix = this.urlPrefix();
if (type) { url.push(this.pathForType(type)); }
if (id && !Ember.isArray(id)) { url.push(id); }
if (prefix) { url.unshift(prefix); }
url = url.join('/');
if (!host && url) { url = '/' + url; }
return url;
}
});
After the request is send and i receive the expected json response, i have two entrys in the store:
Entry 1 : id = 1
Entry 2 : id = 1?identnummer=0000000&parameter2=xy&parameter3=zy
The first entry is the expected one, the second is corrupt and causes this error:
Error: Assertion Failed: Expected an object as `data` in a call to `push` for App.Trefferliste , but was undefined
at new Error (native)
at Error.EmberError (/js/libs/ember-stable/ember.debug.js:12061:21)
at Object.Ember.default.assert (/js/libs/ember-stable/ember.debug.js:5162:13)
at ember$data$lib$system$store$$Service.extend.push (/js/libs/ember-canary/ember-data.js:10849:15)
at /js/libs/ember-canary/ember-data.js:4805:24
at Object.Backburner.run (/js/libs/ember-stable/ember.debug.js:217:27)
at ember$data$lib$system$store$$Service.extend._adapterRun (/js/libs/ember-canary/ember-data.js:11143:33)
at /js/libs/ember-canary/ember-data.js:4802:22
at tryCatch (/js/libs/ember-stable/ember.debug.js:46977:16)
at invokeCallback (/js/libs/ember-stable/ember.debug.js:46989:17)
So my questions are:
Is this a acceptable method to pass the dynamic parameters to the
find method?
Does someone know how i can prevent the corrupt item to
get in the store?
Some more information about my setup:
DEBUG: Ember : 1.11.3
ember.debug.js:5197 DEBUG: Ember Data : 1.0.0-beta.17+canary.4ad70aab2a
ember.debug.js:5197 DEBUG: jQuery : 2.1.3
Serializer Method for Sideloading my records:
/**
Deserialize nested JSON payload into a flat object with sideloaded relationships. */
App.NestedSerializer = DS.RESTSerializer.extend({
/**
Generate a unique ID for a record hash
#param {DS.Store} store
#param {DS.Model} type
#param {Object} hash The record hash object
#return {String} The new ID
*/
generateID: function(store, type, hash){
var id, primaryKey,
serializer = store.serializerFor(type);
type._generatedIds = type._generatedIds || 0;
// Get the ID property name
primaryKey = Ember.get(serializer, 'primaryKey') || 'id';
id = hash[primaryKey];
// Generate ID
if (!id) {
id = ++type._generatedIds;
hash[primaryKey] = id;
}
return id;
},
/**
Sideload a record hash to the payload
#method sideloadRecord
#param {DS.Store} store
#param {DS.Model} type
#param {Object} payload The entire payload
#param {Object} hash The record hash object
#return {Object} The ID of the record(s) sideloaded
*/
sideloadRecord: function(store, type, payload, hash) {
var id, sideLoadkey, sideloadArr, serializer;
// If hash is an array, sideload each item in the array
if (hash instanceof Array) {
id = [];
hash.forEach(function(item, i){
id[i] = this.sideloadRecord(store, type, payload, item);
}, this);
}
// Sideload record
else if (typeof hash === 'object') {
sideLoadkey = type.typeKey.pluralize(); // Sideloaded keys are plural
sideloadArr = payload[sideLoadkey] || []; // Get/create sideload array
id = this.generateID(store, type, hash);
// Sideload, if it's not already sideloaded
if (sideloadArr.findBy('id', id) === undefined){
sideloadArr.push(hash);
payload[sideLoadkey] = sideloadArr;
}
}
// Assume it's already pointing to a sideloaded ID
else {
id = hash;
}
return id;
},
/**
Process nested relationships on a single hash record
#method extractRelationships
#param {DS.Store} store
#param {DS.Model} type
#param {Object} payload The entire payload
#param {Object} hash The hash for the record being processed
#return {Object} The updated hash object
*/
processRelationships: function(store, type, payload, hash) {
// If hash is an array, process each item in the array
if (hash instanceof Array) {
hash.forEach(function(item, i){
hash[i] = this.processRelationships(store, type, payload, item);
}, this);
}
else {
// Find all relationships in this model
type.eachRelationship(function(key, relationship) {
var related = hash[key], // The hash for this relationship
relType = relationship.type; // The model type
this.generateID(store, type, hash);
hash[key] = this.sideloadRecord(store, relType, payload, related);
this.processRelationships(store, relType, payload, related);
}, this);
}
return hash;
},
/**
(overloaded method)
Starts the deserialized of all payloads.
#method normalizePayload
#param {Object} payload
#return {Object} the normalized payload
*/
extract: function(store, type, payload, id, requestType) {
payload = this.normalizePayload(payload);
var rootKeys = Ember.keys(payload);
// Loop through root properties and process extract their relationships
rootKeys.forEach(function(key){
var type = store.container.lookupFactory('model:' + key.singularize()),
hash = payload[key];
// If the key resolves to a model type, sideload any embedded relationships
if (type) {
this.processRelationships(store, type, payload, hash);
}
}, this);
console.log(payload);
return this._super(store, type, payload, id, requestType);
}
,
normalizePayload: function(payload) {
if(payload.elementBeanWrapper){
payload.dashboard = payload.elementBeanWrapper;
delete payload.elementBeanWrapper;
}
return payload;
}
});
App.ApplicationSerializer = App.NestedSerializer;
You can monitor query params like this:
#..controller
queryParamsDidChange: (->
#store.find('myModel', #get('customQueryParams')).then (models)=>
#set 'content', models
).observes 'customQueryParams'
customQueryParams will be object like {myParam1: '123', myParam2: '456'}
Will this work for you?

Normalize ember-data payload in normalizePayload, but "type" is null

I'm trying to write code to generically normalize embedded records. I'm trying to do this in the "normalizePayload" method of the serializer, as "extractObject" and "extractArray" don't get called on a store.pushPayload call (I'm pre-loading data using pushPayload).
Here's my code:
App.ApplicationSerializer = DS.ActiveModelSerializer.reopen
embeddedRelationships: []
extractHasOne: (payload, relationship_name) ->
object = payload[relationship_name]
objectId = object['id']
payload["#{relationship_name}_id"] = objectId
delete payload[relationship_name]
return object
extractHasMany: (payload, relationship_name) ->
objects = payload[relationship_name]
objectIds = objects.mapProperty('id')
payload["#{Ember.Inflector.inflector.singularize(relationship_name)}_ids"] = objectIds
delete payload[relationship_name]
return objects
extractRelationships: (payload) ->
extracted_objects = {}
this.embeddedRelationships.forEach((relationship_name) ->
relationship_payload = payload[relationship_name]
if relationship_payload instanceof Object
# has one
extracted_objects[relationship_name] = this.extractHasOne(relationship_payload, relationship_name)
else if relationship_payload instanceof Array
# has many
extracted_objects[relationship_name] = this.extractHasMany(relationship_payload, relationship_name)
, this)
return extracted_objects
normalizePayload: (type, payload) ->
if payload[type]?
# single object
this.extractRelationships(payload[type])
else if payload[Ember.Inflector.inflector.pluralize(type)]?
# many objects
payload[Ember.Inflector.inflector.pluralize(type)].forEach((object_payload) ->
this.extractRelationships(object_payload)
, this)
NOTE: this isn't done yet, as I'm not merging all the extracted objects and sideloading them yet.
In either case, the idea is that in a subclass, I override "embedded relationships" with a list of strings that represent the relationships that must be normalized for that model.
The problem I'm running into is simple: "normalizePayload" is being called with a null "type" value.
I'm pre-loading the data like so:
this.store.pushPayload('category', window.preloadCategories)
Is this an ember bug, or am I missing something?
pushPayload takes the type and grabs that serializer then it calls pushPayload on the custom serializer which doesn't take a type, so no type is passed into the normalizePayload.
I'd suggest creating a custom serializer for Category instead of Application, then you'll know its a category type.
The reason behind this was pushPayload was geared to pushing more than a single type (aka the payload isn't just Category or Post
pushPayload on the store
/**
Push some raw data into the store.
The data will be automatically deserialized using the
serializer for the `type` param.
This method can be used both to push in brand new
records, as well as to update existing records.
You can push in more than one type of object at once.
All objects should be in the format expected by the
serializer.
```js
App.ApplicationSerializer = DS.ActiveModelSerializer;
var pushData = {
posts: [
{id: 1, post_title: "Great post", comment_ids: [2]}
],
comments: [
{id: 2, comment_body: "Insightful comment"}
]
}
store.pushPayload('post', pushData);
```
#method pushPayload
#param {String} type
#param {Object} payload
*/
pushPayload: function (type, payload) {
var serializer;
if (!payload) {
payload = type;
serializer = defaultSerializer(this.container);
Ember.assert("You cannot use `store#pushPayload` without a type unless your default serializer defines `pushPayload`", serializer.pushPayload);
} else {
serializer = this.serializerFor(type);
}
serializer.pushPayload(this, payload);
},
pushPayload on the serializer
It will first normalize the payload, so you can use this to push
in data streaming in from your server structured the same way
that fetches and saves are structured.
#method pushPayload
#param {DS.Store} store
#param {Object} payload
*/
pushPayload: function(store, payload) {
payload = this.normalizePayload(null, payload);
for (var prop in payload) {
var typeName = this.typeForRoot(prop),
type = store.modelFor(typeName);
/*jshint loopfunc:true*/
var normalizedArray = map.call(payload[prop], function(hash) {
return this.normalize(type, hash, prop);
}, this);
store.pushMany(typeName, normalizedArray);
}
},

How can I clone an Ember Data record, including relationships?

I've figured out that I can clone an Ember Data record and copy its Attributes, but none of the belongsTo/hasMany relationships are cloned. Can I do this somehow if I don't know what relationships would be possible, going off of the relationships that exist?
For reference, here is what I've got that will clone an Ember Data record's attributes:
var attributeKeys = oldModel.get('constructor.attributes.keys.list');
var newRecord = this.get('store').createRecord(oldModel.constructor.typeKey);
newRecord.setProperties(oldModel.getProperties(attributeKeys));
A few improvements to Daniel's answer that allows providing overrides, and clears the id for new records, to be safe. Also I prefer not to call save within the clone method, but leave it up to the caller.
DS.Model.reopen({
clone: function(overrides) {
var model = this,
attrs = model.toJSON(),
class_type = model.constructor;
var root = Ember.String.decamelize(class_type.toString().split('.')[1]);
/*
* Need to replace the belongsTo association ( id ) with the
* actual model instance.
*
* For example if belongsTo association is project, the
* json value for project will be: ( project: "project_id_1" )
* and this needs to be converted to ( project: [projectInstance] )
*/
this.eachRelationship(function(key, relationship) {
if (relationship.kind == 'belongsTo') {
attrs[key] = model.get(key);
}
});
/*
* Need to dissociate the new record from the old.
*/
delete attrs.id;
/*
* Apply overrides if provided.
*/
if (Ember.typeOf(overrides) === 'object') {
Ember.setProperties(attrs, overrides);
}
return this.store.createRecord(root, attrs);
}
});
Here is a clone function that I use. Takes care of the belongs to associations.
DS.Model.reopen({
clone: function() {
var model = this,
attrs = model.toJSON(),
class_type = model.constructor;
var root = Ember.String.decamelize(class_type.toString().split('.')[1]);
/**
* Need to replace the belongsTo association ( id ) with the
* actual model instance.
*
* For example if belongsTo association is project, the
* json value for project will be: ( project: "project_id_1" )
* and this needs to be converted to ( project: [projectInstance] )
*
*/
this.eachRelationship(function(key, relationship){
if (relationship.kind == 'belongsTo') {
attrs[key] = model.get(key)
}
})
return this.store.createRecord(root, attrs).save();
}
})
There's an addon called ember-cli-copyable that according to its description:
Deeply copies your records including their relations. The mixin is smart enough to resolve not loaded relations and is configurable to what should be shallow/deeply copied or excluded entirely.
Here is the simple way to clone your Ember Model with relationships.
working fine.
Create a Copyable mixin like,
import Ember from 'ember';
export default Ember.Mixin.create(Ember.Copyable, {
copy(deepClone) {
var model = this, attrs = model.toJSON(), class_type = model.constructor;
var root = Ember.String.decamelize(class_type.toString().split(':')[1]);
if(deepClone) {
this.eachRelationship(function(key, relationship){
if (relationship.kind == 'belongsTo') {
attrs[key] = model.get(key).copy(true);
} else if(relationship.kind == 'hasMany' && Ember.isArray(attrs[key])) {
attrs[key].splice(0);
model.get(key).forEach(function(obj) {
attrs[key].addObject(obj.copy(true));
});
}
});
}
return this.store.createRecord(root, attrs);
}
});
Add the mixin in your model,
Note: If you want to clone your child model then, you need to include the mixin in child model as well
USAGE:
With relationship : YOURMODEL.copy(true)
Without relationship : YOURMODEL.copy()

How to flush zend_form to multiple entities

I'm struggling with flushing my zend_form to 2 entities. In total I've 3 entities. User, Address and Country. Country is not interesting because it has standard values where only the ID come into Address. My add-action works good (only when I keep my address/country empty, it puts a new record in the database so I've to fix this). But my edit only flushes my latest bind. My edit-action:
/**
* Edit action for single user
*
* #return route zfcadmin/user/add
* #return route zfcadmin/user
* #return array(id, form, flashMessages)
*/
public function editAction()
{
$id = (int)$this->getEvent()->getRouteMatch()->getParam('id');
if (!$id) {
return $this->redirect()->toRoute('zfcadmin/user/', array('action' => 'add'));
}
//Get user with the id (given in URL), and pick his address(es)
$user = $this->getEntityManager()->find('User\Entity\User', $id);
$addresses = $user->addresses;
$form = new UserForm();
//Set country values out of the entity
$form = $this->fillCountrySelectbox($form);
$form->bind($user);
//Get addresses from the user, and bind it to the $form
foreach($addresses as $address) {
//If user has address, bind the addresses
if(isset($address)) {
$form->bind($address);
}
}
$form->get('save_goback')->setAttribute('value', 'Save');
$form->get('save')->setAttribute('value', 'Save & Stay');
$request = $this->getRequest();
if ($request->isPost()) {
//Set the inputfilter on the input values
$inputFilter = new InputFilter();
//Set filters from different entities
$inputFilter = $user->setFilters($inputFilter);
$inputFilter = $address->setFilters($inputFilter);
//Set the inputFilter on the form
$form->setInputFilter($inputFilter);
$form->setData($request->getPost());
if ($form->isValid()) {
$form->bindValues();
//set complete country object in address entity.
$country = $this->getEntityManager()->find('User\Entity\Country', $form->get('country')->getValue());
$address->__set('country', $country);
//Set country Null when no country was selected, otherwise it conflict with country entity (because no '' is set)
if($address->country == '') {
$address->country = NULL;
}
//Set modifier (current user)
$address->__set('last_modifier_id', $this->zfcUserAuthentication()->getIdentity()->getId());
$this->flashMessenger()->addMessage('User saved');
$this->getEntityManager()->flush();
if ($this->getRequest()->getPost('save_goback')) {
return $this->redirect()->toRoute('zfcadmin/user');
}
}
}
return array(
'id' => $id,
'form' => $form,
'flashMessages' => $this->flashMessenger()->getMessages()
);
}
When I not bind the address, it will flush to the user-entity, and when I bind the address, it flushes to address only, so my edits on the user won't work. How can I edit my form, with saving the user AND address?