gobuffalo 中模型的验证强大的地方

我们创建一个模型之后,往往要通过表单往模型的数据表中插入数据。当然我们一般也就会验证表单数据的合法性。
这两天在看buffalo的验证,关于某个字段是否在数据库中已经存在的处理方式很是特别。

比如说我们有个user的模型。

type User struct {
    ID              uuid.UUID `json:"id" db:"id"`
    CreatedAt       time.Time `json:"created_at" db:"created_at"`
    UpdatedAt       time.Time `json:"updated_at" db:"updated_at"`
    Username        string    `json:"username" db:"username"`
    Email           string    `json:"email" db:"email"`
    Admin           bool      `json:"admin" db:"admin"`
    PasswordHash    string    `json:"-" db:"password_hash"`
    Password        string    `json:"-" db:"-"`
    PasswordConfirm string    `json:"-" db:"-"`
}

表单提交部分的逻辑也非常简单:

func UsersRegisterPost(c buffalo.Context) error {
    // Allocate an empty User
    user := &models.User{}
    // Bind user to the html form elements
    if err := c.Bind(user); err != nil {
        return errors.WithStack(err)
    }
    // Get the DB connection from the context
    tx := c.Value("tx").(*pop.Connection)
    // Validate the data from the html form
    verrs, err := user.Create(tx)
    if err != nil {
        return errors.WithStack(err)
    }
    if verrs.HasAny() {
        // Make user available inside the html template
        c.Set("user", user)
        // Make the errors available inside the html template
        c.Set("errors", verrs.Errors)
        // Render again the register.html template that the user can
        // correct the input.
        return c.Render(422, r.HTML("users/register.html"))
    }
    // If there are no errors set a success message
    c.Flash().Add("success", "Account created successfully.")
    // and redirect to the home page
    return c.Redirect(302, "/")
}
  • 第一步,从表单绑定到模型
  • 第二步,插入数据库
  • 第三步,如果有错,将错误信息带回注册页。如果没有错,将成功信息带回home页。

数据的验证是在user模型中,默认的验证可能是这样的。

func (u *User) Validate(tx *pop.Connection) (*validate.Errors, error) {
    return validate.Validate(
        &validators.StringIsPresent{Field: u.Username, Name: "Username"},
        &validators.StringIsPresent{Field: u.Email, Name: "Email"},
    ), nil
}

就是验证了用户名和邮箱是否填了。这个肯定不能满足我们的需要。

func (u *User) Validate(tx *pop.Connection) (*validate.Errors, error) {
    return validate.Validate(
        &validators.StringIsPresent{Field: u.Username, Name: "Username"},
        &validators.StringIsPresent{Field: u.Email, Name: "Email"},
        &validators.EmailIsPresent{Name: "Email", Field: u.Email},
        &validators.StringIsPresent{Field: u.Password, Name: "Password"},
        &validators.StringsMatch{Name: "Password", Field: u.Password, Field2: u.PasswordConfirm, Message: "Passwords do not match."},
        &UsernameNotTaken{Name: "Username", Field: u.Username, tx: tx},
        &EmailNotTaken{Name: "Email", Field: u.Email, tx: tx},
    ), nil
}
  • 多加了邮箱是否是正确的邮箱
  • 多加了密码的验证,同时比较了验证密码是否相同
  • 多加了UsernameNotTaken,EmailNotTaken

这两个玩意是干啥的呢?看代码就知道了。

type UsernameNotTaken struct {
    Name  string
    Field string
    tx    *pop.Connection
}

func (v *UsernameNotTaken) IsValid(errors *validate.Errors) {
    query := v.tx.Where("username = ?", v.Field)
    queryUser := User{}
    err := query.First(&queryUser)
    if err == nil {
        // found a user with same username
        errors.Add(validators.GenerateKey(v.Name), fmt.Sprintf("The username %s is not available.", v.Field))
    }
}

type EmailNotTaken struct {
    Name  string
    Field string
    tx    *pop.Connection
}

// IsValid performs the validation check for unique emails
func (v *EmailNotTaken) IsValid(errors *validate.Errors) {
    query := v.tx.Where("email = ?", v.Field)
    queryUser := User{}
    err := query.First(&queryUser)
    if err == nil {
        // found a user with the same email
        errors.Add(validators.GenerateKey(v.Name), "An account with that email already exists.")
    }
}

从代码看就一目了然了,但是想想,这个思路还是挺奇特的。

superwen
讨论数量: 1

如果是编辑呢,编辑的时候已经是存在一个名字了,怎么过滤掉编辑前的名字

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!