UNPKG

patio

Version:
766 lines (622 loc) 21.8 kB
# 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](./patio.html#connect) to a database. An example model definition flow. ``` var comb = require("comb"), format = comb.string.format, patio = require("patio"); patio.camelize = true; //if you want logging patio.configureLogging(); //disconnect and error callback helpers var disconnect = patio.disconnect.bind(patio); var disconnectError = function(err) { patio.logError(err); patio.disconnect(); }; //create your DB var DB = patio.connect("mysql://test:testpass@localhost:3306/sandbox"); //create an initial model. It cannot be used until it is synced. //To sync call User.sync or patio.syncModels is called var User = patio.addModel("user"); var createSchema = function(){ return DB.forceCreateTable("user", function(){ this.primaryKey("id"); this.firstName(String) this.lastName(String); this.password(String); this.dateOfBirth(Date); this.created(sql.TimeStamp); this.updated(sql.DateTime); }); }; //connect and create schema connectAndCreateSchema() .chain(function(){ //sync the model so it can be used return patio.syncModels().chain(function(){ var myUser = new User({ firstName : "Bob", lastName : "Yukon", password : "password", dateOfBirth : new Date(1980, 8, 29) }); //save the user return myUser.save().chain(function(user){ console.log(format("%s %s's id is %d", user.firstName, user.lastName, user.id)); }); }); }).chain(disconnect, disconnectError); ``` The flow for the above example is as follows: * Connects to a database and creates the "user" table.</li> * Sync the models. This gets the relevant table information from the "user" table * Create a new User * Save the user * Print out some user details, at this point the user is saved in the database * disconnect from the database * The final output should be "BobYukon's id is 1". ## Options Models some options that allow for the customization of the way a model be haves when interacting with the database. * **typecastOnLoad** : Defaults to true. Set to false to prevent properties from being type casted when loaded from the database. See [patio.Database.typecastValue](./patio_Database.html#typecastValue) ``` patio.addModel("user", { static : { //override default typecastOnLoad : false } }); ``` * **typecastOnAssignment** : Defaults to true. Set to false to prevent properties from being type casted when set by something other than the return values from the database. See [patio.Database.typecastValue](./patio_Database.html#typecastValue) ``` patio.addModel("user", { static : { //override default typecastOnAssignment : false } }); ``` So the following would not be typecasted. ``` var myUser = new User(); myUser.updated = new Date(2004, 1, 1, 12, 12, 12); //would not be auto converted to a patio.sql.DateTime myUser.updated = "2004-02-01 12:12:12" //would not be auto converted to a patio.sql.DateTime ``` * **typecastEmptyStringToNull** : Defaults to true. Set to false to prevent empty strings from being typecasted to null. ``` patio.addModel("user", { static : { //override default typecastEmptyStringToNull : false } }); ``` * **raiseOnTypecastError** : Defaults to true. Set to false to prevent errors thrown while type casting a value from being propogated. **USE WITH CARE** ``` patio.addModel("user", { static : { //override default raiseOnTypecastError : false } }); ``` * **useTransactions** : Defaults to true. Set to false to prevent models from using transactions when saving, deleting, or updating. This applies to the model associations also. ``` patio.addModel("user", { static : { //override default useTransactions : false } }); ``` * **identifierOutputMethod** : Defaults to null. Set this to override the Dataset default method of converting identifiers returned from the database. See [patio.Dataset.identifierOutputMethod](./patio_Dataset.html#.identifierOutputMethod) ``` patio.addModel("user", { static : { //override default identifierOutputMethod : "camelize" } }); ``` * **identifierInputMethod** : Defaults to null. Set this to override the Dataset default method of converting identifiers when sending them to the database. See [patio.Dataset.identifierInputMethod](./patio_Dataset.html#.identifierInputMethod). ``` patio.addModel("user", { static : { //override default identifierInputMethod : "underscore" } }); ``` * **camelize** : Defaults to null. Set this to true force this particular model's identifiers to be underscored when sent to the database and camelized when returned. This **WILL** override `patio.camelize`. **USE WITH CARE** ``` patio.addModel("user", { static : { //override default camelize : true } }); ``` * **underscore** : Defaults to null. Set this to force this particular model's identifiers to be camelized when sent to the database and underscored when returned. This **WILL** override `patio.underscore`. **USE WITH CARE** ``` patio.addModel("user", { static : { //override default underscore : true } }); ``` * **reloadOnSave** : Defaults to true. Set this to false to prevent the models properties from being reloaded from the database after a save operation. **Note**: If you set this to false and you have columns that have default values in the database and they are not explictly set they will **NOT** be loaded ``` patio.addModel("user", { static : { //override default reloadOnSave : false } }); ``` * **reloadOnUpdate** : Defaults to true. Set this to false to prevent the model's properties from being reloaded from the database when a model is updated. **Note**: If you set this to false and you have columns that have default values in the database and they are not explictly set they will **NOT** be refreshed. ``` patio.addModel("user", { static : { //override default reloadOnUpdate : false } }); ``` ## Creating a model To create a Model class to use within your code you use the [patio.addModel](.patio.html#addModel) method. ``` var User = patio.addModel("user") //you must sync the model before using it User.sync().chain(function(User){ var myUser = new User({ firstName : "Bob", lastName : "Yukon", password : "password", dateOfBirth : new Date(1980, 8, 29) }); return myUser.save().chain(function(){ console.log(format("%s %s was created at %s", myUser.firstName, myUser.lastName, myUser.created.toString())); console.log(format("%s %s's id is %d", myUser.firstName, myUser.lastName, myUser.id)); }); }).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. ``` var DB1 = patio.createConnection("my://connection/string"); var DB2 = patio.createConnection("my://connection/string2"); //user table in db1 var User1 = patio.addModel(DB1.from("user")); //user table in db2 var User2 = patio.addModel(DB2.from("user")); patio.syncModels().chain(function(User1,User2){ var myUser1 = new User1({ firstName : "Bob1", lastName : "Yukon1", password : "password", dateOfBirth : new Date(1980, 8, 29) }); var myUser2 = new User2({ firstName : "Bob2", lastName : "Yukon2", password : "password", dateOfBirth : new Date(1980, 8, 29) }); return comb.when(myUser1.save(), myUser2.save()).chain(function(saved){ console.log(format("%s %s was created at %s", myUser1.firstName, myUser1.lastName, myUser1.created.toString())); console.log(format("%s %s's id is %d", myUser1.firstName, myUser1.lastName, myUser1.id)); console.log(format("%s %s was created at %s", myUser2.firstName, myUser2.lastName,myUser2.created.toString())); console.log(format("%s %s's id is %d", myUser2.firstName, myUser2.lastName, myUser2.id)); }); }); ``` ## 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: ``` var User = patio.addModel("user", { instance : { _setFirstName : function(firstName){ return firstName.charAt(0).toUpperCase() + firstName.substr(1); }, _setLastName : function(lastName){ return lastName.charAt(0).toUpperCase() + lastName.substr(1); } } }); patio.syncModels().chain(function(User){ var myUser = new User({ firstName : "bob", lastName : "yukon" }); console.log(myUser.firstName); //Bob console.log(myUser.lastName); //Yukon }); ``` ### 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. ``` var User = patio.addModel("user", { instance : { _getRoles : function(roles){ return roles.split(","); } } }); patio.syncModels().chain(function(User){ var myUser = new User({ firstName : "bob", lastName : "yukon", roles : "admin,user,groupAdmin" }); console.log(myUser.roles); //['admin', 'user','groupAdmin']; }); ``` You can also use the getters/setters in tandem. Lets take the getters example from before but use a setter also ``` var User = patio.addModel("user", { instance : { _setRoles : function(roles){ return roles.join(","); }, _getRoles : function(roles){ return roles.split(","); } } }); patio.syncModels().chain(function(User){ var myUser = new User({ firstName : "bob", lastName : "yukon", roles : ["admin","user","groupAdmin"]; }); console.log(myUser.roles); //['admin', 'user','groupAdmin']; //INSERT INTO `user` (`first_name`, `last_name`, `roles`) VALUES ('bob', 'yukon', 'admin,user,groupAdmin') return myUser.save(); }); ``` ## Model hooks Each model has the following hooks * pre * **save<** : called right before the model is saved to the database * **update** : called right before the model is updated * **remove** : called right before a model is deleted * **load** : called right before a model is loaded with values from the database * post * **save<** : called right after the model is saved to the database * **update** : called right after the model is updated * **remove** : called right after a model is deleted * **load** : called right after a model is loaded with values from the database ``` var User = patio.addModel("user", { pre:{ "save":function(next){ console.log("pre save!!!") next(); }, "remove" : function(next){ console.log("pre remove!!!") next(); } }, post:{ "save":function(next){ console.log("post save!!!") next(); }, "remove" : function(next){ console.log("post remove!!!") next(); } }, instance:{ _setFirstName:function(firstName){ return firstName.charAt(0).toUpperCase() + firstName.substr(1); }, _setLastName:function(lastName){ return lastName.charAt(0).toUpperCase() + lastName.substr(1); } } }); ``` ## Using a model If you define a model you can either use the Models constructor directly. ``` //define the model var User = patio.addModel("user"); patio.syncModels(function(err){ if(err){ console.log(err.stack); }else{ var user = new User(); } }) ``` or you can use `patio.getModel` ``` patio.addModel("user"); patio.syncModels(function(err){ if(err){ console.log(err.stack); }else{ var User = patio.getModel("user"); var user = new User(); } }) ``` ###Mutli Database Models If you are working with multiple databases and your model's table is not in the [patio.defaultDatabase]("./patio.html#defaultDatabase") (the first database you connected to) then you will need to pass in the database the model's table is in. ``` var DB1 = patio.createConnection("my://connection/string"); var DB2 = patio.createConnection("my://connection/string2"); //user table in db1 var User1 = patio.addModel(DB1.from("user")); //user table in db2 var User2 = patio.addModel(DB2.from("user")); patio.syncModels(function(err){ if(err){ console.log(err.stack); }else{ var user1 = new User1(), user2 = new User2(); } }); ``` ###Creating The static [save](./patio_Model.html#.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. ``` var Student = patio.getModel("student"); Student.save([ { firstName:"Bob", lastName:"Yukon", gpa:3.689, classYear:"Senior" }, { firstName:"Greg", lastName:"Horn", gpa:3.689, classYear:"Sophomore" }, { firstName:"Sara", lastName:"Malloc", gpa:4.0, classYear:"Junior" }, { firstName:"John", lastName:"Favre", gpa:2.867, classYear:"Junior" }, { firstName:"Kim", lastName:"Bim", gpa:2.24, classYear:"Senior" }, { firstName:"Alex", lastName:"Young", gpa:1.9, classYear:"Freshman" } ]).chain(function(users){ //All users have been saved }, 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. ``` var Student = patio.getModel("student"); Student.save([ { firstName:"Bob", lastName:"Yukon", gpa:3.689, classYear:"Senior" }, { firstName:"Greg", lastName:"Horn", gpa:3.689, classYear:"Sophomore" } ], {transaction : false}).chain(function(users){ //work with the users }); ``` If you have an instance of a model then you can use the [save](./patio_Model.html#save) method on the instance of the model. ``` var myUser = new User({ firstName : "Bob", lastName : "Yukon", password : "password", dateOfBirth : new Date(1980, 8, 29) }); //save the user myUser.save().chain(function(user){ //the save is complete }, disconnectError); ``` You can also pass in values into the save method to set before saving. ``` var myUser = new User(); //save the user myUser.save({ firstName : "Bob", lastName : "Yukon", password : "password", dateOfBirth : new Date(1980, 8, 29) }).chain(function(user){ //the save is complete }, disconnectError); ``` You can also pass in an options object to override options such as using a transaction. ``` var myUser = new User(); //save the user myUser.save({ firstName : "Bob", lastName : "Yukon", password : "password", dateOfBirth : new Date(1980, 8, 29) }, {transaction : false}).chain(function(user){ //the save is complete }, disconnectError); ``` ###Reading The Model contains static methods for all of the datasets methods listed in [patio.Dataset.ACTION_METHODS](./patio_Dataset.html#.ACTION_METHODS) as well as all the methods listed in [patio.Dataset.QUERY_METHODS](./patio_Dataset.html#.QUERY_METHODS). Some of the most commonly used methods are: * [forEach](./patio_Dataset.html#forEach) ``` User.forEach(function(user){ console.log(user.firstName); }); //you may also return the result of another query(or any promise) from a forEach block, //this will prevent the forEach's promise from resolving until all actions that occured in the block have //resolved. var Blog = patio.addModel("blog"); User.forEach(funciton(user){ //create a blog for each user return new Blog({userId : user.id}).save(); }).chain(function(users){ //all users and blogs have been saved }, disconnectError); ``` * [map](./patio_Dataset.html#map) ``` User.map(function(user){ return user.firstName; }).chain(function(names){ console.log("User names are %s", names); }, disconnectError); ``` * [all](./patio_Dataset.html#all) ``` User.all().chain(function(users){ console.log(users.length); }, disconnectError); ``` * [filter](./patio_Dataset.html#filter) ``` //find all users where first names begin with bo case insensitive User.filter({firstName : /^bo/i}).all().chain(function(){ }, disconnectError); ``` * [one](./patio_Dataset.html#one) ``` User.filter({id : 1}).one().chain(function(user){ console.log("%d - %s %s", user.id, user.firstName, user.lastName); }, disconnectError); ``` * [first](./patioDataset.html#first) ``` //SELECT * FROM user WHERE first_name = 'bob' ORDER BY last_name LIMIT 1 User.filter({firstName : "bob"}).order("lastName").first().chain(function(user){ console.log("%d - %s %s", user.id, user.firstName, user.lastName); }, disconnectError); ``` * [last](./patio_Dataset.html#last) ``` //SELECT * FROM user WHERE first_name = 'bob' ORDER BY last_name DESC LIMIT 1 User.filter({firstName : "bob"}).order("lastName").last().chain(function(user){ console.log("%d - %s %s", user.id, user.firstName, user.lastName); }, disconnectError); ``` * [isEmpty](./patio_Dataset.html#isEmpty) ``` User.isEmpty().chain(function(isEmpty){ if(isEmpty){ console.log("user table is empty"); }else{ console.log("user table is not empty"); } }, disconnectError); ``` ###Updating The static [update](./patio_Model.html#.update) can be used for updating a batch of models. ``` //BEGIN //UPDATE `user` SET `password` = NULL //COMMIT 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](./patio_Dataset.html#filter) accepts. ``` User.update({password : null}, function(){ return this.lastAccessed.lte(comb.yearsAgo(1)); }); //same as User.update({password : null}, {lastAccess : {lte : comb.yearsAgo(1)}}); ``` To prevent default transaction behavior you can pass in an additional transaction option ``` 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](./patio_Model.html#update) instance method. ``` var updateUsers = User.forEach(function(user){ //returning the promise from update will cause the forEach not to resolve //until all updates have completed return user.update({fullName : user.firstName + " " + user.lastName}); }); updateUsers.chain(function(){ //updates finished }); ``` as with save you can pass in an options object to prevent default behavior such as transactions. ``` var updateUsers = User.forEach(function(user){ //returning the promise from update will cause the forEach not to resolve //until all updates have completed return user.update({fullName : user.firstName + " " + user.lastName}, {transaction : false}); }); updateUsers.chain(function(){ //updates finished }); ``` ###Deleting The static [remove](./patio_Model.html#.remove) can be used for removing a batch of models. **Note** this method is not anymore efficient just a convenience. ``` //remove all models User.remove(); ``` To limit the models removed you can pass in a query. The filter can be anything that [filter](./patio_Dataset.html#filter) accepts. ``` //remove models that start with m 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. ``` //mass remove models without loading them User.remove(null, {load : false}); ``` To prevent the default transaction behavior pass in the transaction option. ``` //removing models, not using a transaction User.remove(null, {transaction : false}); ``` If you have an instance of a model and you want to remove it you can use the [remove](./patio_Model.remove) instance method. ``` User.forEach(function(user){ return user.remove(); }).chain(function(){ //removed }); ``` To prevent the default transaction behavior pass in the transaction option ``` User.forEach(function(user){ return user.remove({transaction : false}); }).chain(function(){ //removed }); ```