And no it isn't all the time
Though it may be most of the time.
I am writing a silly website for my daughter who is a high school diver. I think the site has some interesting potential if I can actually do enough to make it interesting. I am just getting rolling. Because I am me, I elected a quick, dirty, clean way to accomplish a thing.
const ( Athlete UserType = "Athlete" Coach UserType = "Coach" Owner UserType = "Owner" Supporter UserType = "Supporter" ) type User struct { gorm.Model Email string gorm:"unique;not null" json:"email" Password string gorm:"not null" json:"-" UserName string gorm:"unique;not null" json:"username" FirstName string gorm:"not null" json:"firstname" LastName string gorm:"not null" json:"lastname" Admin bool gorm:"default:false" json:"admin" UserType UserType gorm:"type:text;not null;check:user_type IN ('Athlete', 'Coach', 'Owner', 'Supporter')" json:"usertype" }
So I have Users obviously. And the users can be of various types. Probably only four types forever. I knew when I did this the RIGHT way was to create a UserTypes DB table. But I decided against that figuring expediency was fine.
This is true.
But this is wrong. I am chugging along and felt this nagging at me. I reached out to a DBA buddy who works at Amazon and he correctly, though in a heartless, uncaring fashion confirmed I was doing it wrong. So I figured before I got too far in, I'd make the change.
I went to this.
type User struct { gorm.Model Email string gorm:"unique;not null" json:"email" Password string gorm:"not null" json:"-" UserName string gorm:"unique;not null" json:"username" FirstName string gorm:"not null" json:"firstname" LastName string gorm:"not null" json:"lastname" Admin bool gorm:"default:false" json:"admin" UserTypeID uint gorm:"not null" json:"usertype_id" UserType UserType gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user_type" } type UserType struct { gorm.Model Name string gorm:"unique;not null" json:"name" }
So little changed.
EVERYTHING CHANGED
The first usable page is sign up. Well, that had a drop down of the four constants populated. But that needed to become a query. Easy enough. I figured an hour, or two tops to get it all rolling. My UserCreate should have been working. Look at it. Tell me it should have been working.
// UserCreate creates a new user func UserCreate(email string, password string, username string, firstname string, lastname string, usertypeName string) (*User, error) { hshPasswd, err := helpers.HashPassword(password) if err != nil { log.Printf("Error hashing password: %v", err) return nil, errors.New("Error hashing password") } var usertype UserType if err := db.Database.Where("name = ?", usertypeName).First(&usertype).Error; err != nil { log.Printf("Error finding usertype: %v", err) return nil, errors.New("Error finding usertype") } entry := User{ Email: email, Password: hshPasswd, UserName: username, FirstName: firstname, LastName: lastname, UserType: usertype, } result := db.Database.Create(&entry) if result.Error != nil { log.Printf("Error creating user: %v", result.Error) return nil, errors.New("Error creating user") } return &entry, nil }
That wouldn't create a user.
func TestUserCreate(t *testing.T) { LoadEnv() db.Connect() usertype := UserType{Name: "Test User Type"} db.Database.Create(&usertype) // Test data email := "test@example.com" password := "test_password" username := "test_username" firstname := "Test" lastname := "User" usertypeName := usertype.Name // Call UserCreate function user, err := UserCreate(email, password, username, firstname, lastname, usertypeName) // Assert no error occurred assert.NoError(t, err) // Assert the created user has the expected values assert.NotNil(t, user) assert.NotZero(t, user.ID) assert.Equal(t, email, user.Email) assert.Equal(t, username, user.UserName) assert.Equal(t, firstname, user.FirstName) assert.Equal(t, lastname, user.LastName) assert.Equal(t, usertype.ID, user.UserTypeID) // Check if the password is hashed assert.NotEqual(t, password, user.Password) assert.True(t, helpers.CheckPasswordHash(password, user.Password)) // Cleanup db.Database.Unscoped().Delete(user) db.Database.Unscoped().Delete(&usertype) }
That wouldn't test creating a user.
Failure.
BUZZZZ!!!
Wrong.
Except it was always right. The problem is here. It's just not in these functions which work fine.
So where is the problem?
UserType UserType gorm:"type:text;not null;check:user_type IN ('Athlete', 'Coach', 'Owner', 'Supporter')" json:"usertype"
You see, when that first table got migrated it put a CHECK constraint on the Users table. So even though the code above was right, until I blew the DB away, it wasn't. And I figure there is a way other than blowing it away to fix it, but I had nothing in it and blowing it away made me feel good.
The moral of the story is it took 12 hours of time to get the site PRECISELY back to where it was before I started.
And now I waiver on the doing it right or being satisfied answer :).
Hoping being satisfied wins out next time.
Cheers.