Skip to content

Commit

Permalink
Add method to update (one or multiple rows) specifying the columns to…
Browse files Browse the repository at this point in the history
… be updated (#437)

* Add method to update (one or multiple rows) specifying the columns to be updated

* Improve testing (more test cases)
  • Loading branch information
gustavotero7 authored and stanislas-m committed Oct 2, 2019
1 parent 3ef63dc commit c21e5c8
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 0 deletions.
44 changes: 44 additions & 0 deletions executors.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,50 @@ func (c *Connection) Update(model interface{}, excludeColumns ...string) error {
})
}

// UpdateColumns writes changes from an entry to the database, including only the given columns
// or all columns if no column names are provided.
// It updates the `updated_at` column automatically.
//
// If model is a slice, each item of the slice is updated in the database.
func (c *Connection) UpdateColumns(model interface{}, columnNames ...string) error {
sm := &Model{Value: model}
return sm.iterate(func(m *Model) error {
return c.timeFunc("Update", func() error {
var err error

if err = m.beforeSave(c); err != nil {
return err
}
if err = m.beforeUpdate(c); err != nil {
return err
}

tn := m.TableName()

cols := columns.Columns{}
if len(columnNames) > 0 && tn == sm.TableName() {
cols = columns.NewColumnsWithAlias(tn, m.As)
cols.Add(columnNames...)

} else {
cols = columns.ForStructWithAlias(model, tn, m.As)
}
cols.Remove("id", "created_at")

m.touchUpdatedAt()

if err = c.Dialect.Update(c.Store, m, cols); err != nil {
return err
}
if err = m.afterUpdate(c); err != nil {
return err
}

return m.afterSave(c)
})
})
}

// Destroy deletes a given entry from the database.
//
// If model is a slice, each item of the slice is deleted from the database.
Expand Down
117 changes: 117 additions & 0 deletions executors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,123 @@ func Test_Update(t *testing.T) {
})
}

func Test_UpdateColumns(t *testing.T) {
if PDB == nil {
t.Skip("skipping integration tests")
}
transaction(func(tx *Connection) {
r := require.New(t)

user := User{Name: nulls.NewString("Mark")}
tx.Create(&user)

r.NotZero(user.CreatedAt)
r.NotZero(user.UpdatedAt)

user.Name.String = "Fulano"
user.UserName = "Fulano"
err := tx.UpdateColumns(&user, "user_name") // Update UserName field/column only
r.NoError(err)

r.NoError(tx.Reload(&user))
r.Equal(user.Name.String, "Mark") // Name column should not be updated
r.Equal(user.UserName, "Fulano")
})
}

func Test_UpdateColumns_MultipleColumns(t *testing.T) {
if PDB == nil {
t.Skip("skipping integration tests")
}
transaction(func(tx *Connection) {
r := require.New(t)

user := User{Name: nulls.NewString("Mark"), UserName: "Sagan", Email: "test@example.com"}
tx.Create(&user)

r.NotZero(user.CreatedAt)
r.NotZero(user.UpdatedAt)

user.Name.String = "Ping"
user.UserName = "Pong"
user.Email = "fulano@example"
err := tx.UpdateColumns(&user, "name", "user_name") // Update multiple columns
r.NoError(err)

r.NoError(tx.Reload(&user))
r.Equal(user.Name.String, "Ping")
r.Equal(user.UserName, "Pong")
r.Equal(user.Email, "test@example.com") // Email should not be updated
})
}

func Test_UpdateColumns_All(t *testing.T) {
if PDB == nil {
t.Skip("skipping integration tests")
}
transaction(func(tx *Connection) {
r := require.New(t)

user := User{Name: nulls.NewString("Mark"), UserName: "Sagan"}
tx.Create(&user)

r.NotZero(user.CreatedAt)
r.NotZero(user.UpdatedAt)

user.Name.String = "Ping"
user.UserName = "Pong"
user.Email = "ping@pong.com"
err := tx.UpdateColumns(&user) // Update all columns
r.NoError(err)

r.NoError(tx.Reload(&user))
r.Equal(user.Name.String, "Ping")
r.Equal(user.UserName, "Pong")
r.Equal(user.Email, "ping@pong.com")
})
}

func Test_UpdateColumns_With_Slice(t *testing.T) {
if PDB == nil {
t.Skip("skipping integration tests")
}
transaction(func(tx *Connection) {
r := require.New(t)

user := Users{
{
Name: nulls.NewString("Mark"),
UserName: "Ping",
},
{
Name: nulls.NewString("Larry"),
UserName: "Pong",
},
}
tx.Create(&user)

r.NotZero(user[0].CreatedAt)
r.NotZero(user[0].UpdatedAt)

r.NotZero(user[1].CreatedAt)
r.NotZero(user[1].UpdatedAt)

user[0].Name.String = "Fulano"
user[0].UserName = "Thor"
user[1].Name.String = "Fulana"
user[1].UserName = "Freya"

err := tx.UpdateColumns(&user, "name") // Update Name field/column only
r.NoError(err)

r.NoError(tx.Reload(&user))
r.Equal(user[0].Name.String, "Fulano")
r.Equal(user[0].UserName, "Ping") // UserName should not be updated
r.Equal(user[1].Name.String, "Fulana")
r.Equal(user[1].UserName, "Pong") // UserName should not be updated
})
}

func Test_Update_With_Slice(t *testing.T) {
if PDB == nil {
t.Skip("skipping integration tests")
Expand Down

0 comments on commit c21e5c8

Please sign in to comment.