I am a PHP programmer and I have just learned Golang for weeks. I am writing REST API to get post information from my 'posts' table. I have the FindPostByID method in posts_controller. This controller uses Post struct which is model
func (p *Post) FindPostByID(db *gorm.DB, pid uint64) (*Post, error) {
var err error
err = db.Debug().Model(&Post{}).Where("id = ?", pid).Take(&p).Error
if err != nil {
return &Post{}, err
}
if p.ID != 0 {
err = db.Debug().Model(&User{}).Where("id = ?", p.AuthorID).Take(&p.Author).Error
if err != nil {
return &Post{}, err
}
}
return p, nil
}
But now I want to add method FindByID into parent struct, such as ParentModel because when I have articles_controller I can use FindByID method in parent struct to get an article info. So I have the following code in parent struct
type ParentModel struct {
}
func (m *ParentModel) FindByID(db *gorm.DB, uid uint64) (*ParentModel, error) {
var err error
err = db.Debug().Model(ParentModel{}).Where("id = ?", uid).Take(&m).Error
if err != nil {
return &ParentModel{}, err
}
if gorm.IsRecordNotFoundError(err) {
return &ParentModel{}, errors.New("User Not Found")
}
return m, err
}
And I change in the Post struct like following:
type Post struct {
ParentModel
ID uint64 `gorm:"primary_key;auto_increment" json:"id"`
Title string `gorm:"size:255;not null;unique" json:"title"`
Content string `gorm:"size:255;not null;" json:"content"`
Author User `json:"author"`
AuthorID uint32 `sql:"type:int REFERENCES users(id)" json:"author_id"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}
I change FindPostByID method in posts_controller as well:
func (server *Server) GetPost(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pid, err := strconv.ParseUint(vars["id"], 10, 64)
if err != nil {
responses.ERROR(w, http.StatusBadRequest, err)
return
}
post := models.Post{}
postReceived, err := post.FindByID(server.DB, pid)
if err != nil {
responses.ERROR(w, http.StatusInternalServerError, err)
return
}
responses.JSON(w, http.StatusOK, postReceived)
}
When I run my program, have the err: Table 'golang_rest_api.parent_models' doesn't exist.
How I can do to use inheritance method like PHP language
The error Table 'golang_rest_api.parent_models' doesn't exist means that no table named parent_models exists in your database.
This is because gorm by default uses the pluralised camelcase name of your struct, which you supplied in gorm's Model() method, as its table's name.
Gorm provides in-built support for many kinds of associations which you can find here. I would also like to ask you to complete your ParentModel struct's definition. It's empty right now.
Related
Golang mysql row.Scan(&pointerAddress) not populating fields.
Showing me this when i send request so omitempty does work.
If I fill all User struct fields to row.Scan(&user.Email, etc...) only then show values in result.
Code :
type User struct{
Id int `json:"id"`
Email string `json:"email"`
Password string `json:"password"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
RememberToken string `json:"remember_token"`
Phone int64 `json:"phone"`
AgreeTerms int8 `json:"agree_terms"`
AllowPromotionalOffers int8 `json:"allow_promotional_offers"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
LandlordId int64 `json:"landlord_id"`
RenterId int64 `json:"renter_id"`
}
My Login Function :
func Login(w http.ResponseWriter, r *http.Request) {
user := models.User{}
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
fmt.Println(err)
}
row, err := database.DB.Query("SELECT * FROM users WHERE email=?", user.Email)
if err != nil {
fmt.Errorf("%w", err)
}
defer row.Close()
for row.Next() {
err = row.Scan(&user.Id, &user.Email, &user.Password)
if err != nil {
fmt.Errorf("%w", err)
}
}
err = json.NewEncoder(w).Encode(user)
if err != nil {
fmt.Errorf("%w", err)
}
}
Try to initialize your struct inside the for statement, if your resultset is a query rows (maybe a slices of structs):
var users []*models.User
for row.Next() {
user := new(models.User)
err = row.Scan(&user.Id, &user.Email, &user.Password)
users = append(users, user)
if err != nil {
fmt.Errorf("%w", err)
}
}
......
You don't need to loop if you're using a QueryRow resultSet
row, err := database.DB.QueryRow("SELECT id, email, password FROM users WHERE email=?",
user.Email).Scan(&user.Id, &user.Email, &user.Password)
As the best practice, you should avoid using "*" if not essential. ;)
It's a little verbose but it works.
When I loaded my data from Cloud Storage into Bigquery, the click_url field turned into Record type when get created even though it's a string (maybe because of the noindex not sure tho). When I try to insert the data into BigQuery using Inserter. I got this error message:
Cannot convert std::string to a record field:optional .Msg_0_CLOUD_QUERY_TABLE.Msg_1_CLOUD_QUERY_TABLE_click_url click_url = 1
Table in bigquery:
Schema:
Here's the code:
type Product struct {
Name string `datastore:"name" bigquery:"name"`
ClickUrl string `datastore:"click_url,noindex" bigquery:"click_url"`
DateAdded time.Time `datastore:"date_added" bigquery:"date_added"`
}
func insertRows(data interface{}) error {
projectID := "my-project-id"
datasetID := "mydataset"
tableID := "mytable"
ctx := context.Background()
client, err := bigquery.NewClient(ctx, projectID)
if err != nil {
return fmt.Errorf("bigquery.NewClient: %v", err)
}
defer client.Close()
inserter := client.Dataset(datasetID).Table(tableID).Inserter()
if err := inserter.Put(ctx, data); err != nil {
return err
}
return nil
}
func main() {
product := Product{"product_name", "click_url", "date_added_value"} // Example data from datastore
if err := insertRows(product); err != nil {
fmt.Println(err)
}
}
What should I put on the entity tag "bigquery:click_url" to make this work?
Because the ClickUrl in BigQuery is a STRUCT type, which has key-value pairs.
Maybe try this?
type Product struct {
Name string `datastore:"name" bigquery:"name"`
ClickUrl struct {
String string
Text string
Provided string
} `datastore:"click_url,noindex" bigquery:"click_url"`
DateAdded time.Time `datastore:"date_added" bigquery:"date_added"`
}
Disclaimer: go lang noob here
I've my user struct as
type User struct {
ID uint32 `json:"id"`
FirstName string `json:"firstName" binding:"required"`
LastName string `json:"lastName"`
Email string `json:"email" binding:"required,email,uniqueModelValue=users email"`
Active bool `json:"active"`
Password string `json:"password,omitempty" binding:"required,gte=8"`
UserType string `json:"userType" binding:"oneof=admin backoffice principal staff parent student"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
/POST /users handler
func Create(ctx *gin.Context) {
user := models.User{}
//validate
if err := ctx.ShouldBind(&user); err != nil {
response.Error(ctx, err)
return
}
db, _ := database.GetDB()
db.Create(&user)
// removing password from the response
user.Password = ""
response.Success(ctx, user)
}
I want to create an update handler using the same struct, is there any way I can perform using the same struct ?
Mind you, struct has required bindings on many fields firstName,email etc.
while updating, I might not pass these fields
I came up with something like
/PUT /users/ handler
func Update(ctx *gin.Context) {
userID := ctx.Param("userId")
user := models.User{}
db, _ := database.GetDB()
if err := db.First(&user, userID).Error; err != nil {
response.Error(ctx, err)
return
}
updateUser := models.User{}
if err := ctx.BindJSON(&updateUser); err != nil {
response.Error(ctx, err)
}
//fmt.Printf("%v", updateUser)
db.Model(&user).Updates(updateUser)
response.Success(ctx, user)
}
this is failing obviously due to required validations missing, if I try to update, let's say, just the lastName
You could try for this case to bind to the User struct you just pulled out of the database, like so:
func Update(ctx *gin.Context) {
userID := ctx.Param("userId")
user := models.User{}
db, _ := database.GetDB()
if err := db.First(&user, userID).Error; err != nil {
response.Error(ctx, err)
return
}
if err := ctx.BindJSON(&user); err != nil {
response.Error(ctx, err)
}
db.Model(&user).Updates(user)
response.Success(ctx, user)
}
That might work because the old values + the changes (written into the struct by BindJSON) might be able to pass validation.
Generally speaking, this pattern isn't going to help you for long in Go.
Using the same struct for two different purposes: representation of the entity model and representation of the messages in your API that pertain to the model, will end up giving you trouble sooner or later. These tend to be slightly different, as for example you might eventually have fields that are only exposed internally or, as you have encountered, you have validation that doesn't make sense for all use cases.
For this problem what would help you is to create a new struct to represent the update user message:
package messages
type UpdateUser struct {
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
... fields that are updatable with appropriate validation tags
}
func (u *UpdateUser) ToModel() *model.User {
return &model.User{
FirstName: u.FirstName,
LastName: u.LastName,
...
}
}
Then use that as to validate your request model, then turn it into a model.User for the update:
func Update(ctx *gin.Context) {
userID := ctx.Param("userId")
user := models.User{}
db, _ := database.GetDB()
if err := db.First(&user, userID).Error; err != nil {
response.Error(ctx, err)
return
}
updateUser := messages.UpdateUser{}
if err := ctx.BindJSON(&updateUser); err != nil {
response.Error(ctx, err)
}
//fmt.Printf("%v", updateUser)
db.Model(&user).Updates(updateUser.ToModel())
response.Success(ctx, user)
}
If you use an ORM like Gorm in your project, it is recommended to use viewmodel structs for your requests and responses. Because the structure of your database tables are mostly different then your rest api models. Data binding and validation are easier with viewmodel structs.
// user.go
package models
type User struct {
Id int `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
func (User) GetById(c echo.Context, id int) (*User, error) {
db := c.Get("DB").(*sqlx.DB)
user := User{}
err := db.Get(&user, fmt.Sprintf("SELECT id, created_at, updated_at FROM users WHERE id=%d", id))
if err != nil {
fmt.Println(err)
}
return &user, err
}
// main.go
package main
// Success
func fetch_success(c echo.Context) error {
user := models.User{}
user2, err := models.User.GetById(user, c, 5)
}
// Fail: : not enough arguments in call to method expression models.User.GetById
// have (echo.Context, number)
// want (models.User, echo.Context, int)
func fetch_failure(c echo.Context) error {
user, err := models.User.GetById(c, 5)
}
In the above code, argument definition for GetById is c echo.Context, id int. Just two argument is needed. But compiler alert me such as "not enough arguments in call to method expression models.User.GetById"
What's the problem?
You are calling a method GetById but not on an object. When Go calls a method, it supplies the object as a first parameter implicitly. It is similar to passing self reference in Python but syntactically it goes between func keyword and function name.
Rewrite it to be a function:
func GetUserById(c echo.Context, id int) (*User, error) {
db := c.Get("DB").(*sqlx.DB)
user := User{}
err := db.Get(&user, fmt.Sprintf("SELECT id, created_at, updated_at FROM users WHERE id=%d", id))
if err != nil {
fmt.Println(err)
}
return &user, err
}
and then call
user, err := models.GetUserById(c, 5)
I have Customer struct
type Customer struct {
Model
Email string `json:"email,omitempty"`
Addresses []Address
}
func (c *Customer) BeforeCreate() (err error) {
if err := c.GenerateID(); err != nil {
return err
}
return c.Marshal()
}
And Address struct
type Address struct {
Model
CustomerID string
Address1 string
}
func (a *Address) BeforeCreate() error {
// The ID is still generated, but the insert query has no `id` in it
if err := a.GenerateID(); err != nil {
return err
}
return nil
}
Model struct
type Model struct {
ID string `gorm:"primary_key;type:varchar(100)"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
func (model *Model) GenerateID() error {
uv4, err := uuid.NewV4()
if err != nil {
return err
}
model.ID = uv4.String()
return nil
}
A Customer:
customer := &model.Customer{
Email: "a",
Addresses: []model.Address{
{
Address1: "abc street",
},
},
}
if err := gormDb.Create(customer).Error; err != nil {
return nil, err
}
I got an error: Error 1364: Field 'id' doesn't have a default value for the Address object
But if I remove the Address associations, things work. And the customer's Id is generated.
customer := & model.Customer{
Email: "a",
//Addresses: []model.Address{
//{
//Address1: "abc street",
//},
//},
}
How can I keep the Address associations and insert both successfully, and still using this ID-generation mechanism?
You can use scope.SetColumn to set a field’s value in BeforeCreate hook
func (a *Address) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.New())
return nil
}
Ref: https://v1.gorm.io/docs/create.html#Setting-Field-Values-In-Hooks
You should use tx.Statement.SetColumn to set a field value in GORM hooks e.g. BeforeCreate. Following is the sample implementation.
func (s Product3) BeforeCreate(tx *gorm.DB) (err error) {
tx.Statement.SetColumn("Price", s.Price+100)
return nil
}
Reference implementation link for GORM Github repo
https://github.com/go-gorm/gorm/blob/ac722c16f90e0e0dffc600c7f69e791c110d788c/tests/hooks_test.go#L306-L309
Reference link from GORM docs
https://gorm.io/docs/update.html#Change-Updating-Values