Update README

This commit is contained in:
Jinzhu 2013-11-15 17:27:16 +08:00
parent e0b90d9770
commit 4e1e2a0fbb
1 changed files with 87 additions and 87 deletions

174
README.md
View File

@ -19,11 +19,12 @@ Yet Another ORM library for Go, aims for developer friendly
## Conventions
```go
type User struct { // TableName: `users`, gorm will pluralize struct's name as table name
// TableName: `users`, gorm will pluralize struct's name as table name, you are possible to turn off this feature
type User struct {
Id int64 // Id: Database Primary key
Birthday time.Time
Age int64
Name string `sql:"size:255"` // set this field's length and as not null with tag
Name string `sql:"size:255"` // set this field's length in database
CreatedAt time.Time // Time of record is created, will be insert automatically
UpdatedAt time.Time // Time of record is updated, will be updated automatically
DeletedAt time.Time // Time of record is deleted, refer `Soft Delete` for more
@ -33,22 +34,22 @@ type User struct { // TableName: `users`, gorm will pluralize struct's n
BillingAddressId sql.NullInt64 // Embedded struct BillingAddress's foreign key
ShippingAddress Address // Embedded struct
ShippingAddressId int64 // Embedded struct ShippingAddress's foreign key
IgnoreMe int64 `sql:"-"` // Ignore this field with tag
IgnoreMe int64 `sql:"-"` // Ignore this field
}
type Email struct { // TableName: `emails`
Id int64
UserId int64 // Foreign key for above embedded structs
Email string `sql:"type:varchar(100);"` // Set column type directly with tag
Email string `sql:"type:varchar(100);"` // Set this field's type in database
Subscribed bool
}
type Address struct { // TableName: `addresses`
Id int64
Address1 string `sql:"not null;unique"` // Set column as unique with tag
Address1 string `sql:"not null;unique"` // Set this field as not null and unique in database
Address2 string `sql:"type:varchar(100);unique"`
Post sql.NullString `sql:not null`
// Be careful: "NOT NULL" will only works for NullXXX scanner, because golang will initalize a default value for most type...
// FYI, "NOT NULL" will only works well with NullXXX Scanner, because golang will initalize a default value for most type...
}
```
@ -69,20 +70,28 @@ db, err := Open("postgres", "user=gorm dbname=gorm sslmode=disable")
db.SetPool(100)
// By default, table name is plural of struct type, if you like singular table name
// By default, table name is plural of struct type, you can use struct type as table name with:
db.SingularTable(true)
// Gorm is goroutines friendly, so you can create a global variable to keep the connection and use it everywhere like this
// Gorm is goroutines friendly, so you can create a global variable to keep the connection and use it everywhere in your project
// db.go
package db
var DB gorm.DB
func init() {
DB, err = gorm.Open("postgres", "user=gorm dbname=gorm sslmode=disable")
if err != nil {
panic(fmt.Sprintf("Got error when connect database, the error is '%v'", err))
}
}
// user.go
package user
import _ "db"
...
DB.Save(&User{Name: "xxx"})
...
```
## Struct & Database Mapping
@ -99,9 +108,9 @@ db.DropTable(User{})
Feel Free to update your struct, AutoMigrate will keep your database update to date.
FYI, AutoMigrate will only add new columns, won't change column's type or delete unused columns, to make sure gorm won't harm your data.
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
If table doesn't exist when AutoMigrate, it will run create table automatically.
If table doesn't exist when AutoMigrate, gorm will run create table automatically.
(only postgres and mysql supported)
@ -118,7 +127,7 @@ db.Save(&user)
### Create With SubStruct
Refer [Query With Related](#query-with-related) to find how to find associations
Refer [Query With Related](#query-with-related) for how to find associations
```go
user := User{
@ -142,32 +151,22 @@ db.Save(&user)
// Get the first record
db.First(&user)
//// SELECT * FROM users ORDER BY id LIMIT 1;
// Search table `users` are guessed from the out struct type.
// You are possible to specify the table name with Model() if no out struct for some methods like Pluck()
// Or set table name with Table(), if so, it will ignore the out struct even have it. more details following.
// Search table `users` is guessed from struct's type
// Get the last record
db.Last(&user)
//// SELECT * FROM users ORDER BY id DESC LIMIT 1;
// Get a record without order by primary key
db.Find(&user)
//// SELECT * FROM users LIMIT 1;
// Get first record as map
db.First(&users)
//// SELECT * FROM users LIMIT 1;
// Get All records
db.Find(&users)
//// SELECT * FROM users;
// Using a Primary Key
// Get record with primary key
db.First(&user, 10)
//// SELECT * FROM users WHERE id = 10;
```
### Query With Where (SQL like condition)
### Query With Where (SQL)
```go
// Get the first matched record
@ -205,7 +204,7 @@ db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
// IN For Primary Key
// IN for primary Keys
db.Where([]int64{20, 21, 22}).Find(&users)
//// SELECT * FROM users WHERE id IN (20, 21, 22);
```
@ -221,18 +220,18 @@ db.Not("name", "jinzhu").First(&user)
db.Not("name", []string{"jinzhu", "jinzhu 2"}).Find(&users)
//// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
// Not In for Primary Key
// Not In for primary keys
db.Not([]int64{1,2,3}).First(&user)
//// SELECT * FROM users WHERE id NOT IN (1,2,3);
db.Not([]int64{}).First(&user)
//// SELECT * FROM users;
// Normal SQL
// SQL string
db.Not("name = ?", "jinzhu").First(&user)
//// SELECT * FROM users WHERE NOT(name = "jinzhu");
// Not With Struct
// Not with struct
db.Not(User{Name: "jinzhu"}).First(&user)
//// SELECT * FROM users WHERE name <> "jinzhu";
```
@ -244,7 +243,7 @@ db.Not(User{Name: "jinzhu"}).First(&user)
db.First(&user, 23)
//// SELECT * FROM users WHERE id = 23 LIMIT 1;
// Normal SQL
// SQL string
db.Find(&user, "name = ?", "jinzhu")
//// SELECT * FROM users WHERE name = "jinzhu";
@ -252,11 +251,11 @@ db.Find(&user, "name = ?", "jinzhu")
db.Find(&users, "name <> ? and age > ?", "jinzhu", 20)
//// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
// Inline Search With Struct
// Inline search with struct
db.Find(&users, User{Age: 20})
//// SELECT * FROM users WHERE age = 20;
// Inline Search With Map
// Inline search with map
db.Find(&users, map[string]interface{}{"age": 20})
//// SELECT * FROM users WHERE age = 20;
```
@ -278,17 +277,17 @@ db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2"}).Find(
### Query With Related
```go
// Find emails from user with guessed foreign key
// Find user's emails with guessed foreign key
db.Model(&user).Related(&emails)
//// SELECT * FROM emails WHERE user_id = 111;
// Find address from user with specified foreign key 'BillingAddressId'
// Find user's billing address with specified foreign key 'BillingAddressId'
db.Model(&user).Related(&address1, "BillingAddressId")
//// SELECT * FROM addresses WHERE id = 123; // 123 is the value of user's BillingAddressId
//// SELECT * FROM addresses WHERE id = 123; // 123 is user's BillingAddressId
// Find user from email with guessed primary key value from emails
// Find user with guessed primary key value from email
db.Model(&email).Related(&user)
//// SELECT * FROM users WHERE id = 111; // 111 is the value of email's UserId
//// SELECT * FROM users WHERE id = 111; // 111 is email's UserId
```
### Query Chains
@ -316,16 +315,16 @@ db.Save(&user)
### Update one attribute with `Update`
```go
// Update an existing struct's name if name is different
// Update existing user's name if it is changed
db.Model(&user).Update("name", "hello")
//// UPDATE users SET name='hello' WHERE id=111;
// Find out a struct, and update it if name is different
// Find out a user, and update the name if it is changed
db.First(&user, 111).Update("name", "hello")
//// SELECT * FROM users LIMIT 1;
//// UPDATE users SET name='hello' WHERE id=111;
// Specify table name with where search
// Update name with search condiation and specified table name
db.Table("users").Where(10).Update("name", "hello")
//// UPDATE users SET name='hello' WHERE id = 10;
```
@ -333,7 +332,7 @@ db.Table("users").Where(10).Update("name", "hello")
### Update multiple attributes with `Updates`
```go
// Update an existing record if have any changed values
// Update user's name and age if they are changed
db.Model(&user).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18 WHERE id = 111;
@ -372,28 +371,28 @@ For those don't have the filed, will be deleted from database permanently
db.Delete(&user)
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
// Batch delete when search
// Delete with search condiation
db.Where("age = ?", 20).Delete(&User{})
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
// For structs have DeletedAt field, when do query, will ignore deleted records by default
// Soft deleted records will be ignored when search
db.Where("age = 20").Find(&user)
//// SELECT * FROM users WHERE age = 100 AND (deleted_at IS NULL AND deleted_at <= '0001-01-02');
// Find out all records including those deleted with Unscoped
// Find soft deleted records with Unscoped
db.Unscoped().Where("age = 20").Find(&users)
//// SELECT * FROM users WHERE age = 20;
// Permanently delete a record with Unscoped
// Delete record permanently with Unscoped
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;
```
## FirstOrInit
Try to load the first record, if fails, initialize struct with search conditions.
Try to get the first record, if failed, will initialize the struct with search conditions.
(only support map or struct conditions, SQL like conditions are not supported)
(only support search conditions map and struct)
```go
db.FirstOrInit(&user, User{Name: "non_existing"})
@ -408,17 +407,17 @@ db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
### FirstOrInit With Attrs
Attr's arguments would be used to initialize struct if no record found, but won't be used for search
Ignore Attrs's arguments when search, but use them to initialize the struct if no record found.
```go
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
//// SELECT * FROM USERS WHERE name = 'non_existing';
//// User{Name: "non_existing", Age: 20}
// Above code could be simplified if has only one attribute
// Or write it like this if has only one attribute
db.Where(User{Name: "noexisting_user"}).Attrs("age", 20).FirstOrInit(&user)
// If a record found, Attrs would be just ignored
// If a record found, Attrs would be ignored
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrInit(&user)
//// SELECT * FROM USERS WHERE name = jinzhu';
//// User{Id: 111, Name: "Jinzhu", Age: 20}
@ -426,7 +425,7 @@ db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrInit(&user)
### FirstOrInit With Assign
Assign's arguments would be used to set the struct even a record found, but won't be used for search
Ignore Assign's arguments when search, but use them to fill the struct regardless record found or not
```go
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
@ -438,7 +437,7 @@ db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 30}).FirstOrInit(&user)
## FirstOrCreate
Try to load the first record, if fails, initialize struct with search conditions and save it
Try to get the first record, if failed, will initialize the struct with search conditions and insert it to database
```go
db.FirstOrCreate(&user, User{Name: "non_existing"})
@ -453,7 +452,7 @@ db.FirstOrCreate(&user, map[string]interface{}{"name": "jinzhu", "age": 30})
### FirstOrCreate With Attrs
Attr's arguments would be used to initialize struct if no record found, but won't be used for search
Ignore Attrs's arguments when search, but use them to initialize the struct if no record found.
```go
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
@ -466,9 +465,7 @@ db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 30}).FirstOrCreate(&user)
### FirstOrCreate With Assign
Assign's arguments would be used to initialize the struct if not record found,
If any record found, will assign those values to the record, and save it back to database.
Ignore Assign's arguments when search, but use them to fill the struct regardless record found or not, then save it back to database
```go
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
@ -493,6 +490,7 @@ db.Select("name, age").Find(&users)
db.Order("age desc, name").Find(&users)
//// SELECT * FROM users ORDER BY age desc, name;
// Multiple orders
db.Order("age desc").Order("name").Find(&users)
//// SELECT * FROM users ORDER BY age desc, name;
@ -508,7 +506,7 @@ db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
db.Limit(3).Find(&users)
//// SELECT * FROM users LIMIT 3;
// Cleanup limit with -1
// Remove limit with -1
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
//// SELECT * FROM users LIMIT 10; (users1)
//// SELECT * FROM users; (users2)
@ -520,7 +518,7 @@ db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
db.Offset(3).Find(&users)
//// SELECT * FROM users OFFSET 3;
// Cleanup offset with -1
// Remove offset with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
//// SELECT * FROM users OFFSET 10; (users1)
//// SELECT * FROM users; (users2)
@ -565,29 +563,30 @@ db.Select("name, age").Find(&users)
## Callbacks
Callback is a function defined to a struct, the function would be run when reflect a struct to database.
If a function return error, gorm will prevent future operations and do rollback
Callbacks are functions defined to struct's pointer, they would be run when save a struct to database.
If any callback return error, gorm will stop future operations and do rollback
Those callbacks are defined now:
Below callbacks are supported now:
`BeforeCreate`, `AfterCreate`
`BeforeUpdate`, `AfterUpdate`
`BeforeSave`, `AfterSave`
`BeforeDelete`, `AfterDelete`
For example:
```go
// Won't update readonly user
func (u *User) BeforeUpdate() (err error) {
if u.readonly() {
err = errors.New("Read Only User")
err = errors.New("Read Only User!")
}
return
}
// If have more than 1000 users, will rollback the insertion
// Rollback the insertion if have more than 1000 users
func (u *User) AfterCreate() (err error) {
if (u.Id > 1000) { // just an example, don't use Id to count users
err = errors.New("Only 1000 users allowed")
if (u.Id > 1000) { // Just as example, don't use Id to count users!
err = errors.New("Only 1000 users allowed!")
}
return
}
@ -596,26 +595,20 @@ func (u *User) AfterCreate() (err error) {
## Specify Table Name
```go
// When Create Table from struct
// Create `deleted_users` table with User's fields
db.Table("deleted_users").CreateTable(&User{})
// When Pluck
db.Table("users").Pluck("age", &ages)
//// SELECT age FROM users;
// When Query
// Search from table `deleted_users`, and fill results to []User
var deleted_users []User
db.Table("deleted_users").Find(&deleted_users)
//// SELECT * FROM deleted_users;
// When Delete
// Delete results from table `deleted_users` with search conditions
db.Table("deleted_users").Where("name = ?", "jinzhu").Delete()
//// DELETE FROM deleted_users WHERE name = 'jinzhu';
```
### Specify Table Name for Struct
You are possible to specify table name for a struct by defining method TableName
### Specify Table Name for Struct permanently with TableName method
```go
type Cart struct {
@ -664,7 +657,7 @@ db.LogMode(true)
```go
// Use your own logger
// Checkout gorm's default logger for how to format messages: https://github.com/jinzhu/gorm/blob/master/logger.go#files
// Refer 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))
// Disable log
@ -687,10 +680,12 @@ query := db.Where("name = ?", "jinzhu").First(&user)
query := db.First(&user).Limit(10).Find(&users)
//// query.Error keep the latest error happened
//// query.Errors keep all errors happened
//// If an error happened, gorm will stop do query, insert, update, delete
//// If an error happened, gorm will stop following operations
// I often use below code to do error handling in real applicatoins
err = db.Where("name = ?", "jinzhu").First(&user).Error
// I often use some code like below to do error handling when writting applicatoins
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// ...
}
```
## Advanced Usage With Query Chain
@ -700,28 +695,33 @@ Already excited about above usage? Let's see some magic!
```go
db.First(&first_article).Count(&total_count).Limit(10).Find(&first_page_articles).Offset(10).Find(&second_page_articles)
//// SELECT * FROM articles LIMIT 1; (first_article)
//// SELECT count(*) FROM articles; (count)
//// SELECT count(*) FROM articles; (total_count)
//// SELECT * FROM articles LIMIT 10; (first_page_articles)
//// SELECT * FROM articles LIMIT 10 OFFSET 10; (second_page_articles)
// Mix where conditions with inline conditions
db.Where("created_at > ?", "2013-10-10").Find(&cancelled_orders, "state = ?", "cancelled").Find(&shipped_orders, "state = ?", "shipped")
//// SELECT * FROM orders WHERE created_at > '2013/10/10' AND state = 'cancelled'; (cancelled_orders)
//// SELECT * FROM orders WHERE created_at > '2013/10/10' AND state = 'shipped'; (shipped_orders)
// Search with shared conditions from different tables
db.Where("product_name = ?", "fancy_product").Find(&orders).Find(&shopping_carts)
//// SELECT * FROM orders WHERE product_name = 'fancy_product'; (orders)
//// SELECT * FROM carts WHERE product_name = 'fancy_product'; (shopping_carts)
// Do you noticed the table is different?
db.Where("mail_type = ?", "TEXT").Find(&users1).Table("deleted_users").First(&user2)
// Search with shared conditions from different tables with specified table
db.Where("mail_type = ?", "TEXT").Find(&users1).Table("deleted_users").Find(&users2)
//// SELECT * FROM users WHERE mail_type = 'TEXT'; (users1)
//// SELECT * FROM deleted_users WHERE mail_type = 'TEXT'; (users2)
db.Where("email = ?", "x@example.org").Attrs(User{FromIp: "111.111.111.111"}).FirstOrCreate(&user)
//// SELECT * FROM users WHERE email = 'x@example.org';
//// INSERT INTO "users" (email,from_ip) VALUES ("x@example.org", "111.111.111.111") (if no record found)
// Open your mind, add more cool examples
// An example for how to use FirstOrCreate
db.Where("email = ?", "x@example.org").Attrs(User{RegisteredIp: "111.111.111.111"}).FirstOrCreate(&user)
//// 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
```
## TODO