ozzo-dbx icon indicating copy to clipboard operation
ozzo-dbx copied to clipboard

Updating Table Overrides null values in struct

Open tonespy opened this issue 7 years ago • 1 comments

Hi.

I am developer transitioning from Node.js to go and it has been smooth. But, I'm finding it hard to update a model without overwriting the role in the db with the null data. Example:

I've a User Struct:

// User represents a user's record
type User struct {
	ID            int       `json:"id" db:"pk,id"`
	Firstname     string    `json:"firstname" db:"firstname"`
	Lastname      string    `json:"lastname" db:"lastname"`
	Email         string    `json:"email" db:"email"`
	IsDeleted     bool      `json:"isDeleted" db:"isDeleted"`
	Password      string    `json:"-" db:"password"`
	Country       string    `json:"country" db:"country"`
	State         string    `json:"state" db:"state"`
	FacebookToken *string   `json:"fbToken" db:"fbToken"`
	TwitterToken  *string   `json:"twitterToken" db:"twitterToken"`
	CreatedAt     time.Time `json:"createdAt" db:"createdAt"`
	UpdatedAt     time.Time `json:"updatedAt" db:"updatedAt"`
}

// Validate helps with validating the user field
func (u User) Validate() error {
	return validation.ValidateStruct(&u,
		validation.Field(&u.Firstname, validation.Required, validation.Length(0, 120)),
		validation.Field(&u.Lastname, validation.Required, validation.Length(0, 120)),
		validation.Field(&u.Password, validation.Required, validation.Length(8, 120), is.Alphanumeric),
		validation.Field(&u.Email, validation.Required, is.Email),
		validation.Field(&u.Country, validation.Required),
		validation.Field(&u.State, validation.Required),
	)
}

// IsPassValid - Verify if password is valid
func (u User) IsPassValid(pass string) error {
	return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(pass))
}

// GenerateHash - Helper function to help generate password hash. It returns error if hasing fails, and user if successful.
func (u *User) GenerateHash() error {
	bytes, err := bcrypt.GenerateFromPassword([]byte(u.Password), 14)
	u.Password = string(bytes)
	return err
}

// TableName - Utilized by ozzo-dbx for referencing main table name
func (u User) TableName() string {
	return "users"
}

So, to create a user, I did:

func userManipulation(db *dbx.DB) {
	user := User{
		Firstname: randomdata.FirstName(randomdata.Male),
		Lastname:  randomdata.LastName(),
		Email:     randomdata.Email(),
		Password:  "password",
		Country:   randomdata.Country(randomdata.FullCountry),
		State:     randomdata.State(randomdata.Large),
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	if err := user.Validate(); err != nil {
		panic(err)
	}

	if err := user.GenerateHash(); err != nil {
		panic(err)
	}

	if err := db.Model(&user).Insert(); err != nil {
		// panic(err)
		processErr(err)
	}
  fmt.Println(user)
}

And this gives me:

{
  "id": 2,
  "firstname": "William",
  "lastname": "White",
  "email": "[email protected]",
  "isDeleted": false,
  "country": "Rwanda",
  "state": "Missouri",
  "fbToken": null,
  "twitterToken": null,
  "createdAt": "2018-05-01T11:21:18.080069+01:00",
  "updatedAt": "2018-05-01T11:21:18.08007+01:00"
}

But, whenever I try to update just a specific field, such as in this case:

func userUpdateManipulation(db *dbx.DB) {
	user := User{
		ID:        1,
		Firstname: randomdata.FirstName(randomdata.Female),
		UpdatedAt: time.Now(),
	}

	if err := db.Model(&user).Update(); err != nil {
		panic(err)
	}

	byte, err := json.Marshal(user)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(byte))
}

The data I didn't add are overwritten. How can I update without hardcoding which fields needs to be updated.

{
  "id": 2,
  "firstname": "Addison",
  "lastname": "",
  "email": "",
  "isDeleted": false,
  "country": "",
  "state": "",
  "fbToken": null,
  "twitterToken": null,
  "createdAt": "0001-01-01T00:00:00Z",
  "updatedAt": "2018-05-01T11:38:52.036203+01:00"
}

This is what I had to do to fix that. If you think there is a better way, please, do let me know:

// ExcludeData - Parameters to exclude while updating
func (u User) ExcludeData() []string {
	mainExlude := []string{}
	if len(u.Firstname) <= 0 { mainExlude = append(mainExlude, "Firstname") }
	if len(u.Lastname) <= 0 { mainExlude = append(mainExlude, "Lastname") }
	if len(u.Email) <= 0 { mainExlude = append(mainExlude, "Email") }
	if len(u.Password) <= 0 { mainExlude = append(mainExlude, "Password") }
	if len(u.Country) <= 0 { mainExlude = append(mainExlude, "Country") }
	if len(u.State) <= 0 { mainExlude = append(mainExlude, "Country") }
	if u.FacebookToken == nil { mainExlude = append(mainExlude, "FacebookToken") }
	if u.TwitterToken == nil { mainExlude = append(mainExlude, "TwitterToken") }
	if time.Time.IsZero(u.CreatedAt) { mainExlude = append(mainExlude, "CreatedAt") }
	if time.Time.IsZero(u.UpdatedAt) { mainExlude = append(mainExlude, "UpdatedAt") }
	mainExlude = append(mainExlude, "IsDeleted")
	return mainExlude
}

What would you suggest I do to get this done. Thanks

tonespy avatar May 01 '18 10:05 tonespy

The update works as a crud operation so it updates all the data available in the object. To update only specific fields you would have to define them as described in https://github.com/go-ozzo/ozzo-dbx/blob/master/README.md#building-data-manipulation-queries . Alternatively load the data into a struct, update the fields there and write it all back using a common update ()

kPshi avatar Jul 24 '18 21:07 kPshi