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.")
}
}
从代码看就一目了然了,但是想想,这个思路还是挺奇特的。
如果是编辑呢,编辑的时候已经是存在一个名字了,怎么过滤掉编辑前的名字