Better README

This commit is contained in:
Jinzhu 2013-11-03 10:09:56 +08:00
parent b4981259de
commit a135087af4
2 changed files with 272 additions and 174 deletions

429
README.md
View File

@ -18,7 +18,7 @@ Yet Another ORM library for Go, aims for developer friendly
* Prevent SQL Injection
* Goroutines friendly
* Database Pool
* Convention Over Configuration (CoC)
* Convention Over Configuration
## Basic Usage
@ -55,9 +55,9 @@ type User struct { // TableName: `users`, gorm will pluralize struc
DeletedAt time.Time // DeletedAt: Time of record is deleted, refer Soft Delete for more
Email []Email // Embedded structs
BillingAddress Address // Embedded struct
BillingAddressId int64 // Embedded struct's foreign key
BillingAddressId int64 // Embedded struct BillingAddress's foreign key
ShippingAddress Address // Embedded struct
ShippingAddressId int64 // Embedded struct's foreign key
ShippingAddressId int64 // Embedded struct ShippingAddress's foreign key
}
type Email struct { // TableName: `emails`
@ -92,36 +92,26 @@ user := User{Name: "jinzhu", Age: 18, Birthday: time.Now()}
db.Save(&user)
```
## Update
```go
user.Name = "jinzhu 2"
db.Save(&user)
```
## Delete
```go
db.Delete(&user)
```
## Query
```go
// Get the first record
db.First(&user)
//// SELECT * FROM USERS LIMIT 1;
//// SELECT * FROM users LIMIT 1;
// Search table `users` are guessed from the out struct's name.
// 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's type even have it. more details later.
// Get All records
db.Find(&users)
//// SELECT * FROM USERS;
//// SELECT * FROM users;
// Using a Primary Key
db.First(&user, 10)
//// SELECT * FROM USERS WHERE id = 10;
//// SELECT * FROM users WHERE id = 10;
```
### Where (SQL like condition)
### Query With Where (SQL like condition)
```go
// Get the first matched record
@ -139,10 +129,6 @@ db.Where("name <> ?", "jinzhu").Find(&users)
db.Where("name in (?)", []string["jinzhu", "jinzhu 2"]).Find(&users)
//// SELECT * FROM users WHERE name IN ('jinzhu', 'jinzhu 2');
// IN For Primary Key
db.Where([]int64{20, 21, 22}).Find(&users)
//// SELECT * FROM users WHERE id IN (20, 21, 22);
// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
//// SELECT * FROM users WHERE name LIKE "%jin%";
@ -152,7 +138,7 @@ db.Where("name = ? and age >= ?", "jinzhu", "22").Find(&users)
//// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
```
### Where (Struct & Map)
### Query With Where (Struct & Map)
```go
// Search with struct
@ -162,12 +148,16 @@ db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// Search with map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
// IN For Primary Key
db.Where([]int64{20, 21, 22}).Find(&users)
//// SELECT * FROM users WHERE id IN (20, 21, 22);
```
### Not
### Query With Not
```go
// Not Equal
// Attribute Not Equal
db.Not("name", "jinzhu").First(&user)
//// SELECT * FROM users WHERE name <> "jinzhu" LIMIT 1;
@ -191,7 +181,7 @@ db.Not(User{Name: "jinzhu"}).First(&user)
//// SELECT * FROM users WHERE name <> "jinzhu";
```
### Inline Search Condition
### Inline Search
```go
// Find with primary key
@ -215,6 +205,117 @@ db.Find(&users, map[string]interface{}{"age": 20})
//// SELECT * FROM users WHERE age = 20;
```
### Query With Or
```
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
//// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
// Or With Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2"}).Find(&users)
//// SELECT * FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2';
// Or With Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2"}).Find(&users)
```
### Query Chains
Gorm has a chainable API, so you could write query in chain
```go
db.Where("name <> ?", "jinzhu").Where("age >= ? and role <> ?", 20, "admin").Find(&users)
//// SELECT * FROM users WHERE name <> 'jinzhu' AND age >= 20 AND role <> 'admin';
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Not("name = ?", "jinzhu").Find(&users)
```
## Update
### Update an existing struct
```go
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
//// UPDATE users SET name='jinzhu 2', age=100 WHERE id=111;
```
### Update one attribute with `Update`
```
// Update an existing struct's name if name is different
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
db.First(&user, 111).Update("name", "hello")
//// SELECT * FROM users LIMIT 1;
//// UPDATE users SET name='hello' WHERE id=111;
// Update a record
db.Table("users").Where(10).Update("name", "hello")
//// UPDATE users SET name='hello' WHERE id = 10;
```
### Update multiple attributes with `Updates`
```
// Update an existing record if have any different attributes
db.Model(&user).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18 WHERE id = 111;
// Update with Map
db.Table("users").Where(10).Updates(map[string]interface{}{"name": "hello", "age": 18})
//// UPDATE users SET name='hello', age=18 WHERE id = 10;
// Update with Struct
db.Model(User{}).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18;
```
## Delete
### Delete an existing struct
```go
db.Delete(&email)
// DELETE from emails where id=10;
```
### Batch Delete with search
```go
db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
// DELETE from emails where email LIKE "%jinhu%";
```
### Soft Delete
If a struct have DeletedAt field, it will get soft delete ability automatically!
For those don't have the filed, will be deleted from database permanently
```go
db.Delete(&user)
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
// Batch delete when search
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
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
db.Unscoped().Where("age = 20").Find(&users)
//// SELECT * FROM users WHERE age = 20;
// Permanently delete a record 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.
@ -233,24 +334,24 @@ db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
### FirstOrInit With Attrs
Attr's arguments would be used to initialize struct if not record found, but won't be used for search
Attr's arguments would be used to initialize struct if no record found, but won't be used for search
```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 have only one attribute
// Above code could be simplified if has only one attribute
db.Where(User{Name: "noexisting_user"}).Attrs("age", 20).FirstOrInit(&user)
// If record found, Attrs would be ignored
// If a record found, Attrs would be just 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}
//// User{Id: 111, Name: "Jinzhu", Age: 20}
### FirstOrInit With Assign
Assign's arguments would be used to set the struct even record found, but won't be used for search
Assign's arguments would be used to set the struct even a record found, but won't be used for search
```go
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
@ -272,21 +373,20 @@ db.Where(User{Name: "Jinzhu"}).FirstOrCreate(&user)
//// User{Id: 111, Name: "Jinzhu"}
db.FirstOrCreate(&user, map[string]interface{}{"name": "jinzhu", "age": 30})
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
//// user -> User{Id: 111, Name: "jinzhu", Age: 20}
```
### FirstOrCreate With Attrs
Attr's arguments would be used to initialize struct if not record found, but won't be used for search
Attr's arguments would be used to initialize struct if no record found, but won't be used for search
```go
// FirstOrCreate With Attrs
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
//// SELECT * FROM users WHERE name = 'non_existing';
//// User{Id: 112, Name: "non_existing", Age: 20}
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrCreate(&user)
//// User{Id: 111, Name: "Jinzhu", Age: 20}
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 30}).FirstOrCreate(&user)
//// User{Id: 111, Name: "jinzhu", Age: 20}
```
### FirstOrCreate With Assign
@ -298,192 +398,179 @@ If any record found, will assign those values to the record, and save it back to
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
//// user -> User{Id: 112, Name: "non_existing", Age: 20}
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 30}).FirstOrCreate(&user)
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 30}).FirstOrCreate(&user)
//// SELECT * FROM users WHERE name = 'jinzhu';
//// UPDATE users SET age=30 WHERE id = 111;
//// User{Id: 111, Name: "Jinzhu", Age: 30}
//// User{Id: 111, Name: "jinzhu", Age: 30}
```
### SELECT
## Select
```go
//// user -> User{Id: 111, Name: "Jinzhu", Age: 18}
//// You must noticed that the Attrs is similar to FirstOrInit with Attrs, yes?
db.Select("name, age").Find(&users)
//// SELECT name, age FROM users;
```
// Select
db.Select("name").Find(&users)
//// users -> select name from users;
## Order
// Order
```go
db.Order("age desc, name").Find(&users)
//// users -> select * from users order by age desc, name;
//// SELECT * FROM users ORDER BY age desc, name;
db.Order("age desc").Order("name").Find(&users)
//// users -> select * from users order by age desc, name;
//// SELECT * FROM users ORDER BY age desc, name;
// ReOrder
db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
//// users1 -> select * from users order by age desc;
//// users2 -> select * from users order by age;
//// SELECT * FROM users ORDER BY age desc; (users1)
//// SELECT * FROM users ORDER BY age; (users2)
```
// Limit
## Limit
```go
db.Limit(3).Find(&users)
//// users -> select * from users limit 3;
db.Limit(10).Find(&users1).Limit(20).Find(&users2).Limit(-1).Find(&users3)
//// users1 -> select * from users limit 10;
//// users2 -> select * from users limit 20;
//// users3 -> select * from users;
//// SELECT * FROM users LIMIT 3;
// Offset
//// select * from users offset 3;
// Cleanup limit with -1
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
//// SELECT * FROM users LIMIT 10; (users1)
//// SELECT * FROM users; (users2)
```
## Offset
```go
db.Offset(3).Find(&users)
db.Offset(10).Find(&users1).Offset(20).Find(&users2).Offset(-1).Find(&users3)
//// user1 -> select * from users offset 10;
//// user2 -> select * from users offset 20;
//// user3 -> select * from users;
//// SELECT * FROM users OFFSET 3;
// Or
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
//// users -> select * from users where role = 'admin' or role = 'super_admin';
// Cleanup offset with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
//// SELECT * FROM users OFFSET 10; (users1)
//// SELECT * FROM users; (users2)
```
// Count
## Count
```go
db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count)
//// users -> select * from users where name = 'jinzhu' or name = 'jinzhu 2';
//// count -> select count(*) from users where name = 'jinzhu' or name = 'jinzhu 2';
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
//// SELECT * from USERS WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (users)
//// SELECT count(*) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (count)
// CreatedAt (auto insert current time on create)
If your struct has field CreatedAt,
it will be filled with the current time when insert into database
// Set table name with Model
db.Model(User{}).Where("name = ?", "jinzhu").Count(&count)
//// SELECT count(*) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (count)
// UpdatedAt (auto update the time on save)
If your struct has field UpdatedAt,
it will be filled with the current time when update it
// Set table name with Table
db.Table("deleted_users").Count(&count)
//// SELECT count(*) FROM deleted_users;
```
// Callbacks
Below callbacks are defined now:
## Pluck
`BeforeCreate`, `BeforeUpdate`, `BeforeSave`, `AfterCreate`, `AfterUpdate`, `AfterSave`
`BeforeDelete`, `AfterDelete`
Get struct's attribute as map
Callbacks is a function defined to a model, if the function return error, will prevent the database operations.
func (u *User) BeforeUpdate() (err error) {
if u.readonly() {
err = errors.New("Read Only User")
}
return
}
// Pluck (get users's age as map)
```go
var ages []int64
db.Find(&users).Pluck("age", &ages)
//// ages -> select age from users;
// Set Table With Model
var names []string
db.Model(&User{}).Pluck("name", &names)
//// names -> select name from users;
//// SELECT name FROM users;
// Query Chains
db.Where("name <> ?", "jinzhu").Where("age >= ? and role <> ?", 20, "admin").Find(&users)
//// users -> select * from users where name <> 'jinzhu' andd age >= 20 and role <> 'admin';
// Set Table With Table
db.Table("deleted_users").Pluck("name", &names)
//// SELECT name FROM deleted_users;
```
// Create Table with struct
db.CreateTable(&User{})
## Callbacks
// Drop Table
db.DropTable(&User{})
Callback is a function defined to a struct, the function would be run when reflect a struct to database.
If the function return an error, will prevent following operations. (for example, stop inserting, updating)
// Specify Table Name
Those callbacks are defined now:
`BeforeCreate`, `AfterCreate`
`BeforeUpdate`, `AfterUpdate`
`BeforeSave`, `AfterSave`
`BeforeDelete`, `AfterDelete`
```go
func (u *User) BeforeUpdate() (err error) {
if u.readonly() {
err = errors.New("Read Only User")
}
return
}
```
## Specify Table Name
```
// When Create Table from struct
db.Table("deleted_users").CreateTable(&User{})
// When Pluck
db.Table("users").Pluck("age", &ages)
//// ages -> select age from users;
//// SELECT age FROM users;
// When Query
var deleted_users []User
db.Table("deleted_users").Find(&deleted_users)
//// deleted_users -> select * from deleted_users;
db.Table("deleted_users").Find(&deleted_user)
//// deleted_user -> select * from deleted_users limit 1;
//// SELECT * FROM deleted_users;
// Update
db.Table("users").Where(10).Update("name", "hello")
//// update users set name='hello' where id = 10;
db.Table("users").Update("name", "hello")
//// update users set name='hello';
// When Delete
db.Table("deleted_users").Where("name = ?", "jinzhu").Delete()
//// DELETE FROM deleted_users WHERE name = 'jinzhu';
```
// Updates
db.Table("users").Where(10).Updates(map[string]interface{}{"name": "hello", "age": 18})
//// update users set name='hello', age=18 where id = 10;
db.Table("users").Updates(map[string]interface{}{"name": "hello", "age": 18})
//// update users set name='hello', age=18;
db.Find(&users).Updates(User{Name: "hello", Age: 18})
//// update users set name='hello', age=18;
db.First(&user, 20).Updates(User{Name: "hello", Age: 18})
//// update users set name='hello', age=18 where id = 20;
//// object user's value would be reflected by the Updates also,
//// so you don't need to refetch the user from database
## Run Raw SQl
// Soft Delete
// For those struct have DeletedAt field, they will get soft delete ability automatically!
type Order struct {
Id int64
Amount int64
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
}
order := order{Id:10}
db.Delete(&order)
//// UPDATE orders SET deleted_at="2013-10-29 10:23" WHERE id = 10;
db.Where("amount = ?", 0).Delete(&Order{})
//// UPDATE orders SET deleted_at="2013-10-29 10:23" WHERE amount = 0;
db.Where("amount = 100").Find(&order)
//// order -> select * from orders where amount = 100 and (deleted_at is null and deleted_at <= '0001-01-02');
// And you are possible to query soft deleted orders with Unscoped method
db.Unscoped().Where("amount = 100").Find(&order)
//// order -> select * from orders where amount = 100;
// Of course, you could permanently delete a record with Unscoped
db.Unscoped().Delete(&order)
// DELETE from orders where id=10;
// Run Raw SQL
```go
db.Exec("drop table users;")
```
// Error Handling
## Error Handling
```go
query := db.Where("name = ?", "jinzhu").First(&user)
query := db.First(&user).Limit(10).Find(&users)
//// query.Error -> the last error happened
//// query.Errors -> all errors happened
//// If an error happened, gorm will stop do insert, update, delete operations
//// 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
// I often use below code to do error handling in real applicatoins
err = db.Where("name = ?", "jinzhu").First(&user).Error
```
## Advanced Usage With Query Chain
Already excited about above usage? Let's see some magic!
```go
// Already excited about the basic usage? Let's see some magic!
db.First(&first_article).Count(&total_count).Limit(10).Find(&first_page_articles).Offset(10).Find(&second_page_articles)
//// first_article -> select * from articles limit 1
//// total_count -> select count(*) from articles
//// first_page_articles -> select * from articles limit 10
//// second_page_articles -> select * from articles limit 10 offset 10
//// SELECT * FROM articles LIMIT 1; (first_article)
//// SELECT count(*) FROM articles; (count)
//// SELECT * FROM articles LIMIT 10; (first_page_articles)
//// SELECT * FROM articles LIMIT 10 OFFSET 10; (second_page_articles)
db.Where("created_at > ?", "2013/10/10").Find(&cancelled_orders, "state = ?", "cancelled").Find(&shipped_orders, "state = ?", "shipped")
//// cancelled_orders -> select * from orders where created_at > '2013/10/10' and state = 'cancelled'
//// shipped_orders -> select * from orders where created_at > '2013/10/10' and state = 'shipped'
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)
db.Model(&Order{}).Where("amount > ?", 10000).Pluck("user_id", &paid_user_ids)
//// paid_user_ids -> select user_id from orders where amount > 10000
db.Where("user_id = ?", paid_user_ids).Find(&:paid_users)
//// paid_users -> select * from users where user_id in (10, 20, 99)
db.Where("product_name = ?", "fancy_product").Find(&orders).Find(&shopping_cart)
//// orders -> select * from orders where product_name = 'fancy_product'
//// shopping_cart -> select * from carts where product_name = 'fancy_product'
// Do you noticed the search table is different for above query, yay
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)
//// users1 -> select * from users where mail_type = 'TEXT';
//// users2 -> select * from deleted_users where mail_type = 'TEXT';
//// 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)
//// user -> select * from users where email = 'x@example.org'
//// (if no record found) -> INSERT INTO "users" (email,from_ip) VALUES ("x@example.org", "111.111.111.111")
//// 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
```
@ -497,7 +584,7 @@ db.Where("email = ?", "x@example.org"').Attrs(User{FromIp: "111.111.111.111"}).F
# Author
**Jinzhu**
**jinzhu**
* <http://github.com/jinzhu>
* <wosmvp@gmail.com>

View File

@ -1095,10 +1095,21 @@ func TestT(t *testing.T) {
db.Save(&user)
var user2 User
db.Where("name in (?)", []string{"1,2"}).First(&user2).Update("name", "hhh")
debug("asdsd")
db.Where("name in (?)", []string{"1"}).First(&user2)
debug("aaadsd")
debug(user2)
db.Model(&user2).Updates(User{Name: "lala", Age: 10})
debug("aals")
//// UPDATE users SET name='hello' WHERE id=111;
var users []User
db.Find(&users, User{Age: 20})
debug(users)
// db.Where("name = '3'").Or(User{Name: "2"}).Find(&users)
db.Where("name = '3'").Or(map[string]interface{}{"name": "2"}).Find(&users)
var count int64
db.Model(User{}).Where("name = ?", "jinzhu").Count(&count)
db.Table("users").Count(&count)
debug(count)
}