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/postgres"
"github.com/go-rel/rel"
"github.com/go-rel/rel/where"
_ "github.com/lib/pq"
)
// 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 = "postgres://postgres@localhost/rel_test?sslmode=disable"
func main() {
// initialize postgres adapter.
adapter, _ := postgres.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¶
- gin-example - Todo Backend using Gin and REL
- go-todo-backend - Todo Backend using Chi and REL
- iris-example - Todo Backend using Iris and REL
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
}