Merge pull request #41 from leebrooks0/master

Made some small improvements to the documentation
This commit is contained in:
Jinzhu 2014-01-02 18:56:56 -08:00
commit a2e26d3641
1 changed files with 105 additions and 72 deletions

177
README.md
View File

@ -1,15 +1,15 @@
# GORM # GORM
Yet Another ORM library for Go, aims for developer friendly Yet Another ORM library for Go, aims to be developer friendly.
## Overview ## Overview
* Chainable API * Chainable API
* Relations * Relations
* Callbacks (before/after create/save/update/delete/find) * Callbacks (before/after create/save/update/delete/find)
* Soft Delete * Soft Deletes
* Auto Migration * Auto Migrations
* Transaction * Transactions
* Logger Support * Logger Support
* Bind struct with tag * Bind struct with tag
* Iteration Support via [Rows](#row--rows) * Iteration Support via [Rows](#row--rows)
@ -26,11 +26,16 @@ Yet Another ORM library for Go, aims for developer friendly
* Column name is the snake case of field's name. * Column name is the snake case of field's name.
* Use `Id int64` field as primary key. * Use `Id int64` field as primary key.
* Use tag `sql` to change field's property, change the tag name with `db.SetTagIdentifier(new_name)`. * Use tag `sql` to change field's property, change the tag name with `db.SetTagIdentifier(new_name)`.
* Use `CreatedAt` to store record's created time if it exist. * Use `CreatedAt` to store record's created time if field exists.
* Use `UpdatedAt` to store record's updated time if it exist. * Use `UpdatedAt` to store record's updated time if field exists.
* Use `DeletedAt` to store record's deleted time if it exist. [Soft Delete](#soft-delete) * Use `DeletedAt` to store record's deleted time if field exists. [Soft Delete](#soft-delete)
```go ```go
import (
"database/sql"
"time"
)
type User struct { type User struct {
Id int64 Id int64
Birthday time.Time Birthday time.Time
@ -60,13 +65,14 @@ type Address struct {
Address1 string `sql:"not null;unique"` // Set this field as not nullable and unique in database Address1 string `sql:"not null;unique"` // Set this field as not nullable and unique in database
Address2 string `sql:"type:varchar(100);unique"` Address2 string `sql:"type:varchar(100);unique"`
Post sql.NullString `sql:not null` Post sql.NullString `sql:not null`
// FYI, "NOT NULL" will only works well with NullXXX Scanner, because golang will initalize a default value for most type... // FYI, "NOT NULL" will only work well with NullXXX Scanner, because golang will initalize a default value for most type...
} }
``` ```
## Opening a Database ## Opening a Database
```go ```go
import "github.com/jinzhu/gorm" import "github.com/jinzhu/gorm"
import _ "github.com/lib/pq" import _ "github.com/lib/pq"
// import _ "github.com/go-sql-driver/mysql" // import _ "github.com/go-sql-driver/mysql"
@ -86,16 +92,39 @@ db.DB().Ping()
// By default, table name is plural of struct type, you can use struct type as table name with: // By default, table name is plural of struct type, you can use struct type as table name with:
db.SingularTable(true) db.SingularTable(true)
```
Gorm is goroutines friendly, so you can create a global variable to keep the connection and use it everywhere in your project.
// Gorm is goroutines friendly, so you can create a global variable to keep the connection and use it everywhere in your project ```go
// db.go // db.go
package db package db
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/lib/pq"
)
var DB gorm.DB var DB gorm.DB
func init() { func init() {
var err error var err error
DB, err = gorm.Open("postgres", "user=gorm dbname=gorm sslmode=disable") DB, err = gorm.Open("postgres", "user=gorm dbname=gorm sslmode=disable")
// Connection string parameters for Postgres - http://godoc.org/github.com/lib/pq, if you are using another
// database refer to the relevant driver's documentation.
// * dbname - The name of the database to connect to
// * user - The user to sign in as
// * password - The user's password
// * host - The host to connect to. Values that start with / are for unix domain sockets.
// (default is localhost)
// * port - The port to bind to. (default is 5432)
// * sslmode - Whether or not to use SSL (default is require, this is not the default for libpq)
// Valid SSL modes:
// * disable - No SSL
// * require - Always SSL (skip verification)
// * verify-full - Always SSL (require verification)
if err != nil { if err != nil {
panic(fmt.Sprintf("Got error when connect database, the error is '%v'", err)) panic(fmt.Sprintf("Got error when connect database, the error is '%v'", err))
} }
@ -121,11 +150,12 @@ db.DropTable(User{})
### Automating Migrations ### Automating Migrations
Feel Free to update your struct, AutoMigrate will keep your database update to date. Feel free to update your struct, AutoMigrate will keep your database update to date.
FYI, AutoMigrate will only add new columns, won't change current column's type or delete unused columns, to make sure your data is safe FYI, AutoMigrate will only add new columns, it won't change the current columns' types or delete unused columns, to make sure your data is safe.
If table doesn't exist when AutoMigrate, gorm will run create table automatically. If the table doesn't exist when AutoMigrate, gorm will run create the table automatically.
(the database first needs to be created manually though...).
(only postgres and mysql supported) (only postgres and mysql supported)
@ -154,7 +184,7 @@ db.NewRecord(user) // => false
### Create With SubStruct ### Create With SubStruct
Refer [Query With Related](#query-with-related) for how to find associations Refer to [Query With Related](#query-with-related) for how to find associations
```go ```go
user := User{ user := User{
@ -165,11 +195,13 @@ user := User{
} }
db.Save(&user) db.Save(&user)
//// BEGIN TRANSACTION;
//// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"); //// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1");
//// INSERT INTO "addresses" (address1) VALUES ("Shipping Address - Address 1"); //// INSERT INTO "addresses" (address1) VALUES ("Shipping Address - Address 1");
//// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2); //// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2);
//// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"); //// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com");
//// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu-2@example.com"); //// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu-2@example.com");
//// COMMIT;
``` ```
## Query ## Query
@ -400,19 +432,19 @@ db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
### Soft Delete ### Soft Delete
If a struct has DeletedAt field, it will get soft delete ability automatically! If a struct has a DeletedAt field, it will get soft delete ability automatically!
For those don't have the filed, will be deleted from database permanently Structs that don't have a DeletedAt field will be deleted from the database permanently
```go ```go
db.Delete(&user) db.Delete(&user)
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111; //// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
// Delete with search condiation // Delete with search condition
db.Where("age = ?", 20).Delete(&User{}) db.Where("age = ?", 20).Delete(&User{})
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20; //// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
// Soft deleted records will be ignored when search // Soft deleted records will be ignored when searched
db.Where("age = 20").Find(&user) db.Where("age = 20").Find(&user)
//// SELECT * FROM users WHERE age = 100 AND (deleted_at IS NULL AND deleted_at <= '0001-01-02'); //// SELECT * FROM users WHERE age = 100 AND (deleted_at IS NULL AND deleted_at <= '0001-01-02');
@ -429,7 +461,7 @@ db.Unscoped().Delete(&order)
Try to get the first record, if failed, will initialize the struct with search conditions. Try to get the first record, if failed, will initialize the struct with search conditions.
(only support search conditions map and struct) (only supports search conditions map and struct)
```go ```go
db.FirstOrInit(&user, User{Name: "non_existing"}) db.FirstOrInit(&user, User{Name: "non_existing"})
@ -444,7 +476,7 @@ db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
### FirstOrInit With Attrs ### FirstOrInit With Attrs
Ignore Attrs's arguments when search, but use them to initialize the struct if no record found. Ignore Attrs's arguments when searching, but use them to initialize the struct if no record is found.
```go ```go
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user) db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
@ -462,7 +494,7 @@ db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrInit(&user)
### FirstOrInit With Assign ### FirstOrInit With Assign
Ignore Assign's arguments when search, but use them to fill the struct regardless record found or not Ignore Assign's arguments when searching, but use them to fill the struct regardless, whether the record is found or not.
```go ```go
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
@ -474,7 +506,7 @@ db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 30}).FirstOrInit(&user)
## FirstOrCreate ## FirstOrCreate
Try to get the first record, if failed, will initialize the struct with search conditions and insert it to database Try to get the first record, if failed, will initialize the struct with the search conditions and insert it in the database.
```go ```go
db.FirstOrCreate(&user, User{Name: "non_existing"}) db.FirstOrCreate(&user, User{Name: "non_existing"})
@ -489,7 +521,7 @@ db.FirstOrCreate(&user, map[string]interface{}{"name": "jinzhu", "age": 30})
### FirstOrCreate With Attrs ### FirstOrCreate With Attrs
Ignore Attrs's arguments when search, but use them to initialize the struct if no record found. Ignore Attrs's arguments when searching, but use them to initialize the struct if no record is found.
```go ```go
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user) db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
@ -502,7 +534,7 @@ db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 30}).FirstOrCreate(&user)
### FirstOrCreate With Assign ### FirstOrCreate With Assign
Ignore Assign's arguments when search, but use them to fill the struct regardless record found or not, then save it back to database Ignore Assign's arguments when searching, but use them to fill the struct regardless, whether the record is found or not, then save it back to the database.
```go ```go
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user) db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
@ -579,7 +611,7 @@ db.Table("deleted_users").Count(&count)
## Pluck ## Pluck
Get struct's attribute as map Get struct's selected attributes as a map
```go ```go
var ages []int64 var ages []int64
@ -594,16 +626,35 @@ db.Model(&User{}).Pluck("name", &names)
db.Table("deleted_users").Pluck("name", &names) db.Table("deleted_users").Pluck("name", &names)
//// SELECT name FROM deleted_users; //// SELECT name FROM deleted_users;
// Pluck more than one column? Do it like this // Plucking more than one column? Do it like this:
db.Select("name, age").Find(&users) db.Select("name, age").Find(&users)
``` ```
## Transactions
All individual save and delete operations are run in a transaction by default.
```go
tx := db.Begin()
user := User{Name: "transcation"}
tx.Save(&u)
tx.Update("age": 90)
// do whatever
// rollback
tx.Rollback()
// commit
tx.Commit()
```
## Callbacks ## Callbacks
Callbacks are functions defined to struct's pointer, they would be run when save a struct to database. Callbacks are methods defined on the struct's pointer.
If any callback return error, gorm will stop future operations and rollback all changes If any callback returns an error, gorm will stop future operations and rollback all changes.
Here is a list with all available callbacks, Here is a list of all available callbacks,
listed in the same order in which they will get called during the respective operations. listed in the same order in which they will get called during the respective operations.
### Creating an Object ### Creating an Object
@ -654,9 +705,9 @@ func (u *User) BeforeUpdate() (err error) {
return return
} }
// Rollback the insertion if have more than 1000 users // Rollback the insertion if there are more than 1000 users (hypothetical example)
func (u *User) AfterCreate() (err error) { func (u *User) AfterCreate() (err error) {
if (u.Id > 1000) { // Just as example, don't use Id to count users! if (u.Id > 1000) { // Just as an example, don't use Id to count users!
err = errors.New("Only 1000 users allowed!") err = errors.New("Only 1000 users allowed!")
} }
return return
@ -665,22 +716,22 @@ func (u *User) AfterCreate() (err error) {
```go ```go
// As you know, the save/delete operations are running in a transaction // As you know, the save/delete operations are running in a transaction
// This is means all your changes will be rollbacked if get any errors // This is means that all your changes will be rolled back if there are any errors
// If you want your changes in callbacks be run in the same transaction // If you want your changes in callbacks be run in the same transaction
// You have to pass the transaction as argument to the function // you have to pass the transaction as argument to the function
func (u *User) AfterCreate(tx *gorm.DB) (err error) { func (u *User) AfterCreate(tx *gorm.DB) (err error) {
tx.Model(u).Update("role", "admin") tx.Model(u).Update("role", "admin")
return return
} }
``` ```
## Specify Table Name ## Specifying the Table Name
```go ```go
// Create `deleted_users` table with User's fields // Create `deleted_users` table with User's fields
db.Table("deleted_users").CreateTable(&User{}) db.Table("deleted_users").CreateTable(&User{})
// Search from table `deleted_users`, and fill results to []User // Search from table `deleted_users`
var deleted_users []User var deleted_users []User
db.Table("deleted_users").Find(&deleted_users) db.Table("deleted_users").Find(&deleted_users)
//// SELECT * FROM deleted_users; //// SELECT * FROM deleted_users;
@ -690,7 +741,7 @@ db.Table("deleted_users").Where("name = ?", "jinzhu").Delete()
//// DELETE FROM deleted_users WHERE name = 'jinzhu'; //// DELETE FROM deleted_users WHERE name = 'jinzhu';
``` ```
### Specify Table Name for Struct permanently with TableName method ### Specifying the Table Name for Struct permanently with TableName
```go ```go
type Cart struct { type Cart struct {
@ -709,24 +760,6 @@ func (u User) TableName() string {
} }
``` ```
## Transaction
```go
tx := db.Begin()
user := User{Name: "transcation"}
tx.Save(&u)
tx.Update("age": 90)
// do whatever
// rollback
tx.Rollback()
// commit
tx.Commit()
```
## Scopes ## Scopes
```go ```go
@ -760,7 +793,7 @@ db.Scopes(OrderStatus([]string{"paid", "shipped"})).Find(&orders)
## Logger ## Logger
Grom has builtin logger support, enable it with: Gorm has built-in logger support, enable it with:
```go ```go
db.LogMode(true) db.LogMode(true)
@ -770,13 +803,13 @@ db.LogMode(true)
```go ```go
// Use your own logger // Use your own logger
// Refer gorm's default logger for how to format messages: https://github.com/jinzhu/gorm/blob/master/logger.go#files // Refer to gorm's default logger for how to format messages: https://github.com/jinzhu/gorm/blob/master/logger.go#files
db.SetLogger(log.New(os.Stdout, "\r\n", 0)) db.SetLogger(log.New(os.Stdout, "\r\n", 0))
// Disable log // Disable logging
db.LogMode(false) db.LogMode(false)
// Enable log for a single operation, make debug easy // Enable logging for a single operation, to make debugging easy
db.Debug().Where("name = ?", "jinzhu").First(&User{}) db.Debug().Where("name = ?", "jinzhu").First(&User{})
``` ```
@ -811,14 +844,14 @@ for rows.Next() {
} }
``` ```
## Run Raw SQl ## Run Raw SQL
```go ```go
// Raw sql // Raw SQL
db.Exec("drop table users;") db.Exec("DROP TABLE users;")
// Raw sql with arguments // Raw SQL with arguments
db.Exec("update orders set shipped_at=? where id in (?)", time.Now, []int64{11,22,33}) db.Exec("UPDATE orders SET shipped_at=? WHERE id IN (?)", time.Now, []int64{11,22,33})
``` ```
## Error Handling ## Error Handling
@ -826,16 +859,16 @@ db.Exec("update orders set shipped_at=? where id in (?)", time.Now, []int64{11,2
```go ```go
query := db.Where("name = ?", "jinzhu").First(&user) query := db.Where("name = ?", "jinzhu").First(&user)
query := db.First(&user).Limit(10).Find(&users) query := db.First(&user).Limit(10).Find(&users)
//// query.Error keep the latest error happened //// query.Error returns the last error
//// query.Errors keep all errors happened //// query.Errors returns all errors that have taken place
//// If an error happened, gorm will stop following operations //// If an error has taken place, gorm will stop all following operations
// I often use some code like below to do error handling when writting applicatoins // I often use some code like below to do error handling when writing applications
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil { if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// ... // ...
} }
// If no record found, gorm will return RecordNotFound error, you could check it with // If no record is found, gorm will return RecordNotFound error, you could check it with
db.Where("name = ?", "hello world").First(&User{}).Error == gorm.RecordNotFound db.Where("name = ?", "hello world").First(&User{}).Error == gorm.RecordNotFound
// Or use shortcut method // Or use shortcut method
@ -850,9 +883,9 @@ if db.Model(&user).Related(&credit_card).RecordNotFound() {
} }
``` ```
## Advanced Usage With Query Chain ## Advanced Usage With Query Chaining
Already excited about above usage? Let's see some magic! Already excited with what gorm has to offer? Let's see some magic!
```go ```go
db.First(&first_article).Count(&total_count).Limit(10).Find(&first_page_articles).Offset(10).Find(&second_page_articles) db.First(&first_article).Count(&total_count).Limit(10).Find(&first_page_articles).Offset(10).Find(&second_page_articles)
@ -868,7 +901,7 @@ db.Where("created_at > ?", "2013-10-10").Find(&cancelled_orders, "state = ?", "c
//// SELECT * FROM orders WHERE created_at > '2013/10/10' AND state = 'shipped'; (shipped_orders) //// SELECT * FROM orders WHERE created_at > '2013/10/10' AND state = 'shipped'; (shipped_orders)
// Use variable to keep query chain // Use variables to keep query chain
todays_orders := db.Where("created_at > ?", "2013-10-29") todays_orders := db.Where("created_at > ?", "2013-10-29")
cancelled_orders := todays_orders.Where("state = ?", "cancelled") cancelled_orders := todays_orders.Where("state = ?", "cancelled")
shipped_orders := todays_orders.Where("state = ?", "shipped") shipped_orders := todays_orders.Where("state = ?", "shipped")
@ -886,7 +919,7 @@ db.Where("mail_type = ?", "TEXT").Find(&users1).Table("deleted_users").Find(&use
//// SELECT * FROM deleted_users WHERE mail_type = 'TEXT'; (users2) //// SELECT * FROM deleted_users WHERE mail_type = 'TEXT'; (users2)
// An example for how to use FirstOrCreate // An example on how to use FirstOrCreate
db.Where("email = ?", "x@example.org").Attrs(User{RegisteredIp: "111.111.111.111"}).FirstOrCreate(&user) db.Where("email = ?", "x@example.org").Attrs(User{RegisteredIp: "111.111.111.111"}).FirstOrCreate(&user)
//// SELECT * FROM users WHERE email = 'x@example.org'; //// SELECT * FROM users WHERE email = 'x@example.org';
//// INSERT INTO "users" (email,registered_ip) VALUES ("x@example.org", "111.111.111.111") // if no record found //// INSERT INTO "users" (email,registered_ip) VALUES ("x@example.org", "111.111.111.111") // if no record found