Models

Models are an optional feature in patio that can be extended to encapsulate, query, and associate tables.

When defining a model it is assumed that the database table already exists. So before defining a model you must create the table/s that the model requires to function including associations. To create a model you must connect to a database.

An example model definition flow.

  1. var comb = require("comb"),
  2. format = comb.string.format,
  3. patio = require("patio");
  4. patio.camelize = true;
  5. //if you want logging
  6. patio.configureLogging();
  7. //disconnect and error callback helpers
  8. var disconnect = patio.disconnect.bind(patio);
  9. var disconnectError = function(err) {
  10. patio.logError(err);
  11. patio.disconnect();
  12. };
  13. //create your DB
  14. var DB = patio.connect("mysql://test:testpass@localhost:3306/sandbox");
  15. //create an initial model. It cannot be used until it is synced.
  16. //To sync call User.sync or patio.syncModels is called
  17. var User = patio.addModel("user");
  18. var createSchema = function(){
  19. return DB.forceCreateTable("user", function(){
  20. this.primaryKey("id");
  21. this.firstName(String)
  22. this.lastName(String);
  23. this.password(String);
  24. this.dateOfBirth(Date);
  25. this.created(sql.TimeStamp);
  26. this.updated(sql.DateTime);
  27. });
  28. };
  29. //connect and create schema
  30. connectAndCreateSchema()
  31. .chain(function(){
  32. //sync the model so it can be used
  33. return patio.syncModels().chain(function(){
  34. var myUser = new User({
  35. firstName : "Bob",
  36. lastName : "Yukon",
  37. password : "password",
  38. dateOfBirth : new Date(1980, 8, 29)
  39. });
  40. //save the user
  41. return myUser.save().chain(function(user){
  42. console.log(format("%s %s's id is %d", user.firstName, user.lastName, user.id));
  43. });
  44. });
  45. }).chain(disconnect, disconnectError);

The flow for the above example is as follows:

Options

Models some options that allow for the customization of the way a model be haves when interacting with the database.

  1. patio.addModel("user", {
  2. static : {
  3. //override default
  4. typecastOnLoad : false
  5. }
  6. });
  1. patio.addModel("user", {
  2. static : {
  3. //override default
  4. typecastOnAssignment : false
  5. }
  6. });

So the following would not be typecasted.

  1. var myUser = new User();
  2. myUser.updated = new Date(2004, 1, 1, 12, 12, 12); //would not be auto converted to a patio.sql.DateTime
  3. myUser.updated = "2004-02-01 12:12:12" //would not be auto converted to a patio.sql.DateTime
  1. patio.addModel("user", {
  2. static : {
  3. //override default
  4. typecastEmptyStringToNull : false
  5. }
  6. });
  1. patio.addModel("user", {
  2. static : {
  3. //override default
  4. raiseOnTypecastError : false
  5. }
  6. });
  1. patio.addModel("user", {
  2. static : {
  3. //override default
  4. useTransactions : false
  5. }
  6. });
  1. patio.addModel("user", {
  2. static : {
  3. //override default
  4. identifierOutputMethod : "camelize"
  5. }
  6. });
  1. patio.addModel("user", {
  2. static : {
  3. //override default
  4. identifierInputMethod : "underscore"
  5. }
  6. });
  1. patio.addModel("user", {
  2. static : {
  3. //override default
  4. camelize : true
  5. }
  6. });
  1. patio.addModel("user", {
  2. static : {
  3. //override default
  4. underscore : true
  5. }
  6. });
  1. patio.addModel("user", {
  2. static : {
  3. //override default
  4. reloadOnSave : false
  5. }
  6. });
  1. patio.addModel("user", {
  2. static : {
  3. //override default
  4. reloadOnUpdate : false
  5. }
  6. });

Creating a model

To create a Model class to use within your code you use the patio.addModel method.

  1. var User = patio.addModel("user")
  2. //you must sync the model before using it
  3. User.sync().chain(function(User){
  4. var myUser = new User({
  5. firstName : "Bob",
  6. lastName : "Yukon",
  7. password : "password",
  8. dateOfBirth : new Date(1980, 8, 29)
  9. });
  10. return myUser.save().chain(function(){
  11. console.log(format("%s %s was created at %s", myUser.firstName, myUser.lastName, myUser.created.toString()));
  12. console.log(format("%s %s's id is %d", myUser.firstName, myUser.lastName, myUser.id));
  13. });
  14. }).chain(disconnect, disconnectError);

You may also use a dataset when adding a model. You might use this if you are using multiple databases. Or want to use a custom query as the base for a particular model.

  1. var DB1 = patio.createConnection("my://connection/string");
  2. var DB2 = patio.createConnection("my://connection/string2");
  3. //user table in db1
  4. var User1 = patio.addModel(DB1.from("user"));
  5. //user table in db2
  6. var User2 = patio.addModel(DB2.from("user"));
  7. patio.syncModels().chain(function(User1,User2){
  8. var myUser1 = new User1({
  9. firstName : "Bob1",
  10. lastName : "Yukon1",
  11. password : "password",
  12. dateOfBirth : new Date(1980, 8, 29)
  13. });
  14. var myUser2 = new User2({
  15. firstName : "Bob2",
  16. lastName : "Yukon2",
  17. password : "password",
  18. dateOfBirth : new Date(1980, 8, 29)
  19. });
  20. return comb.when(myUser1.save(), myUser2.save()).chain(function(saved){
  21. console.log(format("%s %s was created at %s", myUser1.firstName, myUser1.lastName, myUser1.created.toString()));
  22. console.log(format("%s %s's id is %d", myUser1.firstName, myUser1.lastName, myUser1.id));
  23. console.log(format("%s %s was created at %s", myUser2.firstName, myUser2.lastName,myUser2.created.toString()));
  24. console.log(format("%s %s's id is %d", myUser2.firstName, myUser2.lastName, myUser2.id));
  25. });
  26. });

Custom setters and getters

Setters

patio creates setters and getters for each column in the database if you want alter the value of a particular property before its set on the model you can use a custom setter.

For example if you wanted to ensure proper case and first and last name of a user:

  1. var User = patio.addModel("user", {
  2. instance : {
  3. _setFirstName : function(firstName){
  4. return firstName.charAt(0).toUpperCase() + firstName.substr(1);
  5. },
  6. _setLastName : function(lastName){
  7. return lastName.charAt(0).toUpperCase() + lastName.substr(1);
  8. }
  9. }
  10. });
  11. patio.syncModels().chain(function(User){
  12. var myUser = new User({
  13. firstName : "bob",
  14. lastName : "yukon"
  15. });
  16. console.log(myUser.firstName); //Bob
  17. console.log(myUser.lastName); //Yukon
  18. });

Getters

Custom getters can be used to change values returned from the database but not alter the value when persisting.

For example if you wanted to return a value as an array but persist as a string you could do the following.

  1. var User = patio.addModel("user", {
  2. instance : {
  3. _getRoles : function(roles){
  4. return roles.split(",");
  5. }
  6. }
  7. });
  8. patio.syncModels().chain(function(User){
  9. var myUser = new User({
  10. firstName : "bob",
  11. lastName : "yukon",
  12. roles : "admin,user,groupAdmin"
  13. });
  14. console.log(myUser.roles); //['admin', 'user','groupAdmin'];
  15. });

You can also use the getters/setters in tandem.

Lets take the getters example from before but use a setter also

  1. var User = patio.addModel("user", {
  2. instance : {
  3. _setRoles : function(roles){
  4. return roles.join(",");
  5. },
  6. _getRoles : function(roles){
  7. return roles.split(",");
  8. }
  9. }
  10. });
  11. patio.syncModels().chain(function(User){
  12. var myUser = new User({
  13. firstName : "bob",
  14. lastName : "yukon",
  15. roles : ["admin","user","groupAdmin"];
  16. });
  17. console.log(myUser.roles); //['admin', 'user','groupAdmin'];
  18. //INSERT INTO `user` (`first_name`, `last_name`, `roles`) VALUES ('bob', 'yukon', 'admin,user,groupAdmin')
  19. return myUser.save();
  20. });

Model hooks

Each model has the following hooks

  1. var User = patio.addModel("user", {
  2. pre:{
  3. "save":function(next){
  4. console.log("pre save!!!")
  5. next();
  6. },
  7. "remove" : function(next){
  8. console.log("pre remove!!!")
  9. next();
  10. }
  11. },
  12. post:{
  13. "save":function(next){
  14. console.log("post save!!!")
  15. next();
  16. },
  17. "remove" : function(next){
  18. console.log("post remove!!!")
  19. next();
  20. }
  21. },
  22. instance:{
  23. _setFirstName:function(firstName){
  24. return firstName.charAt(0).toUpperCase() + firstName.substr(1);
  25. },
  26. _setLastName:function(lastName){
  27. return lastName.charAt(0).toUpperCase() + lastName.substr(1);
  28. }
  29. }
  30. });

Using a model

If you define a model you can either use the Models constructor directly.

  1. //define the model
  2. var User = patio.addModel("user");
  3. patio.syncModels(function(err){
  4. if(err){
  5. console.log(err.stack);
  6. }else{
  7. var user = new User();
  8. }
  9. })

or you can use patio.getModel

  1. patio.addModel("user");
  2. patio.syncModels(function(err){
  3. if(err){
  4. console.log(err.stack);
  5. }else{
  6. var User = patio.getModel("user");
  7. var user = new User();
  8. }
  9. })

Mutli Database Models

If you are working with multiple databases and your model's table is not in the patio.defaultDatabase (the first database you connected to) then you will need to pass in the database the model's table is in.

  1. var DB1 = patio.createConnection("my://connection/string");
  2. var DB2 = patio.createConnection("my://connection/string2");
  3. //user table in db1
  4. var User1 = patio.addModel(DB1.from("user"));
  5. //user table in db2
  6. var User2 = patio.addModel(DB2.from("user"));
  7. patio.syncModels(function(err){
  8. if(err){
  9. console.log(err.stack);
  10. }else{
  11. var user1 = new User1(),
  12. user2 = new User2();
  13. }
  14. });

Creating

The static save can be used for saving a group of models at once. Note this method is not any more efficient than creating a model using new, just less verbose.

  1. var Student = patio.getModel("student");
  2. Student.save([
  3. {
  4. firstName:"Bob",
  5. lastName:"Yukon",
  6. gpa:3.689,
  7. classYear:"Senior"
  8. },
  9. {
  10. firstName:"Greg",
  11. lastName:"Horn",
  12. gpa:3.689,
  13. classYear:"Sophomore"
  14. },
  15. {
  16. firstName:"Sara",
  17. lastName:"Malloc",
  18. gpa:4.0,
  19. classYear:"Junior"
  20. },
  21. {
  22. firstName:"John",
  23. lastName:"Favre",
  24. gpa:2.867,
  25. classYear:"Junior"
  26. },
  27. {
  28. firstName:"Kim",
  29. lastName:"Bim",
  30. gpa:2.24,
  31. classYear:"Senior"
  32. },
  33. {
  34. firstName:"Alex",
  35. lastName:"Young",
  36. gpa:1.9,
  37. classYear:"Freshman"
  38. }
  39. ]).chain(function(users){
  40. //All users have been saved
  41. }, disconnectError);

When saving a group of models the save method will use a transaction unless the useTransactions property is set to false. You can manually override the useTransactions property by passing in an additional options parameter with a transaction value set to false.

  1. var Student = patio.getModel("student");
  2. Student.save([
  3. {
  4. firstName:"Bob",
  5. lastName:"Yukon",
  6. gpa:3.689,
  7. classYear:"Senior"
  8. },
  9. {
  10. firstName:"Greg",
  11. lastName:"Horn",
  12. gpa:3.689,
  13. classYear:"Sophomore"
  14. }
  15. ], {transaction : false}).chain(function(users){
  16. //work with the users
  17. });

If you have an instance of a model then you can use the save method on the instance of the model.

  1. var myUser = new User({
  2. firstName : "Bob",
  3. lastName : "Yukon",
  4. password : "password",
  5. dateOfBirth : new Date(1980, 8, 29)
  6. });
  7. //save the user
  8. myUser.save().chain(function(user){
  9. //the save is complete
  10. }, disconnectError);

You can also pass in values into the save method to set before saving.

  1. var myUser = new User();
  2. //save the user
  3. myUser.save({
  4. firstName : "Bob",
  5. lastName : "Yukon",
  6. password : "password",
  7. dateOfBirth : new Date(1980, 8, 29)
  8. }).chain(function(user){
  9. //the save is complete
  10. }, disconnectError);

You can also pass in an options object to override options such as using a transaction.

  1. var myUser = new User();
  2. //save the user
  3. myUser.save({
  4. firstName : "Bob",
  5. lastName : "Yukon",
  6. password : "password",
  7. dateOfBirth : new Date(1980, 8, 29)
  8. }, {transaction : false}).chain(function(user){
  9. //the save is complete
  10. }, disconnectError);

Reading

The Model contains static methods for all of the datasets methods listed in patio.Dataset.ACTION_METHODS as well as all the methods listed in patio.Dataset.QUERY_METHODS.

Some of the most commonly used methods are:

  1. User.forEach(function(user){
  2. console.log(user.firstName);
  3. });
  4. //you may also return the result of another query(or any promise) from a forEach block,
  5. //this will prevent the forEach's promise from resolving until all actions that occured in the block have
  6. //resolved.
  7. var Blog = patio.addModel("blog");
  8. User.forEach(funciton(user){
  9. //create a blog for each user
  10. return new Blog({userId : user.id}).save();
  11. }).chain(function(users){
  12. //all users and blogs have been saved
  13. }, disconnectError);
  1. User.map(function(user){
  2. return user.firstName;
  3. }).chain(function(names){
  4. console.log("User names are %s", names);
  5. }, disconnectError);
  1. User.all().chain(function(users){
  2. console.log(users.length);
  3. }, disconnectError);
  1. //find all users where first names begin with bo case insensitive
  2. User.filter({firstName : /^bo/i}).all().chain(function(){
  3. }, disconnectError);
  1. User.filter({id : 1}).one().chain(function(user){
  2. console.log("%d - %s %s", user.id, user.firstName, user.lastName);
  3. }, disconnectError);
  1. //SELECT * FROM user WHERE first_name = 'bob' ORDER BY last_name LIMIT 1
  2. User.filter({firstName : "bob"}).order("lastName").first().chain(function(user){
  3. console.log("%d - %s %s", user.id, user.firstName, user.lastName);
  4. }, disconnectError);
  1. //SELECT * FROM user WHERE first_name = 'bob' ORDER BY last_name DESC LIMIT 1
  2. User.filter({firstName : "bob"}).order("lastName").last().chain(function(user){
  3. console.log("%d - %s %s", user.id, user.firstName, user.lastName);
  4. }, disconnectError);
  1. User.isEmpty().chain(function(isEmpty){
  2. if(isEmpty){
  3. console.log("user table is empty");
  4. }else{
  5. console.log("user table is not empty");
  6. }
  7. }, disconnectError);

Updating

The static update can be used for updating a batch of models.

  1. //BEGIN
  2. //UPDATE `user` SET `password` = NULL
  3. //COMMIT
  4. User.update({password : null});

You can also pass in a query to limit the models that are updated. The filter can be anything that filter accepts.

  1. User.update({password : null}, function(){
  2. return this.lastAccessed.lte(comb.yearsAgo(1));
  3. });
  4. //same as
  5. User.update({password : null}, {lastAccess : {lte : comb.yearsAgo(1)}});

To prevent default transaction behavior you can pass in an additional transaction option

  1. User.update({password : null}, {lastAccess : {lte : comb.yearsAgo(1)}}, {transaction : false});

If you have an instance of a model and you want to update it you can use the update instance method.

  1. var updateUsers = User.forEach(function(user){
  2. //returning the promise from update will cause the forEach not to resolve
  3. //until all updates have completed
  4. return user.update({fullName : user.firstName + " " + user.lastName});
  5. });
  6. updateUsers.chain(function(){
  7. //updates finished
  8. });

as with save you can pass in an options object to prevent default behavior such as transactions.

  1. var updateUsers = User.forEach(function(user){
  2. //returning the promise from update will cause the forEach not to resolve
  3. //until all updates have completed
  4. return user.update({fullName : user.firstName + " " + user.lastName}, {transaction : false});
  5. });
  6. updateUsers.chain(function(){
  7. //updates finished
  8. });

Deleting

The static remove can be used for removing a batch of models. Note this method is not anymore efficient just a convenience.

  1. //remove all models
  2. User.remove();

To limit the models removed you can pass in a query. The filter can be anything that filter accepts.

  1. //remove models that start with m
  2. User.remove({lastName : {like : "m%"}});`

The default behavior of remove is to load each model and call remove on it. If you wish to just do a mass delete and not load each model you can pass in an additional options object with a key called load set to false. Note If you do this then the pre/post remove hooks will not be called.

  1. //mass remove models without loading them
  2. User.remove(null, {load : false});

To prevent the default transaction behavior pass in the transaction option.

  1. //removing models, not using a transaction
  2. User.remove(null, {transaction : false});

If you have an instance of a model and you want to remove it you can use the remove instance method.

  1. User.forEach(function(user){
  2. return user.remove();
  3. }).chain(function(){
  4. //removed
  5. });

To prevent the default transaction behavior pass in the transaction option

  1. User.forEach(function(user){
  2. return user.remove({transaction : false});
  3. }).chain(function(){
  4. //removed
  5. });

Documentation generated using coddoc.