Skip to content

Commit

Permalink
Merge pull request #30 from ampatspell/transient-models
Browse files Browse the repository at this point in the history
Transient models
  • Loading branch information
ampatspell authored Jun 28, 2017
2 parents 76f89a6 + ec18635 commit af1b96f
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Version: {{version}}
### 2.0.7

* [new] `has-many-collection` relationships now support fastboot
* [new] `db.transient(modelName, id)` creates transient models which are not savable but are included in fastboot shoebox

### 2.0.6

Expand Down
4 changes: 0 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

## sofa

### Model

* transient models with generated ids for shoebox

### Query

* figure out what context properties are needed, provide those
Expand Down
11 changes: 11 additions & 0 deletions addon/computed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Ember from 'ember';

const {
computed
} = Ember;

export function transient(modelName, id) {
return computed(function() {
return this.transient(modelName, id);
}).readOnly();
}
58 changes: 52 additions & 6 deletions addon/database/database-deserialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ export default Ember.Mixin.create({
return internal;
},

_createTransientInternalModel(modelClass, modelId, props) {
let internal = this.get('store')._createTransientInternalModel(modelClass, this, modelId, props);
this._storeSavedInternalModel(internal);
return internal;
},

_deserializeInternalModelAttachments(internal, doc) {
let definition = internal.definition;
internal.withPropertyChanges(changed => {
Expand All @@ -32,6 +38,11 @@ export default Ember.Mixin.create({
return;
}

assert({
error: 'transient',
reason: `cannot deserialize document '${docId}' delete for transient model '${internal.modelName}'`
}, !internal.transient);

if(!internal.shouldDeserializeRevision(rev)) {
return internal;
}
Expand Down Expand Up @@ -63,6 +74,11 @@ export default Ember.Mixin.create({
_deserializeSavedDocumentToInternalModel(doc, expectedModelClass, optional=true, type=null) {
let docId = doc._id;
let rev = doc._rev;
let transient = !!doc._transient;

if(type !== 'shoebox') {
transient = false;
}

let modelClass = this._modelClassForDocument(doc);
if(!modelClass && !expectedModelClass) {
Expand All @@ -86,7 +102,18 @@ export default Ember.Mixin.create({
} else {
definition = this._definitionForModelClass(modelClass);
let modelId = definition.modelId(docId);
internal = this._createExistingInternalModel(modelClass, modelId, false);
if(transient) {
internal = this._createTransientInternalModel(modelClass, modelId);
} else {
internal = this._createExistingInternalModel(modelClass, modelId);
}
}

if(!transient) {
assert({
error: 'transient',
reason: `cannot deserialize document '${docId}' for transient model '${internal.modelName}'`
}, !internal.transient);
}

if(internal.shouldDeserializeRevision(rev)) {
Expand Down Expand Up @@ -137,7 +164,7 @@ export default Ember.Mixin.create({
let definition = internal.definition;
assert({
error: 'invalid_document',
reason: `document '${docId} is expected to be ${get(modelClass.class, 'modelName')} not ${definition.modelName}`
reason: `document '${docId}' is expected to be ${get(modelClass.class, 'modelName')} not ${definition.modelName}`
}, definition.is(modelClass));
return internal;
}
Expand All @@ -157,11 +184,10 @@ export default Ember.Mixin.create({
this._storeDeletedInternalModel(internal);
},

_existingInternalModelForModelClass(modelClass, modelId, opts) {
let { create, deleted } = merge({ create: false, deleted: false }, opts);
_existingInternalModelForModelClass(modelClass, modelId, opts, props) {
let { create, deleted, transient } = merge({ create: false, deleted: false, transient: false }, opts);

let definition = this._definitionForModelClass(modelClass);

let docId = definition.docId(modelId);

let internal = this._internalModelWithDocId(docId, deleted);
Expand All @@ -170,16 +196,36 @@ export default Ember.Mixin.create({
internal = this._internalModelWithDocId(docId, true);
}
if(!internal) {
internal = this._createExistingInternalModel(modelClass, modelId);
if(transient) {
internal = this._createTransientInternalModel(modelClass, modelId, props);
} else {
internal = this._createExistingInternalModel(modelClass, modelId);
}
}
}

if(transient) {
assert({
error: 'transient',
reason: `model '${definition.modelName}' with id '${modelId}' is already loaded`
}, internal.transient);
}

return internal;
},

_transientInternalModelForModelClass(modelClass, modelId, props) {
return this._existingInternalModelForModelClass(modelClass, modelId, { create: true, transient: true }, props);
},

_existingInternalModelForModelName(modelName, modelId, opts) {
let modelClass = this.modelClassForName(modelName);
return this._existingInternalModelForModelClass(modelClass, modelId, opts);
},

_transientInternalModelForModelName(modelName, modelId, props) {
let modelClass = this.modelClassForName(modelName);
return this._transientInternalModelForModelClass(modelClass, modelId, props);
}

});
16 changes: 16 additions & 0 deletions addon/database/database-internal-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ window.chunkArray = chunkArray;

export default Ember.Mixin.create({

_rejectTransient(operation) {
return reject(new Error({ error: 'transient', reason: `cannot ${operation} transient model` }));
},

_invokeInternalWillSaveCallbacks(internal) {
let model = internal.model;
if(!model) {
Expand Down Expand Up @@ -93,6 +97,10 @@ export default Ember.Mixin.create({
_saveInternalModel(internal, opts) {
opts = merge({}, opts);

if(internal.transient) {
return this._rejectTransient('save');
}

let force = !!opts.force;

let isNew = internal.state.isNew;
Expand Down Expand Up @@ -173,6 +181,10 @@ export default Ember.Mixin.create({
},

_reloadInternalModel(internal) {
if(internal.transient) {
return this._rejectTransient('reload');
}

if(internal.state.isNew) {
return reject(new Error({ error: 'not_saved', reason: 'Model is not saved yet' }));
}
Expand Down Expand Up @@ -309,6 +321,10 @@ export default Ember.Mixin.create({
},

_deleteInternalModel(internal) {
if(internal.transient) {
return this._rejectTransient('delete');
}

if(internal.state.isNew) {
return reject(new Error({ error: 'not_saved', reason: 'Model is not saved yet' }));
}
Expand Down
5 changes: 5 additions & 0 deletions addon/database/database-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export default Ember.Mixin.create({
return this._internalToModel(internal);
},

transient(modelName, modelId, props) {
let internal = this._transientInternalModelForModelName(modelName, modelId, props);
return this._internalToModel(internal);
},

load(modelName, id, opts) {
if(modelName === null) {
return this._loadInternalModelForDocId(id, opts).then(internal => {
Expand Down
8 changes: 7 additions & 1 deletion addon/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import {

const hasOne = belongsTo;

import {
transient
} from './computed';

export {
Error,

Expand Down Expand Up @@ -55,7 +59,9 @@ export {
attr,
belongsTo,
hasOne,
hasMany
hasMany,

transient
};

export default Store;
7 changes: 6 additions & 1 deletion addon/internal-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ export function internalModelDidSetDatabase(internal, props) {

export default class InternalModel {

constructor(store, modelClass, database = null) {
constructor(store, modelClass, database=null, transient=false) {
Ember.assert('modelClass should be factory', !!modelClass.class);
this.store = store;
this.modelClass = modelClass;
this.definition = getDefinition(this.modelClass);
this.values = new EmptyObject();
this.raw = null;
this._database = database;
this._transient = transient;
this.model = null;
this.loadPromise = null;
this.boundNotifyPropertyChange = this.notifyPropertyChange.bind(this);
Expand All @@ -69,6 +70,10 @@ export default class InternalModel {
};
}

get transient() {
return this._transient;
}

get isNew() {
return this.state.isNew;
}
Expand Down
9 changes: 9 additions & 0 deletions addon/model-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,21 @@ export default class Definition {
return true;
}

didSerialize(internal, doc, type) {
if(type === 'shoebox') {
if(internal.transient) {
doc._transient = true;
}
}
}

serialize(internal, type='document') {
isOneOf('type', type, [ 'preview', 'document', 'shoebox' ]);
let doc = copy(internal.raw || {});
this.eachProperty(property => {
property.serialize(internal, doc, type);
});
this.didSerialize(internal, doc, type);
return doc;
}

Expand Down
14 changes: 13 additions & 1 deletion addon/store/store-internal-model.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Ember from 'ember';
import InternalModel from '../internal-model';

const {
merge
} = Ember;

export default Ember.Mixin.create({

_prepareInternalModel(internal, props) {
Expand All @@ -15,12 +19,20 @@ export default Ember.Mixin.create({
return this._prepareInternalModel(internal, props);
},

// { isNew: false, isLoaded: true, id }
// { isNew: false, isLoaded: false }, id
_createExistingInternalModel(modelClass, database, modelId) {
let internal = new InternalModel(this, modelClass, database);
this._prepareInternalModel(internal, { id: modelId });
internal.setState({ isNew: false, isDirty: false }, false);
return internal;
},

// { isNew: false, isLoaded: true, isDirty: false }, id, transient=true
_createTransientInternalModel(modelClass, database, modelId, props) {
let internal = new InternalModel(this, modelClass, database, true);
this._prepareInternalModel(internal, merge({ id: modelId }, props));
internal.setState({ isNew: false, isLoaded: true, isDirty: false }, false);
return internal;
}

});
6 changes: 3 additions & 3 deletions addon/store/store-shoebox.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default Ember.Mixin.create({
let result = [];
for(let identifier in databases) {
let database = databases[identifier];
result.push({ identifier, docs: database._createShoebox() });
result.push({ identifier, shoebox: database._createShoebox() });
}
return result;
},
Expand All @@ -20,11 +20,11 @@ export default Ember.Mixin.create({
if(!shoebox) {
return;
}
return A(shoebox).map(({ identifier, docs }) => {
return A(shoebox).map(({ identifier, shoebox }) => {
let database = this.database(identifier);
return {
identifier,
status: database._pushShoebox(docs)
status: database._pushShoebox(shoebox)
};
});
}
Expand Down
14 changes: 3 additions & 11 deletions tests/dummy/app/services/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,11 @@ import Ember from 'ember';
import { Store } from 'sofa';

const {
computed
computed: { reads }
} = Ember;

const root = () => {
return computed(function() {
return this.get('db.main').model('dummy-root', { id: 'singleton' });
}).readOnly();
};

const prop = key => {
return computed(function() {
return this.get('root').get(key);
}).readOnly();
return reads(`root.${key}`).readOnly();
};

export default Store.extend({
Expand All @@ -35,7 +27,7 @@ export default Store.extend({
}
},

root: root(),
root: reads('db.main.root'),

allModels: prop('all'),
dirtyModels: prop('dirty'),
Expand Down
4 changes: 3 additions & 1 deletion tests/dummy/app/sofa/databases/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Database } from 'sofa';
import { Database, transient } from 'sofa';

export default Database.extend({

root: transient('dummy-root', 'singleton')

});
Loading

0 comments on commit af1b96f

Please sign in to comment.