goaltools / goal

Goal is a toolkit for high productivity web development in Go language in the spirit of Revel Framework that is built around the concept of code generation.
BSD 2-Clause "Simplified" License
87 stars 3 forks source link

Feature: autoform #28

Open ghost opened 8 years ago

ghost commented 8 years ago

Creation and validation of forms is IMO the most boring part of web-development. Let's make it fun again. The use-case is:

  1. End-developer describes their data once.
  2. Goal toolkit generates the code that can be used for both form generation and its validation.

We could possibly use struct tags for that. E.g.:

type User struct {
    Name        string `autoform:"required=true,minsize=2,maxsize=100"`
    Email       string `autoform:"email=true"`
    Age         int    `autoform:"min=13,max=150"`
    CountryCode string `autoform:"length=2"`
    CardNumber  string
}

Though this is not type safe. Alternatively, we can apply a "magic methods" approach we use for the controllers.

Related functionality in other frameworks

  1. https://docs.djangoproject.com/en/1.7/topics/forms/modelforms/
  2. https://www.playframework.com/documentation/2.1.0/JavaForms
xpbliss commented 8 years ago

Looking forward to the autoform !

ghost commented 8 years ago

OK, first the validation part of autoforms. I lean toward the following option:

package models

type User struct {
    Name string `autoform:"Required(true) LenRange(2,64)"`
    Age  int `autoform:"Min(12) Max(120)"`
}

// `field` is a name of struct's field (e.g. "Name" or "Age").
// `value` is what we receive from user (it's automatically converted from `string`).
//
// the rest of parameters are extracted from struct tags.

func (m *User) Required(field, value string, required bool) error {
    if value == "" {
        return fmt.Errorf("%v must not be empty", field)
    }
    return nil
}

func (m *User) Min(field string, value int, min int) error {
    if value > min {
        return fmt.Errorf("%v must be less than %d", field, min, max)
    }
    return nil
}

func (m *User) LenRange(field, value string, min, max int) error {...}
func (m *User) Max(field string, value int, max int) error {...}
type User struct {
    *text.Validators
    *integers.Validators

    Name string `autoform:"Required(true) LenRange(2,64)"`
    Age  int `autoform:"Min(12) Max(120)"`
}

So the validators won't be built in but rather the mechanism will be extendible and customizable.


Now a few things:

`Required(true) LenRange(2,64)`
xpbliss commented 8 years ago

https://github.com/coscms/xweb/blob/master/validation/README.md https://github.com/monoculum/formam https://github.com/bluele/gforms

ghost commented 8 years ago

@xpbliss Thank you for your input, I'll check those packages out.

xpbliss commented 8 years ago

Why not release new updates for the goal?

xpbliss commented 8 years ago

How about the auto form?

hqdo commented 8 years ago

Since this a code gen tool, would it be a good idea to generate the struct from a database schema ?

hqdo commented 8 years ago

With regards to the validation case for password, confirm password, you probably need a method to validate a form in it's entirety after all fields have been validated. It may not always make sense to add a validation to a specific field. This method will probably need access to the form post parameters as well.

ghost commented 8 years ago

With regards to the validation case for password, confirm password, you probably need a method to validate a form in it's entirety after all fields have been validated.

@hqdo, yeah you're right. I'll keep this use-case in mind.

Since this a code gen tool, would it be a good idea to generate the struct from a database schema?

Yes, it is a good idea. For now the priority is to implement autoform to support:

// SomeAction is a sample action. Its form's fields will be automatically binded
// to the values in r.Request.Form.
func (c *Controller) SomeAction(form MyForm) http.Handler {
}
if errs := form.Errors() {
    ...
}
c.Context["myForm"] = form
return c.Render()

As soon as this is ready we can focus on generation of structs from DB schema.

hqdo commented 8 years ago

Why not make goal generate everything statically from a defined schema (could be the Struct in your examples) or from database ? binding, validation, forms (template) all statically generated.

This means no magic, no reflection (during runtime) and much easier for the user to customize in a real application. I've found auto generators (eg. django) impressive for quick demos, but are a pain to customise when you start needing to make changes to the runtime generated stuff.

ghost commented 8 years ago

@hqdo Well, that's how we are going to do it. Everything is generated from structs. And then can be used from actions. No runtime reflection is intended.

xpbliss commented 8 years ago

looking forward to it ! the soon the better!

hqdo commented 8 years ago

@alkchr sorry i may have misunderstood. seeing the following code from your example, i thought the form rendering would be a black box method. Sounds good if everything is generated and we can freely edit the default output.

c.Context["myForm"] = form
return c.Render()