Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.9 #272

Closed
wants to merge 44 commits into from
Closed

1.9 #272

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
4be3395
Add Change model
ritch Jan 26, 2014
1a13a8d
Add Checkpoint model and Model replication methods
ritch Jan 26, 2014
2582c3f
Add replication example
ritch Jan 28, 2014
a0e595d
Add model tests
ritch Jan 28, 2014
cc49d67
Add Change model
ritch Jan 26, 2014
e3d8005
Add Checkpoint model and Model replication methods
ritch Jan 26, 2014
ab8d1ae
Add replication example
ritch Jan 28, 2014
13f6738
Add model tests
ritch Jan 28, 2014
0bf7af1
fixup! rename Change.track => rectifyModelChanges
ritch Feb 5, 2014
867e3ca
fixup! Assert model exists
ritch Feb 5, 2014
e86a00d
Merge branch master
ritch Apr 15, 2014
b660f8a
Add replication e2e tests
ritch Apr 16, 2014
83e74ab
Merge
ritch Apr 16, 2014
e35309b
Fixes for e2e replication / remote connector tests
ritch Apr 30, 2014
69cd6a8
!fixup .replicate() argument handling
ritch Apr 30, 2014
918596f
Refactor DataModel remoting
ritch May 3, 2014
1c7527a
Add missing test/model file
ritch May 3, 2014
012f880
!fixup RemoteConnector tests
ritch May 3, 2014
e026a7f
fixup! unskip failing tests
ritch May 3, 2014
ae2fb9d
!fixup use DataModel instead of Model for all data based models
ritch May 3, 2014
f8b5fa1
All tests passing
ritch May 3, 2014
a3a6828
Move replication implementation to DataModel
ritch May 6, 2014
5bf1f76
!fixup Test cleanup
ritch May 7, 2014
9082214
Fix issues when using MongoDB for replication
May 10, 2014
4bab424
Add error logging for missing data
ritch May 12, 2014
1e2ad9f
Change#getModel(), Doc cleanup, Conflict event
ritch May 14, 2014
64ea805
Merge branch 'master' into feature/replication
ritch May 14, 2014
344601c
bump juggler version
ritch May 14, 2014
d875c51
Rework replication test
ritch May 16, 2014
2f21f4e
Rework replication test
ritch May 16, 2014
d58df51
Merge branch 'feature/replication' of github.com:strongloop/loopback …
ritch May 16, 2014
4d5e788
Add test for conflicts where both deleted
ritch May 16, 2014
558ea60
In progress: rework remoting meta-data
ritch May 16, 2014
eec7bdd
- Use the RemoteObjects class to find remote objects instead of
ritch May 19, 2014
2de33d4
Rework change conflict detection
ritch May 20, 2014
52eb72d
Remove un-rectify-able changes
ritch May 20, 2014
77bd77e
Ensure changes are created in sync
ritch May 20, 2014
1a8ba60
!fixup Mark DAO methods as delegate
ritch May 20, 2014
0c925f7
Depend on [email protected]
ritch May 20, 2014
d237ae5
Merge latest from master
ritch May 20, 2014
7a7f868
Add RC version
ritch May 20, 2014
22670c7
Merge in 1.8.3
ritch May 21, 2014
90bdc73
Fix callback reference
ritch May 21, 2014
1603a3e
Cleanup CHANGES.md
ritch May 21, 2014
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
*.swp
*.swo
node_modules
dist
29 changes: 29 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Breaking Changes

# 1.9

## Remote Method API

`loopback.remoteMethod()` is now deprecated.

Defining remote methods now should be done like this:

```js
// static
MyModel.greet = function(msg, cb) {
cb(null, 'greetings... ' + msg);
}

MyModel.remoteMethod(
'greet',
{
accepts: [{arg: 'msg', type: 'string'}],
returns: {arg: 'greeting', type: 'string'}
}
);
```

**NOTE: remote instance method support is also now deprecated...
Use static methods instead. If you absolutely need it you can still set
`options.isStatic = false`** We plan to drop support for instance methods in
`2.0`.
3 changes: 2 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ module.exports = function(grunt) {

// list of files / patterns to load in the browser
files: [
'test/e2e/remote-connector.e2e.js'
'test/e2e/remote-connector.e2e.js',
'test/e2e/replication.e2e.js'
],

// list of files to exclude
Expand Down
3 changes: 3 additions & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"lib/models/data-model.js",
"lib/models/role.js",
"lib/models/user.js",
"lib/models/change.js",
"docs/api-datasource.md",
"docs/api-geopoint.md",
"docs/api-model.md",
"docs/api-model-remote.md"
],
Expand Down
138 changes: 138 additions & 0 deletions example/replication/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
var loopback = require('../../');
var app = loopback();
var db = app.dataSource('db', {connector: loopback.Memory});
var Color = app.model('color', {dataSource: 'db', options: {trackChanges: true}});
var Color2 = app.model('color2', {dataSource: 'db', options: {trackChanges: true}});
var target = Color2;
var source = Color;
var SPEED = process.env.SPEED || 100;
var conflicts;

var steps = [

createSomeInitialSourceData,

replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data'),

updateSomeTargetData,

replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data '),
list.bind(this, target, 'current TARGET data (includes conflicting update)'),

updateSomeSourceDataCausingAConflict,

replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data (now has a conflict)'),
list.bind(this, target, 'current TARGET data (includes conflicting update)'),

resolveAllConflicts,

replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data (conflict resolved)'),
list.bind(this, target, 'current TARGET data (conflict resolved)'),

createMoreSourceData,

replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data'),

createEvenMoreSourceData,

replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data'),

deleteAllSourceData,

replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data (empty)'),
list.bind(this, target, 'current TARGET data (empty)'),

createSomeNewSourceData,

replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data')
];

run(steps);

function createSomeInitialSourceData() {
Color.create([
{name: 'red'},
{name: 'blue'},
{name: 'green'}
]);
}

function replicateSourceToTarget() {
Color.replicate(0, Color2, {}, function(err, replicationConflicts) {
conflicts = replicationConflicts;
});
}

function resolveAllConflicts() {
if(conflicts.length) {
conflicts.forEach(function(conflict) {
conflict.resolve();
});
}
}

function updateSomeTargetData() {
Color2.findById(1, function(err, color) {
color.name = 'conflict';
color.save();
});
}

function createMoreSourceData() {
Color.create({name: 'orange'});
}

function createEvenMoreSourceData() {
Color.create({name: 'black'});
}

function updateSomeSourceDataCausingAConflict() {
Color.findById(1, function(err, color) {
color.name = 'red!!!!';
color.save();
});
}

function deleteAllSourceData() {
Color.destroyAll();
}

function createSomeNewSourceData() {
Color.create([
{name: 'violet'},
{name: 'amber'},
{name: 'olive'}
]);
}

function list(model, msg) {
console.log(msg);
model.find(function(err, items) {
items.forEach(function(item) {
console.log(' -', item.name);
});
console.log();
});
}

function run(steps) {
setInterval(function() {
var step = steps.shift();
if(step) {
console.log(step.name);
step();
}
}, SPEED);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sample app hangs after the last step was executed.

I'd say the interval should be cleared when there are no more steps to run.

}
15 changes: 6 additions & 9 deletions lib/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ app.model = function (Model, config) {
assert(typeof Model === 'function', 'app.model(Model) => Model must be a function / constructor');
assert(Model.modelName, 'Model must have a "modelName" property');
var remotingClassName = compat.getClassNameForRemoting(Model);
this.remotes().exports[remotingClassName] = Model;
if(Model.sharedClass) {
this.remotes().addClass(Model.sharedClass);
}
this.models().push(Model);
clearHandlerCache(this);
Model.shared = true;
Expand Down Expand Up @@ -205,14 +207,9 @@ app.dataSource = function (name, config) {

app.remoteObjects = function () {
var result = {};
var models = this.models();

// add in models
models.forEach(function (ModelCtor) {
// only add shared models
if(ModelCtor.shared && typeof ModelCtor.sharedCtor === 'function') {
result[compat.getClassNameForRemoting(ModelCtor)] = ModelCtor;
}

this.remotes().classes().forEach(function(sharedClass) {
result[sharedClass.name] = sharedClass.ctor;
});

return result;
Expand Down
2 changes: 1 addition & 1 deletion lib/connectors/base-connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ Connector._createJDBAdapter = function (jdbModule) {

Connector.prototype._addCrudOperationsFromJDBAdapter = function (connector) {

}
}
2 changes: 1 addition & 1 deletion lib/connectors/memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ inherits(Memory, Connector);
* JugglingDB Compatibility
*/

Memory.initialize = JdbMemory.initialize;
Memory.initialize = JdbMemory.initialize;
65 changes: 44 additions & 21 deletions lib/connectors/remote.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
* Dependencies.
*/

var assert = require('assert')
, compat = require('../compat')
, _ = require('underscore');
var assert = require('assert');
var remoting = require('strong-remoting');
var compat = require('../compat');
var DataAccessObject = require('loopback-datasource-juggler/lib/dao');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the idea of depending on the internal layout of the juggler module. If DataAccessObject is a part of the public API, then it should be exposed in module's index.js. The code here then becomes:

var DataAccessObject = require('loopback-datasource-juggler').DataAccessObject;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, DataAccessObject is not used anywhere in the file. You can simply remove this line.


/**
* Export the RemoteConnector class.
Expand All @@ -24,21 +25,25 @@ function RemoteConnector(settings) {
this.root = settings.root || '';
this.host = settings.host || 'localhost';
this.port = settings.port || 3000;
this.remotes = remoting.create();

// TODO(ritch) make sure this name works with Model.getSourceId()
this.name = 'remote-connector';

if(settings.url) {
this.url = settings.url;
} else {
this.url = this.protocol + '://' + this.host + ':' + this.port + this.root;
}

// handle mixins here
this.DataAccessObject = function() {};
// handle mixins in the define() method
var DAO = this.DataAccessObject = function() {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable DAO?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this.DataAccessObject actually used?

}

RemoteConnector.prototype.connect = function() {
this.remotes.connect(this.url, this.adapter);
}


RemoteConnector.initialize = function(dataSource, callback) {
var connector = dataSource.connector = new RemoteConnector(dataSource.settings);
connector.connect();
Expand All @@ -47,22 +52,40 @@ RemoteConnector.initialize = function(dataSource, callback) {

RemoteConnector.prototype.define = function(definition) {
var Model = definition.model;
var className = compat.getClassNameForRemoting(Model);
var url = this.url;
var adapter = this.adapter;

Model.remotes(function(err, remotes) {
var sharedClass = getSharedClass(remotes, className);
remotes.connect(url, adapter);
sharedClass
.methods()
.forEach(Model.createProxyMethod.bind(Model));
});
var remotes = this.remotes;
var SharedClass;

assert(Model.sharedClass, 'cannot attach ' + Model.modelName
+ ' to a remote connector without a Model.sharedClass');

remotes.addClass(Model.sharedClass);

Model
.sharedClass
.methods()
.forEach(function(remoteMethod) {
// TODO(ritch) more elegant way of ignoring a nested shared class
if(remoteMethod.name !== 'Change'
&& remoteMethod.name !== 'Checkpoint') {
createProxyMethod(Model, remotes, remoteMethod);
}
});
}

function getSharedClass(remotes, className) {
return _.find(remotes.classes(), function(sharedClass) {
return sharedClass.name === className;
});
function createProxyMethod(Model, remotes, remoteMethod) {
var scope = remoteMethod.isStatic ? Model : Model.prototype;
var original = scope[remoteMethod.name];

scope[remoteMethod.name] = function remoteMethodProxy() {
var args = Array.prototype.slice.call(arguments);
var lastArgIsFunc = typeof args[args.length - 1] === 'function';
var callback;
if(lastArgIsFunc) {
callback = args.pop();
}

remotes.invoke(remoteMethod.stringName, args, callback);
}
}

function noop() {}
3 changes: 2 additions & 1 deletion lib/loopback.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ loopback.createModel = function (name, properties, options) {
BaseModel = loopback.getModel(BaseModel);
}

BaseModel = BaseModel || loopback.Model;
BaseModel = BaseModel || loopback.DataModel;

var model = BaseModel.extend(name, properties, options);

Expand Down Expand Up @@ -320,6 +320,7 @@ loopback.Role = require('./models/role').Role;
loopback.RoleMapping = require('./models/role').RoleMapping;
loopback.ACL = require('./models/acl').ACL;
loopback.Scope = require('./models/acl').Scope;
loopback.Change = require('./models/change');

/*!
* Automatically attach these models to dataSources
Expand Down
2 changes: 1 addition & 1 deletion lib/models/acl.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ var ACLSchema = {
* @inherits Model
*/

var ACL = loopback.createModel('ACL', ACLSchema);
var ACL = loopback.DataModel.extend('ACL', ACLSchema);

ACL.ALL = AccessContext.ALL;

Expand Down
2 changes: 1 addition & 1 deletion lib/models/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ function generateKey(hmacKey, algorithm, encoding) {
* @inherits {Model}
*/

var Application = loopback.createModel('Application', ApplicationSchema);
var Application = loopback.DataModel.extend('Application', ApplicationSchema);

/*!
* A hook to generate keys before creation
Expand Down
Loading