From 4e1e2a0fbb56d8ae7118a39bfbfc03fa8c3fba00 Mon Sep 17 00:00:00 2001 From: Jinzhu Date: Fri, 15 Nov 2013 17:27:16 +0800 Subject: [PATCH] Update README --- README.md | 174 +++++++++++++++++++++++++++--------------------------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 9d5d82bd..a2554521 100644 --- a/README.md +++ b/README.md @@ -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