Skip to content

Commit

Permalink
Merge pull request #24 from ybkuroki/develop
Browse files Browse the repository at this point in the history
Implement eager fetch by inner join
  • Loading branch information
ybkuroki authored Oct 3, 2020
2 parents bf00683 + 322d920 commit a8014b6
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 51 deletions.
2 changes: 1 addition & 1 deletion controller/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/ybkuroki/go-webapp-sample/session"
)

var dummyAccount = model.NewAccountWithPlainPassword("test", "test", model.NewAuthority("Admin"))
var dummyAccount = model.NewAccountWithPlainPassword("test", "test", 1)

// GetLoginStatus returns the status of login.
func GetLoginStatus() echo.HandlerFunc {
Expand Down
2 changes: 1 addition & 1 deletion controller/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestGetLoginAccount(t *testing.T) {

router.ServeHTTP(rec, req)

account := model.NewAccountWithPlainPassword("test", "test", model.NewAuthority("Admin"))
account := model.NewAccountWithPlainPassword("test", "test", 1)
assert.Equal(t, http.StatusOK, rec.Code)
assert.JSONEq(t, test.ConvertToString(account), rec.Body.String())
}
12 changes: 2 additions & 10 deletions controller/book_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,9 @@ func TestPostBookDelete(t *testing.T) {
}

func setUpTestData() {
model := &model.Book{
ID: 1,
Title: "Test1",
Isbn: "123-123-123-1",
CategoryID: 1,
Category: &model.Category{ID: 1, Name: "技術書"},
FormatID: 1,
Format: &model.Format{ID: 1, Name: "書籍"},
}
model := model.NewBook("Test1", "123-123-123-1", 1, 1)
repo := repository.GetRepository()
_, _ = model.Save(repo)
_, _ = model.Create(repo)
}

func createRegDto() *dto.RegBookDto {
Expand Down
2 changes: 1 addition & 1 deletion migration/master_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func InitMasterData(config *config.Config) {

r := model.NewAuthority("Admin")
_, _ = r.Create(rep)
a := model.NewAccountWithPlainPassword("test", "test", r)
a := model.NewAccountWithPlainPassword("test", "test", r.ID)
_, _ = a.Create(rep)

c := model.NewCategory("技術書")
Expand Down
39 changes: 29 additions & 10 deletions model/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,58 @@ type Account struct {
Authority *Authority `json:"authority"`
}

// RecordAccount defines struct represents the record of the database.
type RecordAccount struct {
ID uint
Name string
Password string
AuthorityID uint
AuthorityName string
}

const selectAccount = "select a.*, r.id as authority_id, r.name as authority_name " +
" from account_master a inner join authority_master r on a.authority_id = r.id "

// TableName returns the table name of account struct and it is used by gorm.
func (Account) TableName() string {
return "account_master"
}

// NewAccount is constructor.
func NewAccount(name string, password string, authority *Authority) *Account {
return &Account{Name: name, Password: password, Authority: authority}
func NewAccount(name string, password string, authorityID uint) *Account {
return &Account{Name: name, Password: password, AuthorityID: authorityID}
}

// NewAccountWithPlainPassword is constructor. And it is encoded plain text password by using bcrypt.
func NewAccountWithPlainPassword(name string, password string, authority *Authority) *Account {
func NewAccountWithPlainPassword(name string, password string, authorityID uint) *Account {
hashed, _ := bcrypt.GenerateFromPassword([]byte(password), 10)
return &Account{Name: name, Password: string(hashed), Authority: authority}
return &Account{Name: name, Password: string(hashed), AuthorityID: authorityID}
}

// FindByName returns accounts full matched given account name.
func (a *Account) FindByName(rep *repository.Repository, name string) (*Account, error) {
var account Account
if error := rep.Preload("Authority").Where("name = ?", name).Find(&account).Error; error != nil {
return nil, error
}
return &account, nil
var account *Account

var rec RecordAccount
rep.Raw(selectAccount+" where a.name = ?", name).Scan(&rec)
account = converToAccount(&rec)

return account, nil
}

// Create persists this account data.
func (a *Account) Create(rep *repository.Repository) (*Account, error) {
if error := rep.Create(a).Error; error != nil {
if error := rep.Select("name", "password", "authority_id").Create(a).Error; error != nil {
return nil, error
}
return a, nil
}

func converToAccount(rec *RecordAccount) *Account {
r := &Authority{ID: rec.AuthorityID, Name: rec.AuthorityName}
return &Account{ID: rec.ID, Name: rec.Name, Password: rec.Password, AuthorityID: rec.AuthorityID, Authority: r}
}

// ToString is return string of object
func (a *Account) ToString() (string, error) {
bytes, error := json.Marshal(a)
Expand Down
95 changes: 74 additions & 21 deletions model/book.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package model

import (
"database/sql"
"encoding/json"
"math"

Expand All @@ -18,57 +19,103 @@ type Book struct {
Format *Format `json:"format"`
}

// RecordBook defines struct represents the record of the database.
type RecordBook struct {
ID uint
Title string
Isbn string
CategoryID uint
CategoryName string
FormatID uint
FormatName string
}

const selectBook = "select b.*, c.id as category_id, c.name as category_name, f.id as format_id, f.name as format_name " +
"from book b inner join category_master c on c.id = b.category_id inner join format_master f on f.id = b.format_id "

// TableName returns the table name of book struct and it is used by gorm.
func (Book) TableName() string {
return "book"
}

// NewBook is constructor
func NewBook(title string, isbn string, category *Category, format *Format) *Book {
return &Book{Title: title, Isbn: isbn, Category: category, Format: format}
func NewBook(title string, isbn string, categoryID uint, formatID uint) *Book {
return &Book{Title: title, Isbn: isbn, CategoryID: categoryID, FormatID: formatID}
}

// FindByID returns a book full matched given book's ID.
func (b *Book) FindByID(rep *repository.Repository, id uint) (*Book, error) {
var book Book
if error := rep.Preload("Category").Preload("Format").Where("id = ?", id).Find(&book).Error; error != nil {
return nil, error
}
return &book, nil
var book *Book

var rec RecordBook
rep.Raw(selectBook+" where b.id = ?", id).Scan(&rec)
book = converToBook(&rec)

return book, nil
}

// FindAll returns all books of the book table.
func (b *Book) FindAll(rep *repository.Repository) (*[]Book, error) {
var books []Book
if error := rep.Preload("Category").Preload("Format").Find(&books).Error; error != nil {
return nil, error

var rec RecordBook
var rows *sql.Rows
var err error
if rows, err = rep.Raw(selectBook).Rows(); err != nil {
return nil, err
}
for rows.Next() {
if err = rep.ScanRows(rows, &rec); err != nil {
return nil, err
}
book := converToBook(&rec)
books = append(books, *book)
}

return &books, nil
}

// FindAllByPage returns the page object of all books.
func (b *Book) FindAllByPage(rep *repository.Repository, page int, size int) (*Page, error) {
var books []Book
p := createPage(rep, &books, page, size)

if error := rep.Preload("Category").Preload("Format").Offset(page * p.Size).Limit(size).Find(&books).Error; error != nil {
return nil, error
var rec RecordBook
var rows *sql.Rows
var err error
if rows, err = rep.Raw(selectBook+" limit ? offset ? ", size, page*size).Rows(); err != nil {
return nil, err
}
for rows.Next() {
if err = rep.ScanRows(rows, &rec); err != nil {
return nil, err
}
book := converToBook(&rec)
books = append(books, *book)
}

p.Content = &books
p := createPage(rep, &books, page, size)
return p, nil
}

// FindByTitle returns the page object of books partially matched given book title.
func (b *Book) FindByTitle(rep *repository.Repository, title string, page int, size int) (*Page, error) {
var books []Book
p := createPage(rep, &books, page, size)

if error := rep.Preload("Category").Preload("Format").Where("title LIKE ?", "%"+title+"%").Offset(page * p.Size).Limit(size).Find(&books).Error; error != nil {
return nil, error
var rec RecordBook
var rows *sql.Rows
var err error
if rows, err = rep.Raw(selectBook+" where title like ? limit ? offset ? ", "%"+title+"%", size, page*size).Rows(); err != nil {
return nil, err
}
for rows.Next() {
if err = rep.ScanRows(rows, &rec); err != nil {
return nil, err
}
book := converToBook(&rec)
books = append(books, *book)
}

p.Content = &books
p := createPage(rep, &books, page, size)
return p, nil
}

Expand All @@ -77,9 +124,9 @@ func createPage(rep *repository.Repository, books *[]Book, page int, size int) *
p.Page = page
p.Size = size
p.NumberOfElements = p.Size

rep.Preload("Category").Preload("Format").Find(&books).Count(&p.TotalElements)
p.TotalElements = len(*books)
p.TotalPages = int(math.Ceil(float64(p.TotalElements) / float64(p.Size)))
p.Content = books

return p
}
Expand All @@ -94,15 +141,15 @@ func (b *Book) Save(rep *repository.Repository) (*Book, error) {

// Update updates this book data.
func (b *Book) Update(rep *repository.Repository) (*Book, error) {
if error := rep.Update(b).Error; error != nil {
if error := rep.Model(Book{}).Where("id = ?", b.ID).Select("title", "isbn", "category_id", "format_id").Updates(b).Error; error != nil {
return nil, error
}
return b, nil
}

// Create persists this book data.
func (b *Book) Create(rep *repository.Repository) (*Book, error) {
if error := rep.Create(b).Error; error != nil {
if error := rep.Select("title", "isbn", "category_id", "format_id").Create(b).Error; error != nil {
return nil, error
}
return b, nil
Expand All @@ -116,6 +163,12 @@ func (b *Book) Delete(rep *repository.Repository) (*Book, error) {
return b, nil
}

func converToBook(rec *RecordBook) *Book {
c := &Category{ID: rec.CategoryID, Name: rec.CategoryName}
f := &Format{ID: rec.FormatID, Name: rec.FormatName}
return &Book{ID: rec.ID, Title: rec.Title, Isbn: rec.Isbn, CategoryID: rec.CategoryID, Category: c, FormatID: rec.FormatID, Format: f}
}

// ToString is return string of object
func (b *Book) ToString() (string, error) {
bytes, error := json.Marshal(b)
Expand Down
4 changes: 1 addition & 3 deletions model/dto/book.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ func NewRegBookDto() *RegBookDto {

// Create creates a book model from this DTO.
func (b *RegBookDto) Create() *model.Book {
c := model.NewCategory("")
f := model.NewFormat("")
return model.NewBook(b.Title, b.Isbn, c, f)
return model.NewBook(b.Title, b.Isbn, b.CategoryID, b.FormatID)
}

// Validate performs validation check for the each item.
Expand Down
16 changes: 16 additions & 0 deletions repository/repository.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package repository

import (
"database/sql"
"fmt"

"github.com/jinzhu/gorm"
Expand Down Expand Up @@ -60,6 +61,16 @@ func GetDB() *gorm.DB {
return rep.db
}

// Model specify the model you would like to run db operations
func (rep *Repository) Model(value interface{}) *gorm.DB {
return rep.db.Model(value)
}

// Select specify fields that you want to retrieve from database when querying, by default, will select all fields;
func (rep *Repository) Select(query interface{}, args ...interface{}) *gorm.DB {
return rep.db.Select(query, args...)
}

// Find find records that match given conditions.
func (rep *Repository) Find(out interface{}, where ...interface{}) *gorm.DB {
return rep.db.Find(out, where...)
Expand Down Expand Up @@ -115,6 +126,11 @@ func (rep *Repository) Scopes(funcs ...func(*gorm.DB) *gorm.DB) *gorm.DB {
return rep.db.Scopes(funcs...)
}

// ScanRows scan `*sql.Rows` to give struct
func (rep *Repository) ScanRows(rows *sql.Rows, result interface{}) error {
return rep.db.ScanRows(rows, result)
}

// Transaction start a transaction as a block.
// If it is failed, will rollback and return error.
// If it is sccuessed, will commit.
Expand Down
10 changes: 6 additions & 4 deletions service/book.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func RegisterBook(dto *dto.RegBookDto) (*model.Book, map[string]string) {
})

if err != nil {
logger.GetEchoLogger().Error(err.Error)
logger.GetEchoLogger().Error(err)
return nil, map[string]string{"error": "transaction error"}
}

Expand Down Expand Up @@ -102,6 +102,8 @@ func EditBook(dto *dto.ChgBookDto) (*model.Book, map[string]string) {

book.Title = dto.Title
book.Isbn = dto.Isbn
book.CategoryID = dto.CategoryID
book.FormatID = dto.FormatID

category := model.Category{}
if book.Category, err = category.FindByID(txrep, dto.CategoryID); err != nil {
Expand All @@ -113,15 +115,15 @@ func EditBook(dto *dto.ChgBookDto) (*model.Book, map[string]string) {
return err
}

if result, err = book.Save(txrep); err != nil {
if result, err = book.Update(txrep); err != nil {
return err
}

return nil
})

if err != nil {
logger.GetEchoLogger().Error(err.Error)
logger.GetEchoLogger().Error(err)
return nil, map[string]string{"error": "transaction error"}
}

Expand Down Expand Up @@ -156,7 +158,7 @@ func DeleteBook(dto *dto.ChgBookDto) (*model.Book, map[string]string) {
})

if err != nil {
logger.GetEchoLogger().Error(err.Error)
logger.GetEchoLogger().Error(err)
return nil, map[string]string{"error": "transaction error"}
}

Expand Down

0 comments on commit a8014b6

Please sign in to comment.