Writing Callbacks
A callback is an action that is performed upon some data change. An example would be that when a new user is created, a message is sent through a message queue for some other backend process. The pattern for writing callbacks is very similar to that of writing validations: insert the callback into your model definition and magic happens. Callbacks are available as:
BeforeSave
: the callback function is performed before the saveBeforeCreate
: the callback function is performed before a createBeforeUpdate
: the callback function is performed before an updateBeforeDestroy
: the callback function is performed before a deleteAfterSave
: the callback function is performed after a saveAfterCreate
: the callback function is performed after a createAfterUpdate
: the callback function is performed after an updateAfterDestroy
: the callback function is performed after a deleteAfterFind
: the callback function is performed after a find
The same pattern applies here with the relationship of Save
to Create
and Update
. Save
is just a helper over the other two. For demonstrating this use, we need to make an update to our User
model by adding an Image
field. The Image
field will be a URL to an online image. However, we want to download and save a copy of that image locally for logging reasons.
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"`
Title string `json:"title" db:"title"`
FirstName string `json:"first_name" db:"first_name"`
LastName string `json:"last_name" db:"last_name"`
Bio string `json:"bio" db:"bio"`
BirthYear string `json:"birth_year" db:"birth_year"`
Location string `json:"location" db:"location"`
Image string `json:"image" db:"image"`
}
...
func (u *User) Validate(tx *pop.Connection) (*validate.Errors, error) {
return validate.Validate(
&validators.StringIsPresent{Field: u.Title, Name: "Title"},
&validators.StringIsPresent{Field: u.FirstName, Name: "FirstName"},
&validators.StringIsPresent{Field: u.LastName, Name: "LastName"},
&validators.StringIsPresent{Field: u.Bio, Name: "Bio"},
&validators.StringIsPresent{Field: u.Location, Name: "Location"},
&LocationValidator{Field: u.Location, Name: "Location"},
&validators.URLIsPresent{Field: u.Image, Name: "Image"},
), nil
}
Now create the migration:
$ soda generate fizz add_image
v3.41.1
> migrations/20180110213028_add_image.up.fizz
> migrations/20180110213028_add_image.down.fizz
And update the migration file. Here's the up:
add_column("users", "image", "string", {"default": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/04/MarvelLogo.svg/1200px-MarvelLogo.svg.png"})
And run the migration:
$ soda migrate up -e development
v3.41.1
> add_image
0.0038 seconds
dumped schema for ./development.sqlite
Check the database to make sure that it has been added (I'm sure that it has). Now, let's write a callback that will go and grab the image to store it locally. In the model definition add
func (u *User) AfterSave(tx *pop.Connection) error {
tokened := strings.Split(u.Image, "/")
fileName := tokened[len(tokened)-1]
output, err := os.Create(fileName)
if err != nil {
return err
}
defer output.Close()
response, err := http.Get(u.Image)
if err != nil {
return err
}
defer response.Body.Close()
n, err := io.Copy(output, response.Body)
if err != nil {
return err
}
}
Here's a sample program to test it:
package main
import (
"bitbucket.org/pop-book/models"
"github.com/gobuffalo/pop"
"log"
)
func main() {
tx, err := pop.Connect("development")
if err != nil {
log.Panic(err)
}
peter := models.User{Title: "Mr.", FirstName: "Peter", LastName: "Parker", Bio: "Student", BirthYear: "1946", Location: "Queens, New York", Image: "https://upload.wikimedia.org/wikipedia/en/3/35/Amazing_Fantasy_15.jpg"}
_, err = tx.ValidateAndSave(&peter)
if err != nil {
log.Panic(err)
}
}
Compile and run it, then check your local file system:
$ ls
Amazing_Fantasy_15.jpg development.sqlite main main.go migrations models
There's the jpg we're looking for.