3
votes

I'm almost certainly doing this backwards in some way. There's a whole lot of confusion around this feature apparently around the interwebs.

I am just trying to create a foreign key relationship to where a Book model has one Author. Using MySQL 8.

When I query a book, I want the author object to be returned with it, so it may be that my query is also wrong... Here's where I am at.

models.go

package main

import (
    "github.com/jinzhu/gorm"
    uuid "github.com/satori/go.uuid"
    "time"
)

/*        Base Models
============================= */

type Base struct {
    ID        uuid.UUID  `gorm:"primary_key" json:"id"`
    CreatedAt *time.Time `json:"created_at"`
    UpdatedAt *time.Time `gorm:"index" json:"updated_at"`
    DeletedAt *time.Time `gorm:"index" json:"deleted_at"`
}

type BaseBook struct {
    ISBN        int64     `gorm:"primary_key;auto_increment:false;" json:"isbn"`
    Description string    `gorm:"type:longtext" json:"description"`
    Author      Author    `gorm:"foreignkey:AuthorId" json:"author"`
    AuthorId    uuid.UUID `json:"author_id"`
    Title       string    `json:"title"`
}

// BeforeCreate -  Sets a UUID instead of incremental numeric ID.
func (base *Base) BeforeCreate(scope *gorm.Scope) error {
    id := uuid.NewV4()
    return scope.SetColumn("ID", id)
}

/*          Tables
============================= */

type Book struct {
    Base
    BaseBook
    Events []Event `gorm:"foreignkey:ISBN"`
}

type Event struct {
    BaseBook
}

type Author struct {
    Base
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
    Middle    string `json:"middle"`
}

Is there an issue with how I'm creating the records here in the seed function?

handlers.go

// GetSeedDatabase - Start seeding database with test data.
func GetSeedDatabase(w http.ResponseWriter, r *http.Request) {
    authors := getSeedDataAuthors()
    books := getSeedDataBooks()
    for _, author := range authors {
        for _, book := range books {
            if book.Author == author.ID.String() {
                newAuthor := Author{
                    Base: Base{
                        ID: author.ID,
                    },
                    FirstName: author.FirstName,
                    LastName:  author.LastName,
                    Middle:    author.Middle,
                }
                newBook := Book{
                    Base: Base{
                        ID: uuid.NewV4(),
                    },
                    BaseBook: BaseBook{
                        AuthorId:    newAuthor.ID,
                        ISBN:        book.ISBN,
                        Title:       book.Title,
                        Description: book.Description,
                    },
                }

                MySQL.Create(newAuthor)
                MySQL.Create(newBook)
            }
        }
    }

    var allAuthors []Author
    MySQL.Find(&allAuthors)

    var allBooks []Book
    MySQL.Find(&allBooks)

    if len(allAuthors) > 0 && len(allBooks) > 0 {
        w.Write([]byte("successfully seeded database"))
        return
    }

    w.Write([]byte("something went wrong seeding database"))
}

// GetAllBooks - Get all books in database.
func GetAllBooks(w http.ResponseWriter, r *http.Request) {
    var allBooks []Book
    MySQL.Find(&allBooks).Related(Author{})
    json.NewEncoder(w).Encode(allBooks)
}

When I hit the GetAllBooks books endpoint the response looks like this:

[
  {
    "id": "0c266efa-3c3c-4bb8-abe6-dbb9b8000cd8",
    "created_at": null,
    "updated_at": null,
    "deleted_at": null,
    "isbn": 9781593275846,
    "description": "JavaScript lies at the heart of almost every modern web application, from social apps to the newest browser-based games. Though simple for beginners to pick up and play with, JavaScript is a flexible, complex language that you can use to build full-scale applications.",
    "author": {
      "id": "00000000-0000-0000-0000-000000000000",
      "created_at": null,
      "updated_at": null,
      "deleted_at": null,
      "first_name": "",
      "last_name": "",
      "middle": ""
    },
    "author_id": "ba0d07bf-1a12-4742-989b-cdaa4c05ad72",
    "title": "Eloquent JavaScript, Second Edition",
    "Events": null
  },
  {
    "id": "0ea3e0fc-f917-46ea-b55b-d97e916bacd9",
    "created_at": null,
    "updated_at": null,
    "deleted_at": null,
    "isbn": 9781491904244,
    "description": "No matter how much experience you have with JavaScript, odds are you don’t fully understand the language. As part of the \"You Don’t Know JS\" series, this compact guide focuses on new features available in ECMAScript 6 (ES6), the latest version of the standard upon which JavaScript is built.",
    "author": {
      "id": "00000000-0000-0000-0000-000000000000",
      "created_at": null,
      "updated_at": null,
      "deleted_at": null,
      "first_name": "",
      "last_name": "",
      "middle": ""
    },
    "author_id": "c7f6e1b8-9e42-4666-bba8-dd9b229a362e",
    "title": "You Don't Know JS",
    "Events": null
  }
]

As you can see, the Author objects returned are their empty versions of themselves.

I'm still trying different combinations of foreign key examples I've found elsewhere but still no luck.

Update

So the first issue is that GORM just straight up doesn't add foreign keys if you try to add them via annotations on the models (like I have). Maybe that's a MySQL8 thing? I'm not sure, but I have added the relation manually like so:

// addConstraints - Since GORM annotations are still broken for foreign keys we do it manually here.
func addConstraints(db *gorm.DB) *gorm.DB {
    log.Println("adding db table constraints")
    db.Model(Book{}).AddForeignKey(
        "author_id",
        "authors(id)",
        "SET DEFAULT",
        "SET DEFAULT",
    )

    return db
}

Now I can preload the author data to be returned with the books like:

// GetAllBooks - Get all books in database.
func GetAllBooks(w http.ResponseWriter, r *http.Request) {
    var allBooks []Book
    MySQL.Preload("Author").Find(&allBooks)
    json.NewEncoder(w).Encode(allBooks)
}
2

2 Answers

2
votes

Refer to this answer: https://stackoverflow.com/a/60003896/8249447

You need to use gorm's Preload function.

For example:

gormDB.Preload("Author").Find(&allBooks)

The official document: Gorm's Preloading

-1
votes

try using preload http://gorm.io/docs/preload.html

I will give an example from my code

type Client struct { ID strfmt.UUID4gorm:"primary_key;type:uuid;default:uuid_generate_v4()" ApplicationId stringgorm:"type:varchar(40); not null" Phone stringjson:"phone" Email stringjson:"email" Beneficiaries []Beneficiarygorm:"foreignkey:client_id" json:"beneficiaries" Address stringgorm:"null" json:"address" } type Beneficiary struct { ID strfmt.UUID4gorm:"primary_key;type:uuid;default:uuid_generate_v4()" FullName string ClientId strfmt.UUID4gorm:"type:uuid REFERENCES app_rsv_client(id)" } func (dbm DbManager) GetClientByAppId(appId string) (*model.Client, error) { var client model.Client err := dbm.DB.Preload("Beneficiaries").Where("application_id = ?", appId).Find(&client).Error return &client, err }