Skip to content

Basics

Full Example

Below is a very basic example on how to utilize REL using mysql adapter. Testing database query using REL can be done using reltest package.

package main

import (
    "context"
    "time"

    "github.com/go-rel/mysql"
    "github.com/go-rel/rel"
    "github.com/go-rel/rel/where"
    _ "github.com/go-sql-driver/mysql"
)

// Author is a model that maps to authors table.
type Author struct {
    ID   int
    Name string
}

// Book is a model that maps to books table.
type Book struct {
    ID        int
    Title     string
    Category  string
    Price     int
    Discount  bool
    Stock     int
    AuthorID  int
    Author    Author
    Publisher string
    CreatedAt time.Time
    UpdatedAt time.Time
}

var dsn = "root@(127.0.0.1:3306)/db?charset=utf8&parseTime=True&loc=Local"

func main() {
    // initialize mysql adapter.
    adapter, _ := mysql.Open(dsn)
    defer adapter.Close()

    // initialize rel's repo.
    repo := rel.New(adapter)

    // run
    Example(context.Background(), repo)
}

// Example is an actual service function that run a complex business package.
// beware: it's actually doing nonsense here.
func Example(ctx context.Context, repo rel.Repository) error {
    var book Book

    // Quickly find a book with id 1 using short alias.
    if err := repo.Find(ctx, &book, where.Eq("id", 1)); err != nil {
        return err
    }

    // Or use chainable query builder.
    query := rel.Select().Where(where.Eq("id", 1)).Limit(1)
    if err := repo.Find(ctx, &book, query); err != nil {
        return err
    }

    // Convenient method to preload Book's Author.
    if err := repo.Preload(ctx, &book, "author"); err != nil {
        return err
    }

    // Performs updates inside a transaction.
    return repo.Transaction(ctx, func(ctx context.Context) error {
        // basic update using struct.
        book.Title = "REL for dummies"
        repo.MustUpdate(ctx, &book)

        // update only specific fields.
        repo.MustUpdate(ctx, &book, rel.Set("discount", false))

        // it even supports atomic inc/dec mutation.
        return repo.Update(ctx, &book, rel.Dec("stock"))
    })
}
package main

import (
    "context"
    "testing"

    "github.com/go-rel/rel"
    "github.com/go-rel/rel/where"
    "github.com/go-rel/reltest"
    "github.com/stretchr/testify/assert"
)

func TestExample(t *testing.T) {
    // create a mocked repository.
    var (
        repo = reltest.New()
        book = Book{
            ID:       1,
            Title:    "Go for dummies",
            Category: "learning",
            AuthorID: 1,
        }
        author = Author{ID: 1, Name: "CZ2I28 Delta"}
    )

    // mock find and return result
    repo.ExpectFind(where.Eq("id", 1)).Result(book)

    // mock find and return result using query builder.
    repo.ExpectFind(rel.Select().Where(where.Eq("id", 1)).Limit(1)).Result(book)

    // mock preload and return result
    repo.ExpectPreload("author").ForType("main.Book").Result(author)

    // mocks transaction
    repo.ExpectTransaction(func(repo *reltest.Repository) {
        // mock updates
        repo.ExpectUpdate().ForType("main.Book")
        repo.ExpectUpdate(rel.Set("discount", false)).ForType("main.Book")
        repo.ExpectUpdate(rel.Dec("stock")).ForType("main.Book")
    })

    // run and asserts
    assert.Nil(t, Example(context.Background(), repo))
    repo.AssertExpectations(t)
}

Other Examples

Conventions

Schema Definition

REL uses a struct as the schema to infer table name, columns and primary field.

// Table name: books
type Book struct {
    ID        int       // id
    Title     string    // title
    Category  string    // category
    CreatedAt time.Time // created_at
    UpdatedAt time.Time // updated_at
}

Table Name

Table name will be the pluralized struct name in snake case, you may create a Table() string method to override the default table name.

// Default table name is `books`
type Book struct {}

// Override table name to be `ebooks`
func (b Book) Table() string {
    return "ebooks"
}

Column Name

Column name will be the struct field name in snake case, you may override the column name by using db tag.

type Book struct {
    ID       int                // this field will be mapped to `id` column.
    Title    string `db:"name"` // this field will be mapped to `name` column.
    Category string `db:"-"`    // this field will be skipped
}

Primary Key

REL requires every struct to have at least primary key. by default field named id will be used as primary key. To use other field as primary key, you may define it as primary using db tag. Defining multiple field as primary will forms composite primary key.

type Book struct {
    UUID string `db:"uuid,primary"` // or just `db:",primary"`
}

Timestamp

REL automatically track created and updated time of each struct if CreatedAt or UpdatedAt field exists.

Embedded structs

REL supports embedding structs and struct pointers. By default, fields of embedded structs have no column name prefix. A prefix can be set with the db tag

type Model struct {
    ID    int
    Owner int
}

type Book struct {
    Model `db:"model_"` // id and owner will mapped to model_id and model_owner
    Title string
}

Last update: 2022-06-25