From a59a3c1a88e7c91b14a649ece704830b086f47b0 Mon Sep 17 00:00:00 2001 From: Wenbo han Date: Sat, 11 Mar 2023 12:24:48 +0800 Subject: [PATCH 1/2] Add some new methods to Orm --- README.md | 6 ++-- README_zh.md | 6 ++-- contracts/database/orm.go | 5 ++- database/gorm/gorm.go | 34 ++++++++++++++++---- database/gorm/gorm_test.go | 66 ++++++++++++++++++++++++++++++-------- database/orm/model.go | 3 ++ 6 files changed, 92 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 2e6d85461..239231d2b 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,11 @@ Welcome to star, PR and issues! - [x] Mail - [x] Validation - [x] Mock +- [x] Hash +- [x] Crypt ## Roadmap -- [ ] Hash -- [ ] Crypt -- [ ] Broadcasting -- [ ] Delay Queue - [ ] Queue supports DB driver - [ ] Notifications - [ ] Optimize unit tests diff --git a/README_zh.md b/README_zh.md index da953f582..0120f9097 100644 --- a/README_zh.md +++ b/README_zh.md @@ -30,13 +30,11 @@ Laravel! - [x] 邮件 - [x] 表单验证 - [x] Mock +- [x] Hash +- [x] Crypt ## 路线图 -- [ ] Hash -- [ ] Crypt -- [ ] 广播系统 -- [ ] 延迟队列 - [ ] 队列支持 DB 驱动 - [ ] 消息通知 - [ ] 完善单元测试 diff --git a/contracts/database/orm.go b/contracts/database/orm.go index 58c489e31..b24cffd83 100644 --- a/contracts/database/orm.go +++ b/contracts/database/orm.go @@ -34,6 +34,9 @@ type Query interface { Find(dest any, conds ...any) error First(dest any) error FirstOrCreate(dest any, conds ...any) error + FirstOr(dest any, callback func() error) error + FirstOrFail(dest any) error + FirstOrNew(dest any, attributes any, values ...any) error ForceDelete(value any, conds ...any) (*Result, error) Get(dest any) error Group(name string) Query @@ -57,7 +60,7 @@ type Query interface { Table(name string, args ...any) Query Update(column string, value any) error Updates(values any) (*Result, error) - UpdateOrCreate(dest any, attributes any, values ...any) error + UpdateOrCreate(dest any, attributes any, values any) error Where(query any, args ...any) Query WithTrashed() Query With(query string, args ...any) Query diff --git a/database/gorm/gorm.go b/database/gorm/gorm.go index 137a47b08..0f30432aa 100644 --- a/database/gorm/gorm.go +++ b/database/gorm/gorm.go @@ -266,6 +266,32 @@ func (r *Query) First(dest any) error { return err } +func (r *Query) FirstOr(dest any, callback func() error) error { + err := r.instance.First(dest).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return callback() + } + + return nil +} + +func (r *Query) FirstOrFail(dest any) error { + err := r.instance.First(dest).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return orm.ErrRecordNotFound + } + + return err +} + +func (r *Query) FirstOrNew(dest any, attributes any, values ...any) error { + if len(values) > 0 { + return r.instance.Attrs(values[0]).FirstOrInit(dest, attributes).Error + } else { + return r.instance.FirstOrInit(dest, attributes).Error + } +} + func (r *Query) FirstOrCreate(dest any, conds ...any) error { var err error if len(conds) > 1 { @@ -527,12 +553,8 @@ func (r *Query) Updates(values any) (*contractsdatabase.Result, error) { }, result.Error } -func (r *Query) UpdateOrCreate(dest any, attributes any, values ...any) error { - if len(values) > 0 { - return r.instance.Assign(values[0]).FirstOrCreate(dest, attributes).Error - } else { - return r.instance.FirstOrCreate(dest, attributes).Error - } +func (r *Query) UpdateOrCreate(dest any, attributes any, values any) error { + return r.instance.Assign(values).FirstOrCreate(dest, attributes).Error } func (r *Query) Where(query any, args ...any) contractsdatabase.Query { diff --git a/database/gorm/gorm_test.go b/database/gorm/gorm_test.go index 10632316e..e035aa8b7 100644 --- a/database/gorm/gorm_test.go +++ b/database/gorm/gorm_test.go @@ -1,6 +1,7 @@ package gorm import ( + "errors" "fmt" "log" "strconv" @@ -596,7 +597,7 @@ func (s *GormQueryTestSuite) TestFirst() { s.True(user.ID > 0) var user1 User - s.Nil(query.Where("name = ?", "first_user").First(&user1)) + s.Nil(query.Where("name", "first_user").First(&user1)) s.True(user1.ID > 0) }) } @@ -606,17 +607,66 @@ func (s *GormQueryTestSuite) TestFirstOrCreate() { for driver, query := range s.queries { s.Run(driver.String(), func() { var user User - s.Nil(query.Where("avatar = ?", "first_or_create_avatar").FirstOrCreate(&user, User{Name: "first_or_create_user"})) + s.Nil(query.Where("avatar", "first_or_create_avatar").FirstOrCreate(&user, User{Name: "first_or_create_user"})) s.True(user.ID > 0) var user1 User - s.Nil(query.Where("avatar = ?", "first_or_create_avatar").FirstOrCreate(&user1, User{Name: "user"}, User{Avatar: "first_or_create_avatar1"})) + s.Nil(query.Where("avatar", "first_or_create_avatar").FirstOrCreate(&user1, User{Name: "user"}, User{Avatar: "first_or_create_avatar1"})) s.True(user1.ID > 0) s.True(user1.Avatar == "first_or_create_avatar1") }) } } +func (s *GormQueryTestSuite) TestFirstOr() { + for driver, query := range s.queries { + s.Run(driver.String(), func() { + var user User + s.Nil(query.Where("name", "first_or_user").FirstOr(&user, func() error { + user.Name = "goravel" + + return nil + })) + s.Equal(uint(0), user.ID) + s.Equal("goravel", user.Name) + + var user1 User + s.EqualError(query.Where("name", "first_or_user").FirstOr(&user1, func() error { + return errors.New("error") + }), "error") + s.Equal(uint(0), user1.ID) + }) + } +} + +func (s *GormQueryTestSuite) TestFirstOrFail() { + for driver, query := range s.queries { + s.Run(driver.String(), func() { + var user User + s.Equal(orm.ErrRecordNotFound, query.Where("name", "first_or_fail_user").FirstOrFail(&user)) + s.Equal(uint(0), user.ID) + }) + } +} + +func (s *GormQueryTestSuite) TestFirstOrNew() { + for driver, query := range s.queries { + s.Run(driver.String(), func() { + var user User + s.Nil(query.FirstOrNew(&user, User{Name: "first_or_new_name"})) + s.Equal(uint(0), user.ID) + s.Equal("first_or_new_name", user.Name) + s.Equal("", user.Avatar) + + var user1 User + s.Nil(query.FirstOrNew(&user1, User{Name: "first_or_new_name"}, User{Avatar: "first_or_new_avatar"})) + s.Equal(uint(0), user1.ID) + s.Equal("first_or_new_name", user1.Name) + s.Equal("first_or_new_avatar", user1.Avatar) + }) + } +} + func (s *GormQueryTestSuite) TestGet() { for driver, query := range s.queries { s.Run(driver.String(), func() { @@ -1279,16 +1329,6 @@ func (s *GormQueryTestSuite) TestUpdateOrCreate() { var count int64 err = query.Model(User{}).Where("name", "update_or_create_user").Count(&count) s.Equal(int64(1), count) - - var user4 User - err = query.UpdateOrCreate(&user4, User{Name: "update_or_create_user1", Avatar: "update_or_create_avatar"}) - s.Nil(err) - s.True(user4.ID > 0) - - var user5 User - err = query.Where("name", "update_or_create_user1").Where("avatar", "update_or_create_avatar").Find(&user5) - s.Nil(err) - s.True(user1.ID > 0) }) } } diff --git a/database/orm/model.go b/database/orm/model.go index f48ff6880..f21f9c408 100644 --- a/database/orm/model.go +++ b/database/orm/model.go @@ -1,6 +1,7 @@ package orm import ( + "errors" "time" "gorm.io/gorm" @@ -9,6 +10,8 @@ import ( const Associations = clause.Associations +var ErrRecordNotFound = errors.New("record not found") + type Model struct { ID uint `gorm:"primaryKey"` Timestamps From 18bbc83eb5c5db78c0b95a268fbace27b2cfb785 Mon Sep 17 00:00:00 2001 From: Wenbo han Date: Sat, 11 Mar 2023 12:39:39 +0800 Subject: [PATCH 2/2] Update orm mock --- contracts/database/mocks/Query.go | 56 +++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/contracts/database/mocks/Query.go b/contracts/database/mocks/Query.go index 3ae8a5056..255ecd0a3 100644 --- a/contracts/database/mocks/Query.go +++ b/contracts/database/mocks/Query.go @@ -194,6 +194,20 @@ func (_m *Query) First(dest interface{}) error { return r0 } +// FirstOr provides a mock function with given fields: dest, callback +func (_m *Query) FirstOr(dest interface{}, callback func() error) error { + ret := _m.Called(dest, callback) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}, func() error) error); ok { + r0 = rf(dest, callback) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // FirstOrCreate provides a mock function with given fields: dest, conds func (_m *Query) FirstOrCreate(dest interface{}, conds ...interface{}) error { var _ca []interface{} @@ -211,6 +225,37 @@ func (_m *Query) FirstOrCreate(dest interface{}, conds ...interface{}) error { return r0 } +// FirstOrFail provides a mock function with given fields: dest +func (_m *Query) FirstOrFail(dest interface{}) error { + ret := _m.Called(dest) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(dest) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// FirstOrNew provides a mock function with given fields: dest, attributes, values +func (_m *Query) FirstOrNew(dest interface{}, attributes interface{}, values ...interface{}) error { + var _ca []interface{} + _ca = append(_ca, dest, attributes) + _ca = append(_ca, values...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}, interface{}, ...interface{}) error); ok { + r0 = rf(dest, attributes, values...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // ForceDelete provides a mock function with given fields: value, conds func (_m *Query) ForceDelete(value interface{}, conds ...interface{}) (*database.Result, error) { var _ca []interface{} @@ -594,15 +639,12 @@ func (_m *Query) Update(column string, value interface{}) error { } // UpdateOrCreate provides a mock function with given fields: dest, attributes, values -func (_m *Query) UpdateOrCreate(dest interface{}, attributes interface{}, values ...interface{}) error { - var _ca []interface{} - _ca = append(_ca, dest, attributes) - _ca = append(_ca, values...) - ret := _m.Called(_ca...) +func (_m *Query) UpdateOrCreate(dest interface{}, attributes interface{}, values interface{}) error { + ret := _m.Called(dest, attributes, values) var r0 error - if rf, ok := ret.Get(0).(func(interface{}, interface{}, ...interface{}) error); ok { - r0 = rf(dest, attributes, values...) + if rf, ok := ret.Get(0).(func(interface{}, interface{}, interface{}) error); ok { + r0 = rf(dest, attributes, values) } else { r0 = ret.Error(0) }