Skip to content

Use Gorm with Blueprint

Joseph Spurrier edited this page Sep 15, 2016 · 2 revisions

MySQL Driver

To use gorm with Blue Jay, you can add this file to blue-jay/core/storage/driver/gorm/gorm.go:

// Package gorm provides a wrapper around the jinzhu/gorm package.
package gorm

import (
	"fmt"
	"strings"
	"sync"

	gm "github.com/jinzhu/gorm"

	_ "github.com/jinzhu/gorm/dialects/mysql"
)

// *****************************************************************************
// Thread-Safe Configuration
// *****************************************************************************

var (
	info      Info
	infoMutex sync.RWMutex
)

// Info holds the details for the MySQL connection.
type Info struct {
	Username        string
	Password        string
	Database        string
	Charset         string
	Collation       string
	Hostname        string
	Port            int
	Parameter       string
	MigrationFolder string
	Extension       string
}

// SetConfig stores the config.
func SetConfig(i Info) {
	infoMutex.Lock()
	info = i
	infoMutex.Unlock()
}

// Config returns the config.
func Config() Info {
	infoMutex.RLock()
	defer infoMutex.RUnlock()
	return info
}

// ResetConfig removes the config.
func ResetConfig() {
	infoMutex.Lock()
	info = Info{}
	infoMutex.Unlock()
}

// *****************************************************************************
// Database Handling
// *****************************************************************************

// Connect to the database.
func Connect(specificDatabase bool) error {
	var err error

	// Connect to MySQL and ping
	if SQL, err = gm.Open("mysql", dsn(specificDatabase)); err != nil {
		return err
	}

	return err
}

// Disconnect the database connection.
func Disconnect() error {
	return SQL.Close()
}

// Create a new database.
func Create() error {
	// Set defaults
	ci := setDefaults()

	// Create the database
	err := SQL.Exec(fmt.Sprintf(`CREATE DATABASE %v
				DEFAULT CHARSET = %v
				COLLATE = %v
				;`, ci.Database,
		ci.Charset,
		ci.Collation)).Error
	return err
}

// Drop a database.
func Drop() error {
	// Drop the database
	err := SQL.Exec(fmt.Sprintf(`DROP DATABASE %v;`, Config().Database)).Error
	return err
}

// *****************************************************************************
// MySQL Specific
// *****************************************************************************

var (
	// SQL wrapper
	SQL *gm.DB
)

// DSN returns the Data Source Name.
func dsn(includeDatabase bool) string {
	// Set defaults
	ci := setDefaults()

	// Build parameters
	param := ci.Parameter

	// If parameter is specified, add a question mark
	// Don't add one if a question mark is already there
	if len(ci.Parameter) > 0 && !strings.HasPrefix(ci.Parameter, "?") {
		param = "?" + ci.Parameter
	}

	// Add collation
	if !strings.Contains(param, "collation") {
		if len(param) > 0 {
			param += "&collation=" + ci.Collation
		} else {
			param = "?collation=" + ci.Collation
		}
	}

	// Add charset
	if !strings.Contains(param, "charset") {
		if len(param) > 0 {
			param += "&charset=" + ci.Charset
		} else {
			param = "?charset=" + ci.Charset
		}
	}

	// Example: root:password@tcp(localhost:3306)/test
	s := fmt.Sprintf("%v:%v@tcp(%v:%d)/%v", ci.Username, ci.Password, ci.Hostname, ci.Port, param)

	if includeDatabase {
		s = fmt.Sprintf("%v:%v@tcp(%v:%d)/%v%v", ci.Username, ci.Password, ci.Hostname, ci.Port, ci.Database, param)
	}

	return s
}

// setDefaults sets the charset and collation if they are not set.
func setDefaults() Info {
	ci := Config()

	if len(ci.Charset) == 0 {
		ci.Charset = "utf8"
	}
	if len(ci.Collation) == 0 {
		ci.Collation = "utf8_unicode_ci"
	}

	return ci
}

Example Model

This is an example of how to change the blue-jay/blueprint/model/user/user.go file to work with Gorm:

// Package user provides access to the user table in the MySQL database.
package user

import (
	"time"

	database "github.com/blue-jay/blueprint/lib/gorm"
	"github.com/blue-jay/blueprint/model"

	"github.com/go-sql-driver/mysql"
)

// User table.
type User struct {
	ID         uint32 `db:"id"`
	FirstName  string `db:"first_name"`
	LastName   string `db:"last_name"`
	Email      string `db:"email"`
	Password   string `db:"password"`
	UserStatus UserStatus
	StatusID   uint8          `db:"status_id"`
	CreatedAt  mysql.NullTime `db:"created_at"`
	UpdatedAt  mysql.NullTime `db:"updated_at"`
	DeletedAt  mysql.NullTime `db:"deleted_at"`
}

// TableName for user table.
func (User) TableName() string {
	return "user"
}

// UserStatus table.
type UserStatus struct {
	ID        uint8     `db:"id"`
	Status    string    `db:"status"`
	CreatedAt time.Time `db:"created_at"`
	UpdatedAt time.Time `db:"updated_at"`
	DeletedAt time.Time `db:"deleted_at"`
}

// TableName for user_status table.
func (UserStatus) TableName() string {
	return "user_status"
}

// ByEmail gets user information from email.
func ByEmail(email string) (User, error) {
	result := User{}
	return result, model.StandardError(database.SQL.Where("email = ?", email).
		First(&result).Error)
}

// Create creates user.
func Create(firstName, lastName, email, password string) error {
	item := &User{
		FirstName: firstName,
		LastName:  lastName,
		Email:     email,
		Password:  password,
		StatusID:  1,
	}
	return model.StandardError(database.SQL.Create(item).Error)
}

Model Package

The other change you'd need to make is to update the blue-jay/blueprint/model/model.go file:

// Package model provides error standardization for the models.
package model

import (
	"database/sql"
	"errors"

	"github.com/jinzhu/gorm"
)

var (
	// ErrNoResult is when no results are found.
	ErrNoResult = errors.New("Result not found.")
)

// StandardError returns a model defined error.
func StandardError(err error) error {
	if err == sql.ErrNoRows {
		return ErrNoResult
	} else if err == gorm.ErrRecordNotFound {
		return ErrNoResult
	}

	return err
}