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 save
  • BeforeCreate: the callback function is performed before a create
  • BeforeUpdate: the callback function is performed before an update
  • BeforeDestroy: the callback function is performed before a delete
  • AfterSave: the callback function is performed after a save
  • AfterCreate: the callback function is performed after a create
  • AfterUpdate: the callback function is performed after an update
  • AfterDestroy: the callback function is performed after a delete
  • AfterFind: 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.

results matching ""

    No results matching ""