1# node-mysql 2 3An enhancement to the mysql lib to make it a bit easier to use. Based on the existing funtionalities of (npm) mysql, it also 4 5* Handles transactions. 6* Provides a simple ORM, which 7 * Detects table schema automatically. 8 * Provides handy functions for creating and updating rows. 9 * Handles optimistic lock through versioning transparently. 10 * Maintains "date_created" and "last_updated" automatically. 11 * Provides database row level lock functionality. 12 13## Install 14 15```text 16npm install node-mysql 17``` 18 19### Dependencies 20 21```json 22 "dependencies": { 23 "better-js-class": "*", 24 "cps": "*", 25 "mysql": "*", 26 "underscore": "*" 27 } 28``` 29 30## Use 31 32```javascript 33var db = require('node-mysql'); 34var DB = db.DB; 35var BaseRow = db.Row; 36var BaseTable = db.Table; 37``` 38 39## APIs 40 41* DB 42 * [new DB](#new-DB) 43 * [db.connect](#db-connect) 44 * [db.transaction](#db-transaction) 45 * [db.cursor](#db-cursor) 46 * [db.end](#db-end) 47 * [db.add](#db-add) 48 * [db.get](#db-get) 49 * [DB.format](#DB-format) 50* Table 51 * [new Table](#new-Table) 52 * [table.create](#table-create) 53 * [table.clone](#table-clone) 54 * [table.find](#table-find) 55 * [table.findById](#table-findById) 56 * [table.lockById](#table-lockById) 57 * [table.findAll](#table-findAll) 58 * [table.baseQuery](#table-baseQuery) 59 * [table.linksTo](#table-linksTo) 60 * [table.linkedBy](#table-linkedBy) 61 * [table.relatesTo](#table-relatesTo) 62* Row 63 * [new Row](#new-Row) 64 * [row.update](#row-update) 65 * [row.updateWithoutOptimisticLock](#row-updateWithoutOptimisticLock) 66 * [row.get](#row-get) 67 * [row.getId](#row-getId) 68 * [row.linksTo](#row-linksTo) 69 * [row.linkedBy](#row-linkedBy) 70 * [row.relatesTo](#row-relatesTo) 71 72<a name="new-DB"/> 73### new DB(conf) 74 75Please refer to the the [connection pool conf](https://github.com/felixge/node-mysql#pooling-connections) in mysql package for the format of "conf". 76 77__Example__ 78 79```javascript 80var box = new DB({ 81 host : 'localhost', 82 user : 'root', 83 password : '', 84 database : 'prod_clone' 85}); 86``` 87 88Beyond the configuration fields provided by mysql package, there are two additional configuration fields that can be used in "conf": 89 90* useTransaction 91* useCursor 92 93 94#### useTransaction 95 96Only if "useTransaction" is provided can "[db.transaction](#db-transaction)" API be called. Otherwise, calls to "[db.transaction](#db-transaction)" will throw an error with the message "transation-not-setup-error". The "useTransaction" field itself is an configuration object that overrides the fields in "conf" to set up a connection pool for transactions. For instance: 97 98__Example__ 99 100```javascript 101var box = new DB({ 102 host : 'localhost', 103 user : 'root', 104 password : '', 105 database : 'prod_clone', 106 connectionLimit: 50, 107 useTransaction: { 108 connectionLimit: 1 109 } 110}); 111``` 112 113will allow the db object "box" to use "box.transaction" API, with a connection pool for transactions set up the same way as the normal connection pool except for the connectionLimit field being overridden to 1. So in "box", there are two mysql connection pools, for normal db requests and transactional db requests, repectively. The normal connection pool's configuration is: 114 115```javascript 116{ 117 host : 'localhost', 118 user : 'root', 119 password : '', 120 database : 'prod_clone', 121 connectionLimit: 50 122} 123``` 124 125while the transactional connection pool's configuration is: 126 127```javascript 128{ 129 host : 'localhost', 130 user : 'root', 131 password : '', 132 database : 'prod_clone', 133 connectionLimit: 1 134} 135``` 136 137#### useCursor 138 139Similarly to "useTransaction", only if "useCursor" is provided can "[db.cursor](#db-cursor)" API be called. Otherwise, calls to "[db.cursor](#db-cursor)" will throw an exception with the error message "cursor-not-setup-error". The field "useCursor" is very similar to the field "useTransaction", with the only difference that it is for setting up the mysql connection pool for cursors rather than transactions. "useCursor" is also an overriding object based upon the connection pool configuration for normal connections. For instance: 140 141__Example__ 142 143```javascript 144var box = new DB({ 145 host : 'localhost', 146 user : 'root', 147 password : '', 148 database : 'prod_clone', 149 connectionLimit: 50, 150 useCursor: { 151 connectionLimit: 1 152 } 153}); 154``` 155 156will allow the API "box.cursor" to be called, with a connection pool for cursors set up the same way as the normal connection pool except for the connectionLimit field being overridden to 1. So in "box", there are two mysql connection pools, for normal db requests and cursor db requests, repectively. The normal connection pool's configuration is: 157 158```javascript 159{ 160 host : 'localhost', 161 user : 'root', 162 password : '', 163 database : 'prod_clone', 164 connectionLimit: 50 165} 166``` 167 168while the cursor connection pool's configuration is: 169 170```javascript 171{ 172 host : 'localhost', 173 user : 'root', 174 password : '', 175 database : 'prod_clone', 176 connectionLimit: 1 177} 178``` 179 180#### Use Both 181 182"useTransaction" and "useCursor" can be used together: 183 184__Example__ 185 186```javascript 187var box = new DB({ 188 host : 'localhost', 189 user : 'root', 190 password : '', 191 database : 'prod_clone', 192 connectionLimit: 50, 193 useTransaction: { 194 connectionLimit: 20 195 }, 196 useCursor: { 197 connectionLimit: 1 198 } 199}); 200``` 201 202This will allow all of the three APIs, "box.connect", "box.transaction" and "box.cursor" to be called. In this case, box hold three connection pools, for normal connections, transactional connections and cursor connections, respectively. The normal connection pool is configured as: 203 204```javascript 205{ 206 host : 'localhost', 207 user : 'root', 208 password : '', 209 database : 'prod_clone', 210 connectionLimit: 50 211} 212``` 213 214the transactional connection pool is configured as: 215 216```javascript 217{ 218 host : 'localhost', 219 user : 'root', 220 password : '', 221 database : 'prod_clone', 222 connectionLimit: 20 223} 224``` 225 226and the cursor connection pool is configured as: 227 228```javascript 229{ 230 host : 'localhost', 231 user : 'root', 232 password : '', 233 database : 'prod_clone', 234 connectionLimit: 1 235} 236``` 237 238 239<a name="db-connect"> 240### db.connect(procedure, callback) 241 242The procedure is a function of the type: 243 244```javascript 245function(connection, callback) { 246 // work with the database connection 247} 248``` 249 250__Example__ 251 252```javascript 253var basicTest = function(cb) { 254 box.connect(function(conn, cb) { 255 cps.seq([ 256 function(_, cb) { 257 conn.query('select * from users limit 1', cb); 258 }, 259 function(res, cb) { 260 console.log(res); 261 cb(); 262 } 263 ], cb); 264 }, cb); 265}; 266``` 267 268<a name="db-transaction"/> 269### db.transaction(db_connection, procedure, callback) 270 271The procedure is a function of the type: 272 273```javascript 274function(connection, callback) { 275 // work with the database connection 276} 277``` 278 279Note that db.transaction takes one more arguemnt than the db.connect, 280which is a database connection object. If this connection object is 281already a transactional, then it will be used directly in the provided 282procedure. Otherwise, the connection will be "made transactional" and 283then used in the provided procedure. 284 285__Example__ 286 287```javascript 288var txnTest = function(cb) { 289 var add2Rows = function(conn, b, cb) { 290 dw.transaction( 291 conn/*This is a non-transactional connection.*/, 292 function( 293 conn/*A new transactional connection is 294 created to handle the transactional session.*/, 295 cb 296 ) { 297 cps.seq([ 298 function(_, cb) { 299 Model.Table.create(conn, getSampleDto(), cb); 300 }, 301 function(_, cb) { 302 dw.transaction( 303 conn/*This is already a transactional connection.*/, 304 function( 305 conn/*No new transactional connection created. 306 This connection is the same one as 307 in the calling context*/, 308 cb 309 ) { 310 console.log(conn.__transaction__) 311 Model.Table.create(conn, getSampleDto(), cb); 312 }, 313 cb 314 ); 315 }, 316 function(_, cb) { 317 if (b) { 318 cb(null, "Commit"); 319 } else { 320 throw new Error("Roll back"); 321 } 322 } 323 ], cb); 324 }, 325 cb 326 ); 327 }; 328 329 dw.connect(function(conn, cb) { 330 /* Uncommenting the following line will merge the two calls 331 to add2Rows into one transaction. */ 332 // dw.transaction(conn, function(conn, cb) { 333 cps.seq([ 334 function(_, cb) { 335 add2Rows(conn, true, cb); 336 }, 337 function(_, cb) { 338 add2Rows(conn, true, cb); 339 } 340 ], cb); 341 // }, cb); 342 }, cb); 343}; 344``` 345 346<a name="db-cursor"/> 347### db.cursor(query_string, procedure, callback) 348 349This API can be used to cursor thought the (potentially very long list 350of) results of a query. The procedure parameter is the operation to 351be applied to each row in the results of the query. Please note the following: 352 353* db.cursor will create a separate db connection for cursoring 354 purpose. That is why this API does not take a db_connection in the 355 parameter list, unlike most of the other db related APIs. 356 357* If there is an exception thrown out of the row handling procedure, 358 the cursoring will be stopped and the exception will be thrown out 359 to the top level callback (the 3rd parameter to db.cursor). If you 360 intend for the cursor to go over all the results, you need to catch 361 any exceptions in the row handling procedure (using cps.rescue). 362 363__Example__ 364 365```javascript 366// cursor through all the users and update the active status to "1" 367var cursorTest = function(cb) { 368 db.connect(function(conn, cb) { 369 var q = 'select * from users'; 370 371 db.cursor(q, function(row, cb) { 372 cps.rescur({ 373 'try': function() { 374 cps.seq([ 375 function(_, cb) { 376 var user = new User.Row(row); 377 user.update(conn, {active: 1}, cb); 378 }, 379 function(res, cb) { 380 console.log(res); 381 cb(); 382 } 383 ], cb); 384 }, 385 'catch': function(err, cb) { 386 console.log(err); 387 cb(); 388 } 389 }, cb); 390 }, cb); 391 }, cb); 392}; 393``` 394 395 396 397 398<a name="db-end" /> 399### db.end(); 400 401This function destructs the db object. 402 403<a name="db-add"/> 404 405### db.add(config); 406 407This function is used to define a model that belongs to the db object. The model config object is of the following format: 408 409```json 410{ 411 "name": { 412 "type": "String", 413 "description": "the name of the table" 414 }, 415 "idFieldName": { 416 "type": "String", 417 "optional": true, 418 "default": "id", 419 "description": "the name of the primary id column" 420 }, 421 "versionFieldName": { 422 "type": "String", 423 "optional": true, 424 "default": "version", 425 "description": "optimistic lock version" 426 }, 427 "createdFieldName": { 428 "type": "String", 429 "optional": true, 430 "default": "date_created", 431 "description": "creation time of the row" 432 }, 433 "updatedFieldName": { 434 "type": "String", 435 "optional": true, 436 "default": "last_updated", 437 "description": "last update time of the row" 438 }, 439 "Row": { 440 "type": "Object", 441 "optional": true, 442 "default": "{}", 443 "description": "the overriding method definition for the Row class of this model" 444 }, 445 "Table": { 446 "type": "Object", 447 "optional": true, 448 "default": "{}", 449 "description": "the overriding method definition for the Table class of this model" 450 } 451} 452``` 453 454Note that db.add returns a Table object, which can be further chained 455with linking/joining functions such as [linksTo](#table-linksTo), 456[linkedBy](#table-linkedBy) and [relatesTo](#table-relatesTo). 457 458__Example__ 459 460```javascript 461db.add({ 462 name: 'orders', 463 idFieldName: 'order_id', 464 Row: { 465 getFirstOrderItem: function(conn, cb) { 466 var me = this; 467 468 cps.seq([ 469 function(_, cb) { 470 me.linkedBy(conn, 'items', cb); 471 }, 472 function(items, cb) { 473 cb(null, items[0]); 474 } 475 ], cb); 476 } 477 }, 478 Table: { 479 createOrderUsingCoupon: function(conn, dto, coupon, cb) { 480 dto['coupon_id'] = coupon.getId(); 481 this.create(conn, dto, cb); 482 } 483 } 484}) 485 .linkedBy({ 486 name: 'items', 487 key: 'order_id', 488 table: 'order_items' 489 }) 490 .linksTo({ 491 name: 'user', 492 key: 'user_id', 493 table: 'users' 494 }) 495 .linksTo({ 496 name: 'coupon', 497 key: 'coupon_id', 498 table: 'coupons' 499 }) 500 .linksTo({ 501 name: 'credit_card', 502 key: 'credit_card_id', 503 table: 'credit_cards' 504 }) 505; 506 507``` 508 509In this example: 510 511* We created a model for the table "orders" in the database. 512* We add a method getFirstOrderItem into the Row class of this model. 513* We add a method createOrderWithCoupon into the Table class of this model. 514* We follow the foreign key relations of the "orders" table to define some link relations. 515 516<a name="db-get"/> 517 518### db.get(table_name) 519 520Once you use db.add to create a model in the db object, you can then 521use db.get to retrieve it by name. The return value of 522db.get if a object of the following format: 523 524```json 525{ 526 "Row": { 527 "type": "Row object" 528 }, 529 "Table": { 530 "type": "Table object" 531 } 532} 533``` 534 535__Example__ 536 537```javascript 538var Order = db.get('orders'); 539var Coupon = db.get('coupons'); 540 541db.connect(function(conn, cb) { 542 cps.seq([ 543 function(_, cb) { 544 Coupon.Table.findByCode(conn, '10-percent-off', cb); 545 } 546 function(coupon, cb) { 547 var dto = {/*dto data*/}; 548 Order.createOrderWithCoupon(conn, dto, coupon, cb); 549 }, 550 function(order, cb) { 551 order.getFirstOrderItem(conn, cb); 552 }, 553 function(firstItem, cb) { 554 console.log(firstItem); 555 cb() 556 } 557 ], cb); 558}, cb); 559``` 560 561 562<a name="DB-format" /> 563### DB.format(query_string, variable_bindings) 564 565This is a wrapper of the query string formatting functionality 566provided by the mysql package. Note that this is a global static 567method defined on the class DB. It is NOT an instance method defined 568a a DB instance db. 569 570__Example__ 571 572```javascript 573DB.format('select * from users where id = ?' [userId]); 574``` 575<a name="new-Table"/> 576### new Table(table_config) 577 578The table config schema is defined as follows: 579 580```json 581{ 582 "name": { 583 "type": "String", 584 "optional": false, 585 "description": "the name of the database table" 586 }, 587 "idFieldName": { 588 "type": "String", 589 "optional": true, 590 "default": "id", 591 "description": "the name of the primary id column" 592 }, 593 "versionFieldName": { 594 "type": "String", 595 "optional": true, 596 "default": "version", 597 "description": "optimistic lock version" 598 }, 599 "createdFieldName": { 600 "type": "String", 601 "optional": true, 602 "default": "date_created", 603 "description": "creation time of the row" 604 }, 605 "updatedFieldName": { 606 "type": "String", 607 "optional": true, 608 "default": "last_updated", 609 "description": "last update time of the row" 610 }, 611 "rowClass": { 612 "type": "Row class", 613 "optional": false, 614 "description": "the Row class of this table" 615 }, 616 "db": { 617 "type": "DB class instance", 618 "optional": false, 619 "description": "the DB instance that the table belongs to" 620 } 621} 622``` 623 624See [here](#row-table-instantiation) for an example of creating a table. 625 626 627<a name="table-create"/> 628 629### table.create(database_connection, data_object, callback) 630 631The callback here takes a Row object as result. 632 633__Example__ 634 635```javascript 636var createTest = function(cb) { 637 dw.connect(function(conn, cb) { 638 cps.seq([ 639 function(_, cb) { 640 User.Table.create(conn, { 641 first_name: 'Hannah', 642 last_name: 'Mckay', 643 gender: 'female' 644 // .... 645 }, cb); 646 }, 647 function(user, cb) { // user is an object of the class User.Row 648 console.log(user.get('first_name')); // print out 'Hannah' 649 cb(); 650 } 651 ], cb); 652 }, cb); 653}; 654``` 655 656In the input data object, please do NOT specify the following fields: 657 658* primary ID 659* date_created 660* last_udpated 661* version 662 663All of the these fields will be filled by the invocation to table.create. 664 665 666<a name="table-clone"/> 667 668### table.clone(database_connection, data_object, callback) 669 670This API is very similar to table.create. The key difference is that 671it does not mask out any data field carried in data_object. Instead, 672it literally uses every thing in data_object to create a new row. In 673other words, it'll honor the values of the following fields in 674data_object: 675 676* primary ID 677* date_created 678* last_udpated 679* version 680 681This API can be useful when one attempts to clone a row in a table 682literally to another table (which might be in another database). 683 684<a name="table-find"/> 685### table.find(database_connection, query_string, callback) 686 687This function is not too different from doing a query directly on a 688database connection. The only extra thing it does is to turn the 689result from a list of simple hash objects to a list of Row objects of 690the corresponding table's "rowClass". 691 692__Example__ 693 694```javascript 695dw.connect(function(conn, cb) { 696 var o; 697 cps.seq([ 698 function(_, cb) { 699 User.Table.find(conn, 'select * from users', cb); 700 }, 701 function(users, cb) { // users is a list of user object of the class User.Row 702 console.log(users[0]); // print the information of the first user 703 cb(); 704 } 705 ], cb); 706}, cb); 707``` 708 709<a name="table-findById"/> 710### table.findById(database_connection, row_id, callback) 711 712This is simply a short-hand for: 713 714```javascript 715cps.seq([ 716 function(_, cb) { 717 table.find( 718 conn, 719 DB.format('select * from table_name where primary_id = ?', [row_id]), 720 cb 721 ); 722 }, 723 function(res, cb) { 724 cb(res[0]); 725 } 726], cb); 727``` 728 729It finds a row in a table by its primary ID and returns a single row 730object of the table's corresponding rowClass. 731 732<a name="table-lockById"/> 733### table.lockById(database_connection, row_id, callback) 734 735This function does the same thing as findById and additionally, it 736locks the corresponding row for an atomic update. lockById can ONLY 737be used in a transaction context. Without a transaction context, it 738behaves the same as findById. Once a row is locked in one 739transaction, attempts of locking the same row in other transactions 740will hang until the current transaction either commits or rolls back, 741which release the current lock. 742 743__Example__ 744 745```javascript 746var lockTest = function(cb) { 747 var exclusiveUpdate = function(conn, delay, value, cb) { 748 dw.transaction(null, function(conn, cb) { 749 cps.seq([ 750 function(_, cb) { 751 Model.Table.lockById(conn, 1, cb); 752 }, 753 function(res, cb) { 754 setTimeout(function() { 755 cb(null, res); 756 }, delay); 757 }, 758 function(row, cb) { 759 row.update(conn, {'subscription_status': value}, cb); 760 }, 761 function(res, cb) { 762 cb(); 763 } 764 ], cb) 765 }, cb); 766 767 }; 768 769 var conn; 770 771 dw.transaction(conn, function(conn, cb) { 772 cps.seq([ 773 function(_, cb) { 774 cps.parallel([ 775 function(cb) { 776 exclusiveUpdate(conn, 2000, 'foo1', cb); 777 }, 778 function(cb) { 779 exclusiveUpdate(conn, 0, 'bar1', cb); 780 } 781 ], cb); 782 }, 783 function(res, cb) { 784 console.log(res); 785 cb(); 786 } 787 ], cb); 788 }, cb); 789}; 790``` 791 792In this example, two threads are executed in parallel. The thread of 793setting value "bar1" will be block by the thread of setting value 794"foo1". 795 796<a name="table-findAll"/> 797### table.findAll(database_connection, callback) 798 799This finds all the rows in a table. 800 801<a name="table-baseQuery"/> 802### table.baseQuery(query_string, variable_bindings) 803 804This is a short-hand for: 805 806```javascript 807DB.format('select * from table_name' + query_string, variable_bindings); 808``` 809 810It simply prepend a partial string indicating from which table the 811query is being performed. This might come handy in many cases. 812 813<a name="table-linksTo"/> 814### table.linksTo(config) 815 816The config object has the following schema: 817 818```json 819{ 820 "name": { 821 "type": "String", 822 "description": "The name of the field to add to the row's data." 823 }, 824 "key": { 825 "type": "String", 826 "description": "The key that belongs to the current table and links to another table." 827 }, 828 "table": { 829 "type": "String", 830 "description": "The name of the table that the current table links to." 831 } 832} 833``` 834 835__Example__ 836 837```javascript 838 Order.Table 839 .linksTo({ 840 name: 'credit_card', 841 key: 'credit_card_id' 842 table: 'credit_cards' 843 }) 844 .linksTo({ 845 name: 'shipping_address', 846 key: 'shipping_address_id' 847 table: 'addresses' 848 }) 849 ; 850``` 851 852Note that for "linksTo", the (join) key is on the current table. Once 853a "linksTo" is set up, a row object that corresponds to this table can 854call the "linksTo" method to pull more (associated) data into the row. 855See examples [here](#row-linksTo). 856 857<a name="table-linkedBy"/> 858### table.linkedBy(config) 859 860The config object has the following schema: 861 862```json 863{ 864 "name": { 865 "type": "String", 866 "description": "The name of the field to add to the row's data." 867 }, 868 "key": { 869 "type": "String", 870 "description": "The key that belongs to the other table and links to the current table." 871 }, 872 "table": { 873 "type": "String", 874 "description": "The name of the table that the current table is linked by." 875 } 876} 877``` 878 879__Example__ 880 881```javascript 882 Order.Table 883 .linkedBy({ 884 name: 'items', 885 table: OrderItem.Table, 886 key: 'order_id' 887 }) 888 ; 889``` 890 891Once a "linkedBy" is set up on a table, a row object corresponding to 892this table can call the "linkedBy" method to pull more (associated) 893data into the row. See examples [here](#row-linkedBy). 894 895<a name="table-relatesTo"/> 896 897### table.relatesTo(config) 898 899The config object has the following schema: 900 901```json 902{ 903 "name": { 904 "type": "String", 905 "description": "The name of the field to add to the row's data." 906 }, 907 "through": { 908 "type": "String", 909 "description": "The name of the through table, which joins both the current table and the target table." 910 }, 911 "leftKey": { 912 "type": "String", 913 "description": "The key that belongs to the current table and joins with the through table." 914 }, 915 "table": { 916 "type": "String", 917 "description": "The name of the target table that the current table is joining thourgh the through-table." 918 }, 919 "rightKey": { 920 "type": "String", 921 "description": "The key that belongs to the target table and joins with the through table." 922 } 923} 924``` 925 926__Example__ 927 928```javascript 929 Order.Table 930 .relatesTo({ 931 name: 'coupons', 932 leftKey: 'order_id', 933 through: 'order_coupons', 934 rightKey: 'coupon_id', 935 table: 'coupons' 936 }) 937 ; 938``` 939 940table.relatesTo is designed to represent ORM of a many-to-many 941relation. Once a "relatesTo" is set up on a table, a row object 942corresponding to this table can call the "relatesTo" method to pull 943more (associated) data into the row. See examples 944[here](#row-relatesTo). 945 946<a name="new-Row" /> 947### new Row(row_data) 948 949After having a concrete Row class, row instances can be created using 950it. The row_data parameter is an object mapping database table column 951names to their corresponding values. 952 953__Example__ 954 955```javascript 956new User.Row({ 957 first_name: 'Hannah', 958 last_name: 'Mckay', 959 gender: 'female' 960 //.... 961}); 962``` 963<a name="row-update"/> 964 965### row.update(database_connection, update_object, callback) 966 967This function will set the following column automatically: 968 969* last_updated. This field will be set to the present time stamp. 970* version. This field will be increased. 971 972Other than these columns, only columns listed in the update_object will be updated. 973 974__Example__ 975 976```javascript 977var findAndUpdateTest = function(cb) { 978 dw.connect(function(conn, cb) { 979 cps.seq([ 980 function(_, cb) { 981 User.Table.findById(conn, id, cb); 982 }, 983 function(user, cb) { 984 var dto = { 985 'last_name': 'Morgan' 986 }; 987 user.update(conn, dto, cb); 988 } 989 ], cb); 990 }, cb); 991}; 992``` 993 994<a name="row-updateWithoutOptimisticLock"/> 995### row.updateWithoutOptimisticLock(database_connection, update_object, callback) 996 997This function is the same as row.update with only one difference: it 998does not care about the optimistic lock version field. It neither 999looks at this field nor update field. This might be useful 1000ocasionally when optimistic lock functionality needs to be overriden. 1001 1002 1003<a name="row-get"/> 1004### row.get(column_name) 1005 1006Get the value of a certain column from the row object. 1007 1008<a name="row-getId"/> 1009### row.getId() 1010 1011Get the primary ID of the row object. 1012 1013<a name="row-linksTo"/> 1014### row.linksTo(database_connection, field_name, callback) 1015 1016Given a "linksTo" setup in the corresponding "Table" object, row.linksTo pulls further relevant data into the row. 1017 1018__Example__ 1019 1020```javascript 1021var order; 1022 1023cps.seq([ 1024 function(_, cb) { 1025 Order.Table.findById(conn, id, cb); 1026 }, 1027 function(res, cb) { 1028 order = res; 1029 order.linksTo(conn, 'credit_card', cb); 1030 }, 1031 function(_, cb) { 1032 order.linksTo(conn, 'shipping_address', cb); 1033 }, 1034 function(_, cb) { 1035 console.log(order.get('credit_card').getId()); 1036 console.log(order.get('shipping_address').getId()); 1037 cb(); 1038 } 1039], cb); 1040``` 1041 1042<a name="row-linkedBy"/> 1043### row.linkedBy(database_connection, field_name, callback) 1044 1045Given a "linkedBy" setup in the corresponding "Table" object, row.linkedBy pulls further relevant data into the row. 1046 1047__Example__ 1048 1049```javascript 1050var order; 1051 1052cps.seq([ 1053 function(_, cb) { 1054 Order.Table.findById(conn, id, cb); 1055 }, 1056 function(res, cb) { 1057 order = res; 1058 order.linkedBy(conn, 'items', cb); 1059 }, 1060 function(items, cb) { 1061 // items will both be bound to the return value and be assigned to the 'items' field. 1062 console.log(items); 1063 console.log(order.get('items')); 1064 cb(); 1065 } 1066], cb); 1067``` 1068 1069<a name="row-relatesTo"/> 1070### row.relatesTo(database_connection, field_name, callback) 1071 1072Given a "relatesTo" setup in the corresponding "Table" object, row.relatesTo pulls further relevant data into the row. 1073 1074__Example__ 1075 1076```javascript 1077var order; 1078 1079cps.seq([ 1080 function(_, cb) { 1081 Order.Table.findById(conn, id, cb); 1082 }, 1083 function(res, cb) { 1084 order = res; 1085 order.relatesTo(conn, 'coupons', cb); 1086 }, 1087 function(coupons, cb) { 1088 // coupons will both be bound to the return value and be assigned to the 'coupons' field. 1089 console.log(coupons); 1090 console.log(order.get('coupons')); 1091 cb(); 1092 } 1093], cb); 1094``` 1095