Last active
July 20, 2017 13:55
-
-
Save borissuska/d8e4d75708b6ea763537a1f40a9cf70c to your computer and use it in GitHub Desktop.
Check relationships change
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Adapter from "ember-data/adapters/json-api"; | |
export default Adapter.extend(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember'; | |
import Session from '../utils/session'; | |
export default Ember.Controller.extend({ | |
store: Ember.inject.service(), | |
model: undefined, | |
session: undefined, | |
modelToEdit: undefined, | |
allRelated: undefined, | |
actions: { | |
loadModel() { | |
this.get('store').findRecord('myModel', 1).then((model) => this.set('model', model)); | |
}, | |
editModel() { | |
let session = Session.create(); | |
this.set('session', session); | |
this.get('store').findAll('myRelated').then((related) => this.set('allRelated', related)); | |
session.add(this.get('model')).then((model) => { | |
this.set('modelToEdit', model) | |
}); | |
}, | |
changeRandomly() { | |
Ember.$.get('/my-models/1/random-change'); | |
}, | |
changeRelated(related, event) { | |
// let related = this.get('store').peekRecord('myRelated', relatedId); | |
if (event.target.checked) { | |
related.set('model', this.get('modelToEdit')); | |
// this.get('modelToEdit.related').addObject(related); | |
} else { | |
related.set('model', undefined); | |
// this.get('modelToEdit.related').removeObject(related); | |
} | |
}, | |
submit() { | |
this.get('session').submit(); | |
}, | |
rollback() { | |
this.get('session').rollback(); | |
this.set('session', undefined); | |
this.set('modelToEdit', undefined); | |
} | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function returnJSON(status, body) { | |
return json(...arguments); | |
}; | |
export function json(status, body) { | |
if (arguments.length === 1) { | |
body = status; | |
status = 200; | |
} | |
return [ | |
status, | |
{ "Content-Type": "application/json" }, | |
JSON.stringify(body) | |
]; | |
}; | |
export const server = new Pretender(); | |
export function initialize() { | |
server.handledRequest = function(verb, path, request) { | |
console.log(`handled request to ${verb} ${path}`); | |
}; | |
server.unhandledRequest = function(verb, path, request) { | |
console.log(`undhandled request ${verb} ${path}`, JSON.parse(request.requestBody)); | |
}; | |
let db = { | |
myModel: { | |
1: { | |
attributes: { | |
name: 'model 1', | |
version: 1 | |
}, | |
relationships: { | |
related: { | |
data: [ | |
{ type: 'my-related', id: '1' }, | |
{ type: 'my-related', id: '2' }, | |
{ type: 'my-related', id: '3' } | |
] | |
} | |
} | |
} | |
}, | |
myRelated: { | |
1: { | |
attributes: { | |
name: 'related 1', | |
version: 1 | |
}, | |
relationships: { | |
model: { | |
data: { type: 'my-model', id: '1'} | |
} | |
} | |
}, | |
2: { | |
attributes: { | |
name: 'related 2', | |
version: 1 | |
}, | |
relationships: { | |
model: { | |
data: { type: 'my-model', id: '1'} | |
} | |
} | |
}, | |
3: { | |
attributes: { | |
name: 'related 3', | |
version: 1 | |
}, | |
relationships: { | |
model: { | |
data: { type: 'my-model', id: '1'} | |
} | |
} | |
}, | |
4: { | |
attributes: { | |
name: 'related 4', | |
version: 1 | |
}, | |
relationships: { | |
model: { | |
data: null | |
} | |
} | |
}, | |
5: { | |
attributes: { | |
name: 'related 5', | |
version: 1 | |
}, | |
relationships: { | |
model: { | |
data: null | |
} | |
} | |
} | |
} | |
}; | |
function randomizeRelated(modelId, model = db['myModel'][modelId]) { | |
const a = []; | |
const relatedIds = Object.keys(db['myRelated']); | |
const rate = 1 + Math.floor(Math.random() * 5); | |
let lastIdx = 0; | |
for (let i=0; i < relatedIds.length; i++) { | |
let rId = relatedIds[i]; | |
let refModel = db['myRelated'][rId].relationships.model; | |
// exclude approximatelly 1 of RATE items | |
if (Math.floor(Math.random() * 10000) % rate !== 0) { | |
a[lastIdx++] = { type: 'my-related', id: rId }; | |
refModel.data = { type: 'my-model', id: modelId }; | |
} else if (refModel.data && refModel.data.id === modelId) { | |
refModel.data = null; | |
} | |
} | |
model.relationships.related.data = a; | |
} | |
server.map(function() { | |
// Get latest version of my model | |
this.get('/my-models/:id', function(request) { | |
return json({ | |
data: Object.assign({ | |
type: 'my-model', | |
id: request.params.id | |
}, db['myModel'][request.params.id]) | |
}); | |
}); | |
// Randomly change my model - it simulates concurrent update | |
this.get('/my-models/:id/random-change', function(request) { | |
const model = db['myModel'][request.params.id]; | |
// remove random | |
model.attributes.name = model.attributes.name.replace(/\s\(rev\.\s\d+\)$/, ''); | |
model.attributes.name += ` (rev. ${Math.round(Math.random()*10000)})`; | |
randomizeRelated(request.params.id, model); | |
model.attributes.version++; | |
return json({status: 'ok'}); | |
}); | |
// update my model only if version matches | |
this.patch('/my-models/:id', function(request) { | |
let originalData = db['myModel'][request.params.id]; | |
let newData = JSON.parse(request.requestBody).data; | |
if (originalData.attributes.version === newData.attributes.version) { | |
newData.attributes.version++; | |
db[request.params.id] = newData; | |
return json(202, { | |
data: Object.assign({ | |
type: 'my-model', | |
id: request.params.id | |
}, newData) | |
}); | |
} else { | |
return json(409, { | |
data: Object.assign({ | |
type: 'my-model', | |
id: request.params.id, | |
}, newData), | |
errors: [{ | |
code: 409, | |
status: "409", | |
title: "Concurrent modification of entity", | |
source: { | |
pointer: "data/attributes/version" | |
} | |
}] | |
}); | |
} | |
}); | |
// Get latest version of my-related | |
this.get('/my-relateds', function(request) { | |
let relatedIds = Object.keys(db['myRelated']); | |
let data = []; | |
for (let i=0; i < relatedIds.length; i++) { | |
let rId = relatedIds[i] | |
data.push(Object.assign({ | |
type: 'my-related', | |
id: rId | |
}, db['myRelated'][rId]) | |
); | |
} | |
return json({ | |
data: data | |
}); | |
}); | |
this.get('/my-relateds/:id', function(request) { | |
return json({ | |
data: Object.assign({ | |
type: 'my-related', | |
id: request.params.id | |
}, db['myRelated'][request.params.id]) | |
}); | |
}); | |
}); | |
}; | |
export default { | |
name: 'pretender', | |
initialize | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Model from "ember-data/model"; | |
import attr from "ember-data/attr"; | |
import { belongsTo, hasMany } from "ember-data/relationships"; | |
export default Model.extend({ | |
name: attr('string'), | |
version: attr('number'), | |
related: hasMany('myRelated', { async: true, inverse: 'model' }) | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.clearfix { | |
margin: 0 -15px; | |
} | |
.clearfix:after { | |
visibility: hidden; | |
display: block; | |
font-size: 0; | |
content: " "; | |
clear: both; | |
height: 0; | |
} | |
.clearfix { display: inline-block; } | |
/* start commented backslash hack \*/ | |
* html .clearfix { height: 1%; } | |
.clearfix { display: block; } | |
/* close commented backslash hack */ | |
.half { | |
width: 50%; | |
float: left; | |
padding: 0 15px; | |
} | |
.rest { | |
width: auto; | |
overflow: hidden; | |
} | |
body { | |
margin: 12px 16px; | |
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
font-size: 12pt; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"version": "0.12.1", | |
"EmberENV": { | |
"FEATURES": {} | |
}, | |
"options": { | |
"use_pods": false, | |
"enable-testing": false | |
}, | |
"dependencies": { | |
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js", | |
"ember": "2.12.2", | |
"ember-template-compiler": "2.12.0", | |
"ember-testing": "2.12.0", | |
"route-recognizer": "https://rawgit.com/tildeio/route-recognizer/56f5fcec6ae58d8e86b5dc77609809fb91198142/dist/route-recognizer.js", | |
"FakeXMLHttpRequest": "https://rawgit.com/pretenderjs/FakeXMLHttpRequest/23c3a96b5b24f1bfe595867437e4f128a29c2840/fake_xml_http_request.js", | |
"pretender": "https://rawgit.com/pretenderjs/pretender/c6de9afe18b1472aded2592f5a80ad9a26a0e262/pretender.js" | |
}, | |
"addons": { | |
"ember-data": "2.14.2", | |
"ember-array-contains-helper": "1.3.2", | |
"ember-concurrency": "0.8.7" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember'; | |
import { task, all } from 'ember-concurrency'; | |
const COPIES = "__session_copies__"; | |
const COPY_TASK_RUNNER = "__session_copy_task_runner__"; | |
const COPY_TASK = "__session_copy_task__"; | |
const { | |
assign, | |
inject, | |
Logger, | |
guidFor, | |
isEmpty, | |
runInDebug, | |
getOwner | |
} = Ember; | |
const { | |
keys | |
} = Object; | |
const PRIMITIVE_TYPES = ['string', 'number', 'boolean']; | |
export default Ember.Object.extend({ | |
[COPIES]: Ember.computed(function() { return {}; }), | |
_transforms: Ember.computed(function() { return {}; }), | |
/** | |
* Get the transform for a given type. | |
* | |
* @method getTransform | |
* @private | |
* @param {DS.Model} model | |
* @param {String} type | |
* @return {DS.Transform} | |
*/ | |
getTransform(model, type) { | |
let transform = this.get(`_transforms.${type}`); | |
if (!transform) { | |
transform = getOwner(model).lookup(`transform:${type}`); | |
this.set(`_transforms.${type}`, transform); | |
} | |
return transform; | |
}, | |
/** | |
* The copy task runner. Allows our copy task to have a drop | |
* concurrency policy | |
* | |
* @type {Task} | |
* @private | |
*/ | |
[COPY_TASK_RUNNER]: task(function *(model) { | |
let isSuccessful = false; | |
try { | |
let copy = yield this.get(COPY_TASK).perform(model); | |
isSuccessful = true; | |
return copy; | |
} catch (e) { | |
runInDebug(() => Logger.error('[ember-data-session]', e)); | |
// Throw so the task promise will be rejected | |
throw new Error(e); | |
} finally { | |
if (!isSuccessful) { | |
let copiesKeys = this.rollback(); | |
// Display the error | |
runInDebug(() => Logger.error(`[ember-data-session] Failed to copy model '${model}'. Cleaning up ${copiesKeys.length} created copies...`)); | |
} | |
} | |
}).drop(), | |
/** | |
* The copy task that gets called from `copy`. Does all the grunt work. | |
* | |
* NOTE: This task cannot have a concurrency policy since it breaks cyclical | |
* relationships. | |
* | |
* @type {Task} | |
* @private | |
*/ | |
[COPY_TASK]: task(function *(model) { | |
let { modelName } = model.constructor; | |
let copies = this.get(COPIES); | |
let store = getOwner(model).lookup('service:store'); | |
let guid = guidFor(model); | |
let attrs = {}; | |
// Handle cyclic relationships: If the model has already been copied, | |
// just return that model | |
if (copies[guid]) { | |
console.log('copy-guid', copies[guid] + ''); | |
return copies[guid]; | |
} | |
let copy = store.createRecord(modelName); | |
copies[guid] = copy; | |
// Copy all the attributes | |
model.eachAttribute((name, { type, options }) => { | |
if (!isEmpty(type) && !PRIMITIVE_TYPES.includes(type)) { | |
let value = model.get(name); | |
let transform = getTransform(model, type); | |
// Run the transform on the value. This should guarantee that we get | |
// a new instance. | |
value = transform.serialize(value, options); | |
value = transform.deserialize(value, options); | |
attrs[name] = value; | |
} else { | |
attrs[name] = model.get(name); | |
} | |
}); | |
let relationships = []; | |
// Get all the relationship data | |
model.eachRelationship((name, meta) => { | |
relationships.push({ name, meta }); | |
}); | |
// Copy all the relationships | |
for (let i = 0; i < relationships.length; i++) { | |
let { name, meta } = relationships[i]; | |
let value = yield model.get(name); | |
if (meta.kind === 'belongsTo') { | |
if (value) { | |
attrs[name] = yield this.get(COPY_TASK).perform(value); | |
} else { | |
attrs[name] = value; | |
} | |
} else if (meta.kind === 'hasMany') { | |
let allRels = []; | |
value.forEach((rel) => allRels.push(this.get(COPY_TASK).perform(rel))); | |
attrs[name] = yield all(allRels); | |
} | |
} | |
// Set the properties on the copied model | |
copy.setProperties(attrs); | |
return copy; | |
}), | |
add(model) { | |
// return copy instance | |
return this.get(COPY_TASK_RUNNER).perform(model); | |
}, | |
rollback() { | |
let store = undefined; | |
let copies = this.get(COPIES); | |
let copiesKeys = keys(copies); | |
// Unload all created records | |
copiesKeys.forEach((key) => { | |
let copy = copies[key]; | |
if (!store) { | |
store = getOwner(copy).lookup('service:store'); | |
} | |
store.unloadRecord(copy); | |
}); | |
this.set(COPIES, {}); | |
return copiesKeys; | |
}, | |
submit() { | |
let copies = this.get(COPIES); | |
let copiesKeys = keys(copies); | |
let copiesPromisses = []; | |
// save all records | |
copiesKeys.forEach((key) => { | |
copiesPromisses.push(copies[key].save()); | |
}); | |
return Ember.RSVP.all(copiesPromisses); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment