Model class methods are functions built into the model itself that performs a particular task its instances (records). This is where you will find the familiar CRUD methods for performing database operations like .create, .update, ,destroy, .find, etc.
These are special class methods that are dynamically generated by Sails when you lift your app. We call them dynamic finders. They perform many of the same functions as the other class methods but you can call them directly on an attribute in your model. This was taken from Java conventions to help anyone who are coming over from the Java world.
This is another special type of class method. It stands for 'Publish, Subscribe' and thats just what they do. These methods play a big role in how Sails integrates and utilizes Socket.IO . They are used to subscribe clients to and publish messages about the creation, update, and destruction of models. If you want to build real-time functionality in Sails, these will come in handy.
Instance methods are functions built into model instances (records).
They perform tasks on their parent record. Imagine you have a small monkey named Timothy that rides on your shoulders and styles your hair ONLY when you are scheduled to speak at a conference. In this scenario, you are an instance of the model 'Javascript Expert', Timothy is your personal instance method and the function he performs is making sure your hair looks good for conferences.
In addition to useful ones like .save(), .destroy(), and .validate() , you can also define your own custom ones inside of your model.
If you would like to write your own instance methods, you will declare them inside of your model. For more information, see the guide on models
For most class methods, the callback parameter is optional and if one is not supplied, it will return a chainable object.
Creates a new instance of this model in the database.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Records to Create | {} , [{}] |
Yes |
2 | Callback | function |
No |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Records Created | {} , [{}] |
// create a new record with name 'Walter Jr'
User.create({name:'Walter Jr'}).exec(function createCB(err,created){
console.log('Created user with name '+created.name);
});
// Created user with name Walter Jr
// Don't forget to handle your errors and abide by the rules you defined in your model
Updates existing record in the database that match the given criteria.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} ,[{}] , string , int |
Yes |
2 | Updated Records | {} ,[{}] |
Yes |
3 | Callback | function |
No |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Sucessfully Updated Records | [{}] |
User.update({name:'Walter Jr'},{name:'Flynn'}).exec(function updateCB(err,updated){
console.log('Updated user to have name '+updated[0].name);
});
// Updated user to have name Flynn
// Don't forget to handle your errors and abide by the rules you defined in your model
Although you may pass .update() an object or an array of objects, it will always return an array of objects. Any string arguments passed must be the ID of the record.
If you specify a primary key (e.g.
7
or50c9b254b07e040200000028
) instead of a criteria object, any.where()
filters will be ignored.
Destroys all record in your database that matches the given criteria.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} ,[{}] , string , int |
Yes |
2 | Callback | function |
No |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
User.destroy({name:'Flynn'}).exec(function deleteCB(err){
console.log('The record has been deleted');
});
// If the record existed, then it has been deleted
// Don't forget to handle your errors
If you want to confirm the record exists before you delete it, you must first perform a find() Any string arguments passed must be the ID of the record.
Checks for the existence of the record in the first parameter. If it can't be found, the record in the second parameter is created.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} ,[{}] , string , int |
Yes |
2 | Records to Create | {} ,[{}] |
Yes |
2 | Callback | function |
No |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Records Created | {} , [{}] |
User.findOrCreate({name:'Walter'},{name:'Jessie'}).exec(function createFindCB(err,record){
console.log('What\'s cookin\' '+record.name+'?');
});
// What's cookin' Jessie?
// Don't forget to handle your errors and abide by the rules you defined in your model
Any string arguments passed must be the ID of the record. If you are trying to find an attribute that is an array, you must wrap it in an additional set of brackets otherwise Waterline will think you want to perform an inQuery.
This finds and returns a single record that meets the criteria.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} , string |
Yes |
2 | Callback | function |
Yes |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Found Record | {} |
User.findOne({name:'Jessie'}).exec(function findOneCB(err,found){
console.log('We found '+found.name);
});
// We found Jessie
// Don't forget to handle your errors
Any string arguments passed must be the ID of the record. If you are trying to find an attribute that is an array, you must wrap it in an additional set of brackets otherwise Waterline will think you want to perform an inQuery.
Finds and returns all records that meet the criteria object(s) that you pass it.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} ,[{}] , string , int |
Yes |
2 | Callback | function |
Yes |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Found Records | [{}] |
User.find({}).exec(function findCB(err,found){
while (found.length)
console.log('Found User with name '+found.pop().name)
});
// Found User with name Flynn
// Found User with name Jessie
// Don't forget to handle your errors
Any string arguments passed must be the ID of the record. This method will ALWAYS return records in an array. If you are trying to find an attribute that is an array, you must wrap it in an additional set of brackets otherwise Waterline will think you want to perform an inQuery.
Returns the number of records in your database that meet the given search criteria.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} ,[{}] , string , int |
Yes |
2 | Callback | function |
No |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Number of Records | int |
User.count({name:'Flynn'}).exec(function countCB(err,found){
console.log('There are '+found+' users called \'Flynn\'');
});
// There are 1 users called 'Flynn'
// Don't forget to handle your errors
Any string arguments passed must be the ID of the record.
This method uses a node write stream to pipe model data as it is retrieved without first having to buffer all of the results to memory.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} ,[{}] , string , int |
Yes |
2 | Custom Write/End Methods | {} |
No |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Stream of Records | stream |
UsersController.js
module.exports = {
testStream: function(req,res){
if (req.param('startStream') && req.isSocket){
var getSocket = req.socket;
// Start the stream. Pipe it to sockets.
User.stream({name:'Walter'}).pipe(getSocket.emit);
} else {
res.view();
}
}
}
views/users/testSocket.ejs
<style>.addButton{display:inline-block;line-height:100px;width:400px;height:100px;border:1px solid black;cursor:pointer;}</style>
<script>
window.onload = function startListening(){
socket.on('gotUser',function(data){
console.log(data.name+' number '+data.id+' has joined the party');
});
};
</script>
<center>
<div class="addButton" onClick="socket.get('/users/testStream/',{startStream:true})">
Stream all the Users ! </div>
This method is useful for piping data from VERY large models straight to res. You can also pipe it other places. See the node stream docs for more info. Only the mongo, mysql, and posgresql adapters support this method. This won't work with the disk adapter. Any string arguments passed must be the ID of the record.
Returns an instance of the specified collection for performing raw Mongo queries.
This method only works with Mongo! use .query() for PostgreSQL and mySQL.
Performs raw SQL queries for PostgreSQL and mySQL.
This method only works with PostgreSQL and mySQL! use .native() for Mongo.
Find and return records by a specific model attribute.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} ,[{}] , string , int |
Yes |
2 | Callback | function |
No |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Found Records | [{}] |
User.findByName(['Flynn','Walter','craig']).exec(function findCB(err,found){
while (found.length)
console.log('Found User with name '+found.pop().name);
});
// Found User with name Walter
// Found User with name Flynn
// Don't forget to handle your errors
Any string arguments passed must be the ID of the record. If you are trying to find an attribute that is an array, you must wrap it in an additional set of brackets otherwise Waterline will think you want to perform an inQuery.
Find and return one record by a specific model attribute.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} ,[{}] , string , int |
Yes |
2 | Callback | function |
No |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Found Record | {} |
User.findOneByName('Walter').exec(function findCB(err,found){
console.log('Found User with name '+found.name);
});
// Found User with name Walter
// Don't forget to handle your errors
This will always return a single object. Any string arguments passed must be the ID of the record.
Count the number of records in a model with a particular model attribute.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} ,[{}] , string , int |
Yes |
2 | Callback | function |
No |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Number of Records | int |
User.countByName('Walter').exec(function countCB(err,found){
console.log('There are '+found+' users called \'Walter\'');
});
// There are 1 users called 'Walter'
// Don't forget to handle your errors
The value returned will be equal to the sum of the products of all matched criteria objects and the number of records that particular object matched. SUM [ matchedObjects * RecordsMatchedByObject ] Any string arguments passed must be the ID of the record.
Find records based on the starting letters of one of its attributes value.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} ,[{}] , string , int |
Yes |
2 | Callback | function |
Yes |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Found Records | {} , [{}] |
User.nameStartsWith('W', function startsWithCB(err,found){
while (found.length)
console.log('User '+found.pop().name+' has name that starts with \'W\'');
});
// User Walter has name that starts with 'W'
// Don't forget to handle your errors
Warning! Your attribute in the method name must be lowerCase! Warning! .exec() DOES NOT work on this method. You MUST supply a callback. Any string arguments passed must be the ID of the record.
Find records based on the last letters of one of its attributes value.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Find Criteria | {} ,[{}] , string , int |
Yes |
2 | Callback | function |
Yes |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Found Records | {} , [{}] |
User.nameEndsWith('sie', function endsWithCB(err,found){
console.log('User '+found[0].name+' has name that ends with \'sie\'');
});
// User Jessie has name that ends with 'sie'
// Don't forget to handle your errors
Warning! Your attribute in the method name must be lowerCase! Warning! .exec() DOES NOT work on this method. You MUST supply a callback. Any string arguments passed must be the ID of the record.
PublishCreate doesn't actually create anything. It simply publishes information about the creation of a model instance via websockets.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Data to Send | {} |
No |
2 | Socket to Omit | SocketIO Socket |
No |
UsersController.js
module.exports = {
testSocket: function(req,res){
var nameSent = req.param('name');
if (nameSent && req.isSocket){
User.create({name:nameSent}).exec(function created(err,newGuy){
User.publishCreate({id:newGuy.id,name:newGuy.name});
console.log('A new user called '+newGuy.name+' has been created');
});
} else if (req.isSocket){
User.subscribe(req.socket);
console.log('User with socket id '+req.socket.id+' is now subscribed to the model class \'users\'.');
} else {
res.view();
}
}
}
// Don't forget to handle your errors
views/users/testSocket.ejs
<style>.addButton{display:inline-block;line-height:100px;width:400px;height:100px;border:1px solid black;cursor:pointer;}</style>
<script>
window.onload = function subscribeAndListen(){
// When the document loads, send a request to users.testSocket
// The controller code will subscribe you to the model 'users'
socket.get('/users/testSocket/');
// Listen for the event called 'message' emited by the publishCreate() method.
socket.on('message',function(obj){
data = obj.data;
console.log('User '+data.name+' has been created.');
});
};
function makeNew(){
// Send the new users name to the 'testSocket' action on the 'users' contoller
socket.get('/users/testSocket/',{name:'Walter'});
}
</script>
<center>
<div class="addButton" onClick="makeNew()">
Click Me to add a new 'Walter' ! </div>
The client's socket must have first been subscribed using the .subscribe({}) method. Published objects can be accessed via the data key on the socket callback parameter.
PublishUpdate updates nothing. It publishes information about the update of a model instance via websockets.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | ID of Updated Record | int , string |
Yes |
2 | Data to Send | {} |
No |
3 | Socket to Omit | SocketIO Socket |
No |
UsersController.js
module.exports = {
testSocket: function(req,res){
var nameSent = req.param('name');
if (nameSent && req.isSocket){
User.update({name:nameSent},{name:'Heisenberg'}).exec(function update(err,updated){
User.publishUpdate(updated[0].id,{ name:updated[0].name });
});
} else if (req.isSocket){
User.find({}).exec(function(e,listOfUsers){
User.subscribe(req.socket,listOfUsers);
console.log('User with socket id '+req.socket.id+' is now subscribed to all of the model instances in \'users\'.');
});
} else {
res.view();
}
}
}
// Don't forget to handle your errors
views/users/testSocket.ejs
<style>.addButton{display:inline-block;line-height:100px;width:400px;height:100px;border:1px solid black;cursor:pointer;}</style>
<script>
window.onload = function subscribeAndListen(){
// When the document loads, send a request to users.testSocket
// The controller code will subscribe you to all of the 'users' model instances (records)
socket.get('/users/testSocket/');
// Listen for the event called 'message' emited by the publishUpdate() method.
socket.on('message',function(obj){
data = obj.data;
console.log('User '+data.name+' has been '+obj.verb+'ed .');
});
};
function doEdit(){
// Send the name to the testSocket action on the 'Users' contoller
socket.get('/users/testSocket/',{name:'Walter'});
}
</script>
<center>
<div class="addButton" onClick="doEdit()">
Click Me to add a new User! </div>
The client's socket must have first been subscribed using the .subscribe({},[]) method. Any string arguments passed must be the ID of the record.
Publish the destruction of a model
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | ID of Destroyed Record | int , string |
Yes |
2 | Socket to Omit | SocketIO Socket |
No |
UsersController.js
module.exports = {
testSocket: function(req,res){
var nameSent = req.param('name');
if (nameSent && req.isSocket){
User.findOne({name:nameSent}).exec(function findIt(err,foundHim){
User.destroy({id:foundHim.id}).exec(function destroy(err){
User.publishDestroy(foundHim.id);
});
});
} else if (req.isSocket){
User.find({}).exec(function(e,listOfUsers){
User.subscribe(req.socket,listOfUsers);
console.log('User with socket id '+req.socket.id+' is now subscribed to all of the model instances in \'users\'.');
});
} else {
res.view();
}
}
}
// Don't forget to handle your errors
views/users/testSocket.ejs
<style>.addButton{display:inline-block;line-height:100px;width:400px;height:100px;border:1px solid black;cursor:pointer;}</style>
<script>
window.onload = function subscribeAndListen(){
// When the document loads, send a request to users.testSocket
// The controller code will subscribe you to all of the 'users' model instances (records)
socket.get('/users/testSocket/');
// Listen for the event called 'message' emited by the publishDestroy() method.
socket.on('message',function(obj){
console.log('User with ID '+obj.id+' has been '+obj.verb+'ed .');
});
};
function destroy(){
// Send the name to the testSocket action on the 'Users' contoller
socket.get('/users/testSocket/',{name:'Walter'});
}
</script>
<center>
<div class="addButton" onClick="destroy()">
Click Me to destroy user 'Walter' ! </div>
Any string arguments passed must be the ID of the record.
This is 1 of the 2 subscribe methods. It will subscribe clients to the model class. This allows clients to see message emitted by .publishCreate() only.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Requesting Socket | Socket.IO Socket |
Yes |
2 | Socket to Omit | SocketIO Socket |
No |
Controller Code
User.subscribe(req.socket);
console.log('User with socket id '+req.socket.id+' is now subscribed to the model class \'users\'.');
#.subscribe(socket
,[recordIDs
],[socket
])
This subscribes clients to existing model instances (records). It allows clients to see message emitted by .publishUpdate() and .publishDestroy() only.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Requesting Socket | Socket.IO socket |
Yes |
2 | Record IDs | [] , string , int |
No |
3 | Socket to Omit | SocketIO Socket |
No |
Controller Code
User.find({}).exec(function(e,listOfUsers){
User.subscribe(req.socket,listOfUsers);
console.log('User with socket id '+req.socket.id+' is now subscribed to all of the model instances in \'users\'.');
});
// Don't forget to handle your errors
The record IDs are not required but do not pass an empty array or the method will fail. This method will be deprecated in an upcoming release. Subscriptions should be called from the request object or socket themselves, not from the model. Any string arguments passed must be the ID of the record.
This is 1 of 2 unsubscribe methods. This will ONLY unsubscribe a socket from a particular model class.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Requesting Socket | Socket.IO Socket |
Yes |
2 | Socket to Omit | SocketIO Socket |
No |
Controller Code
User.unsubscribe(req.socket, [] );
Most of the time you shouldn't use this since sessions are destroyed when the client closes their tab Any string arguments passed must be the ID of the record.
This method will unsubscribe a socket from the model instances (records) who's IDs are supplied in the array.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Requesting Socket | Socket.IO Socket |
Yes |
2 | Record IDs | [] , string , int |
No |
3 | Socket to Omit | SocketIO Socket |
No |
Controller Code
User.unsubscribe(req.socket,[1,2,3,4,5,6]);
Most of the time you shouldn't use this since sessions are destroyed when the client closes their tab The record IDs are not required but do not pass an empty array or the method will fail. Any string arguments passed must be the ID of the record.
The save
method updates your record in the database using the current attributes. It then returns the newly saved object in the callback.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Callback | function |
Yes |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Saved Record | { } |
User.find().exec(
function(err,myRecords){
// Grab a record off the top of the returned array and save a new attribute to it
var getOneRecord = myRecords.pop();
getOneRecord.name = 'Hank';
getOneRecord.save(
function(err,s){
console.log('User with ID '+s.id+' now has name '+s.name);
});
});
// User with ID 1 now has name Hank
// Don't forget to handle your errors.
// Don't forget to abide by the rules you set in your model
This is an instance method. Currently, instance methods ARE NOT TRANSACTIONAL. Because of this, it is recommended that you use the equivalent model method instead.
Destroys the your record in the database. It returns an error in the callback if it has trouble.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Callback | function |
Yes |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
User.find().exec(
function(err,myRecords){
// Grab a record off the top of the returned array then destroy it
var getOneRecord = myRecords.pop();
getOneRecord.destroy(
function(err){
console.log('User with ID '+getOneRecord.id+' was destroyed');
});
});
// User with ID 1 was destroyed
// Don't forget to handle your errors.
This is an instance method. Currently, instance methods ARE NOT TRANSACTIONAL. Because of this, it is recommended that you use the equivalent model method instead.
Checks the current keys/values on the record against the validations specified in the attributes object of your model.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Callback | function |
Yes |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
User.find().exec(
function(err,myRecords){
// Grab a record off the top, change it to the wrong data type, then try to validate
var getOneRecord = myRecords.pop();
getOneRecord.name = ['Marie','Hank'];
getOneRecord.name.validate(
function(err){
if (err)
console.log(JSON.stringify(err));
});
});
// {"ValidationError":{"name":[{"data":["Marie","Hank"],"message":"Validation error: \"Marie,Hank\" is not of type \"string\"","rule":"string"}]}}
For model
module.exports = {
attributes: {
name: 'string'
}
};
This is shorthand for
Model.validate({ attributes }, cb)
If you.save()
without first validating, waterline tries to convert. If it cant, it will throw an error. In this case, it would have converted the array to the string 'Marie,Hank'
There will be no parameters in the callback unless there is an error. No news is good news.
Note, This method is not asynchronous
The toObject method returns a cloned model instance (record) but stripped of all instance methods.
Description | Possible Data Types | |
---|---|---|
Cloned Record | { } |
See usage in .toJSON()
You will only want to use .toObject when overriding the default .toJSON instance method.
This method also returns a cloned model instance. This one however includes all instance methods. Be sure to read the notes on this one.
Description | Possible Data Types | |
---|---|---|
Cloned Record | { } |
User.find().exec(
function(err,myRecord){
var datUser = myRecord.pop().toObject();
console.log(datUser);
});
/* { id: 2,
createdAt: '2013-10-31T22:42:25.459Z',
updatedAt: '2013-11-01T20:12:55.534Z',
name: 'Hank',
phoneNumber: '101-150-1337' } */
User.find().exec(
function(err,myRecord){
var datUser = myRecord.pop().toJSON();
console.log(datUser);
});
/* { id: 2,
createdAt: '2013-10-31T22:42:25.459Z',
updatedAt: '2013-11-01T20:12:55.534Z',
name: 'Hank' } */
// Don't forget to handle your errors
For model
module.exports = {
attributes: {
name: 'string',
phoneNumber: 'string',
// Override the default toJSON method
toJSON: function() {
var obj = this.toObject();
delete obj.phoneNumber;
return obj;
}
}
}
The real power of toJSON relies on the fact every model instance sent out via res.json is first passed through toJSON. Instead of writing custom code for every controller action that uses a particular model (including the "out of the box" blueprints), you can manipulate outgoing records by simply overriding the default toJSON function in your model.
You would use this to keep private data like email addresses and passwords from being sent back to every client.
Note, This method is not asynchronous
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Criteria Object | {} |
Yes |
var myQuery = User.find();
myQuery.where({'name':{startsWith:'W'}});
myQuery.exec(function callBack(err,results){
console.log(results)
});
The .find() method returns a chainable object if you don't supply a callback. This method can be chained to .find() to further filter your results.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Number to Return | int |
Yes |
var myQuery = User.find();
myQuery.limit(12);
myQuery.exec(function callBack(err,results){
console.log(results)
});
The .find() method returns a chainable object if you don't supply a callback. This method can be chained to .find() to further filter your results.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Number to Skip | int |
Yes |
var myQuery = User.find();
myQuery.skip(12);
myQuery.exec(function callBack(err,results){
console.log(results)
});
The .find() method returns a chainable object if you don't supply a callback. This method can be chained to .find() to further filter your results.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Sort String | string |
Yes |
var myQuery = User.find();
var sortString= 'name ASC';
// Sort strings look like this
// '<Model Attribute> <sort type>'
myQuery.sort('name ASC');
myQuery.exec(function callBack(err,results){
console.log(results)
});
The .find() method returns a chainable object if you don't supply a callback. This method can be chained to .find() to further filter your results.
Other Sort Types include
- ASC
- DES
This is run at the end of a chain of stringable methods. It signals the adapter to run the query.
Description | Accepted Data Types | Required ? | |
---|---|---|---|
1 | Callback | function |
Yes |
Description | Possible Data Types | |
---|---|---|
1 | Error | Error |
2 | Data Returned | {} , [{}] , int |
// refer to any of the examples above
The .find() method returns a chainable object if you don't supply a callback. This method can be chained to .find() to further filter your results.
If you dont run .exec() , your query will not execute.