railstack/go-on-rails

View on GitHub
lib/generators/templates/gor_model_mysql.go.erb

Summary

Maintainability
Test Coverage
<%#- var_pmn stands for pluralized model name, its format is same as the table name -#%>
<%- var_pmn = table_name = @model_name.tableize -%>
<%#- var_umn stands for underscored model name -#%>
<%- var_umn = @model_name.underscore -%>
<%- fields  = @struct_info[:select_fields] -%>
<%- col_names = @struct_info[:col_names] -%>
<%- has_assoc = !@struct_info[:assoc_info][:has_many].empty? || !@struct_info[:assoc_info][:has_one].empty? -%>
// Package models includes the functions on the model <%= @model_name %>.
package models

import (
    "errors"
    "fmt"
    "log"
    "math"
    "strings"
<%- if @struct_info[:has_datetime_type] -%>
    "time"
<%- end -%>

    "github.com/asaskevich/govalidator"
)

// set flags to output more detailed log
func init() {
    log.SetFlags(log.LstdFlags | log.Lshortfile)
}

type <%= @model_name %> struct {
    <%= @struct_info[:struct_body] -%>
}

// DataStruct for the pagination
type <%= @model_name %>Page struct {
    WhereString string
    WhereParams []interface{}
    Order       map[string]string
    FirstId     int64
    LastId      int64
    PageNum     int
    PerPage     int
    TotalPages  int
    TotalItems  int64
    orderStr    string
}

// Current get the current page of <%= @model_name %>Page object for pagination.
func (_p *<%= @model_name %>Page) Current() ([]<%= @model_name %>, error) {
    if _, exist := _p.Order["id"]; !exist {
        return nil, errors.New("No id order specified in Order map")
    }
    err := _p.buildPageCount()
    if err != nil {
        return nil, fmt.Errorf("Calculate page count error: %v", err)
    }
    if _p.orderStr == "" {
        _p.buildOrder()
    }
    idStr, idParams := _p.buildIdRestrict("current")
    whereStr := fmt.Sprintf("%s %s %s LIMIT %v", _p.WhereString, idStr, _p.orderStr, _p.PerPage)
    whereParams := []interface{}{}
    whereParams = append(append(whereParams, _p.WhereParams...), idParams...)
    <%= var_pmn %>, err := Find<%= @model_name.pluralize %>Where(whereStr, whereParams...)
    if err != nil {
        return nil, err
    }
    if len(<%= var_pmn %>) != 0 {
        _p.FirstId, _p.LastId = <%= var_pmn %>[0].Id, <%= var_pmn %>[len(<%= var_pmn %>)-1].Id
    }
    return <%= var_pmn %>, nil
}

// Previous get the previous page of <%= @model_name %>Page object for pagination.
func (_p *<%= @model_name %>Page) Previous() ([]<%= @model_name %>, error) {
    if _p.PageNum == 0 {
        return nil, errors.New("This's the first page, no previous page yet")
    }
    if _, exist := _p.Order["id"]; !exist {
        return nil, errors.New("No id order specified in Order map")
    }
    err := _p.buildPageCount()
    if err != nil {
        return nil, fmt.Errorf("Calculate page count error: %v", err)
    }
    if _p.orderStr == "" {
        _p.buildOrder()
    }
    idStr, idParams := _p.buildIdRestrict("previous")
    whereStr := fmt.Sprintf("%s %s %s LIMIT %v", _p.WhereString, idStr, _p.orderStr, _p.PerPage)
    whereParams := []interface{}{}
    whereParams = append(append(whereParams, _p.WhereParams...), idParams...)
    <%= var_pmn %>, err := Find<%= @model_name.pluralize %>Where(whereStr, whereParams...)
    if err != nil {
        return nil, err
    }
    if len(<%= var_pmn %>) != 0 {
        _p.FirstId, _p.LastId = <%= var_pmn %>[0].Id, <%= var_pmn %>[len(<%= var_pmn %>)-1].Id
    }
    _p.PageNum -= 1
    return <%= var_pmn %>, nil
}

// Next get the next page of <%= @model_name %>Page object for pagination.
func (_p *<%= @model_name %>Page) Next() ([]<%= @model_name %>, error) {
    if _p.PageNum == _p.TotalPages-1 {
        return nil, errors.New("This's the last page, no next page yet")
    }
    if _, exist := _p.Order["id"]; !exist {
        return nil, errors.New("No id order specified in Order map")
    }
    err := _p.buildPageCount()
    if err != nil {
        return nil, fmt.Errorf("Calculate page count error: %v", err)
    }
    if _p.orderStr == "" {
        _p.buildOrder()
    }
    idStr, idParams := _p.buildIdRestrict("next")
    whereStr := fmt.Sprintf("%s %s %s LIMIT %v", _p.WhereString, idStr, _p.orderStr, _p.PerPage)
    whereParams := []interface{}{}
    whereParams = append(append(whereParams, _p.WhereParams...), idParams...)
    <%= var_pmn %>, err := Find<%= @model_name.pluralize %>Where(whereStr, whereParams...)
    if err != nil {
        return nil, err
    }
    if len(<%= var_pmn %>) != 0 {
        _p.FirstId, _p.LastId = <%= var_pmn %>[0].Id, <%= var_pmn %>[len(<%= var_pmn %>)-1].Id
    }
    _p.PageNum += 1
    return <%= var_pmn %>, nil
}

// GetPage is a helper function for the <%= @model_name %>Page object to return a corresponding page due to
// the parameter passed in, i.e. one of "previous, current or next".
func (_p *<%= @model_name %>Page) GetPage(direction string) (ps []<%= @model_name %>, err error) {
    switch direction {
    case "previous":
        ps, _ = _p.Previous()
    case "next":
        ps, _ = _p.Next()
    case "current":
        ps, _ = _p.Current()
    default:
        return nil, errors.New("Error: wrong dircetion! None of previous, current or next!")
    }
    return
}

// buildOrder is for <%= @model_name %>Page object to build a SQL ORDER BY clause.
func (_p *<%= @model_name %>Page) buildOrder() {
    tempList := []string{}
    for k, v := range _p.Order {
        tempList = append(tempList, fmt.Sprintf("%v %v", k, v))
    }
    _p.orderStr = " ORDER BY " + strings.Join(tempList, ", ")
}

// buildIdRestrict is for <%= @model_name %>Page object to build a SQL clause for ID restriction,
// implementing a simple keyset style pagination.
func (_p *<%= @model_name %>Page) buildIdRestrict(direction string) (idStr string, idParams []interface{}) {
    switch direction {
    case "previous":
        if strings.ToLower(_p.Order["id"]) == "desc" {
            idStr += "id > ? "
            idParams = append(idParams, _p.FirstId)
        } else {
            idStr += "id < ? "
            idParams = append(idParams, _p.FirstId)
        }
    case "current":
        // trick to make Where function work
        if _p.PageNum == 0 && _p.FirstId == 0 && _p.LastId == 0 {
            idStr += "id > ? "
            idParams = append(idParams, 0)
        } else {
            if strings.ToLower(_p.Order["id"]) == "desc" {
                idStr += "id <= ? AND id >= ? "
                idParams = append(idParams, _p.FirstId, _p.LastId)
            } else {
                idStr += "id >= ? AND id <= ? "
                idParams = append(idParams, _p.FirstId, _p.LastId)
            }
        }
    case "next":
        if strings.ToLower(_p.Order["id"]) == "desc" {
            idStr += "id < ? "
            idParams = append(idParams, _p.LastId)
        } else {
            idStr += "id > ? "
            idParams = append(idParams, _p.LastId)
        }
    }
    if _p.WhereString != "" {
        idStr = " AND " + idStr
    }
    return
}

// buildPageCount calculate the TotalItems/TotalPages for the <%= @model_name %>Page object.
func (_p *<%= @model_name %>Page) buildPageCount() error {
    count, err := <%= @model_name %>CountWhere(_p.WhereString, _p.WhereParams...)
    if err != nil {
        return err
    }
    _p.TotalItems = count
    if _p.PerPage == 0 {
        _p.PerPage = 10
    }
    _p.TotalPages = int(math.Ceil(float64(_p.TotalItems) / float64(_p.PerPage)))
    return nil
}


// Find<%= @model_name %> find a single <%= var_umn %> by an ID.
func Find<%= @model_name %>(id int64) (*<%= @model_name %>, error) {
    if id == 0 {
        return nil, errors.New("Invalid ID: it can't be zero")
    }
    _<%= var_umn %> := <%= @model_name %>{}
    err := DB.Get(&_<%= var_umn %>, DB.Rebind(`SELECT <%= fields %> FROM <%= table_name %> WHERE <%= table_name %>.id = ? LIMIT 1`), id)
    if err != nil {
        log.Printf("Error: %v\n", err)
        return nil, err
    }
    return &_<%= var_umn %>, nil
}

// First<%= @model_name %> find the first one <%= var_umn %> by ID ASC order.
func First<%= @model_name %>() (*<%= @model_name %>, error) {
    _<%= var_umn %> := <%= @model_name %>{}
    err := DB.Get(&_<%= var_umn %>, DB.Rebind(`SELECT <%= fields %> FROM <%= table_name %> ORDER BY <%= table_name %>.id ASC LIMIT 1`))
    if err != nil {
        log.Printf("Error: %v\n", err)
        return nil, err
    }
    return &_<%= var_umn %>, nil
}

// First<%= @model_name.pluralize %> find the first N <%= var_umn.pluralize %> by ID ASC order.
func First<%= @model_name.pluralize %>(n uint32) ([]<%= @model_name %>, error) {
    _<%= var_pmn %> := []<%= @model_name %>{}
    sql := fmt.Sprintf("SELECT <%= fields %> FROM <%= table_name %> ORDER BY <%= table_name %>.id ASC LIMIT %v", n)
    err := DB.Select(&_<%= var_pmn %>, DB.Rebind(sql))
    if err != nil {
        log.Printf("Error: %v\n", err)
        return nil, err
    }
    return _<%= var_pmn %>, nil
}

// Last<%= @model_name %> find the last one <%= var_umn %> by ID DESC order.
func Last<%= @model_name %>() (*<%= @model_name %>, error) {
    _<%= var_umn %> := <%= @model_name %>{}
    err := DB.Get(&_<%= var_umn %>, DB.Rebind(`SELECT <%= fields %> FROM <%= table_name %> ORDER BY <%= table_name %>.id DESC LIMIT 1`))
    if err != nil {
        log.Printf("Error: %v\n", err)
        return nil, err
    }
    return &_<%= var_umn %>, nil
}

// Last<%= @model_name.pluralize %> find the last N <%= var_umn.pluralize %> by ID DESC order.
func Last<%= @model_name.pluralize %>(n uint32) ([]<%= @model_name %>, error) {
    _<%= var_pmn %> := []<%= @model_name %>{}
    sql := fmt.Sprintf("SELECT <%= fields %> FROM <%= table_name %> ORDER BY <%= table_name %>.id DESC LIMIT %v", n)
    err := DB.Select(&_<%= var_pmn %>, DB.Rebind(sql))
    if err != nil {
        log.Printf("Error: %v\n", err)
        return nil, err
    }
    return _<%= var_pmn %>, nil
}

// Find<%= @model_name.pluralize %> find one or more <%= var_umn.pluralize %> by the given ID(s).
func Find<%= @model_name.pluralize %>(ids ...int64) ([]<%= @model_name %>, error) {
    if len(ids) == 0 {
        msg := "At least one or more ids needed"
        log.Println(msg)
        return nil, errors.New(msg)
    }
    _<%= var_pmn %> := []<%= @model_name %>{}
    idsHolder := strings.Repeat(",?", len(ids)-1)
    sql := DB.Rebind(fmt.Sprintf(`SELECT <%= fields %> FROM <%= table_name %> WHERE <%= table_name %>.id IN (?%s)`, idsHolder))
    idsT := []interface{}{}
    for _,id := range ids {
        idsT = append(idsT, interface{}(id))
    }
    err := DB.Select(&_<%= var_pmn %>, sql, idsT...)
    if err != nil {
        log.Printf("Error: %v\n", err)
        return nil, err
    }
    return _<%= var_pmn %>, nil
}

// Find<%= @model_name %>By find a single <%= var_umn %> by a field name and a value.
func Find<%= @model_name %>By(field string, val interface{}) (*<%= @model_name %>, error) {
    _<%= var_umn %> := <%= @model_name %>{}
    sqlFmt := `SELECT <%= fields %> FROM <%= table_name %> WHERE %s = ? LIMIT 1`
    sqlStr := fmt.Sprintf(sqlFmt, field)
    err := DB.Get(&_<%= var_umn %>, DB.Rebind(sqlStr), val)
    if err != nil {
        log.Printf("Error: %v\n", err)
        return nil, err
    }
    return &_<%= var_umn %>, nil
}

// Find<%= @model_name.pluralize %>By find all <%= var_pmn %> by a field name and a value.
func Find<%= @model_name.pluralize %>By(field string, val interface{}) (_<%= var_pmn %> []<%= @model_name %>, err error) {
    sqlFmt := `SELECT <%= fields %> FROM <%= table_name %> WHERE %s = ?`
    sqlStr := fmt.Sprintf(sqlFmt, field)
    err = DB.Select(&_<%= var_pmn %>, DB.Rebind(sqlStr), val)
    if err != nil {
        log.Printf("Error: %v\n", err)
        return nil, err
    }
    return _<%= var_pmn %>, nil
}

// All<%= @model_name.pluralize %> get all the <%= @model_name %> records.
func All<%= @model_name.pluralize %>() (<%= var_pmn %> []<%= @model_name %>, err error) {
    err = DB.Select(&<%= var_pmn %>, "SELECT <%= fields %> FROM <%= table_name %>")
    if err != nil {
        log.Println(err)
        return nil, err
    }
    return <%= var_pmn %>, nil
}

// <%= @model_name %>Count get the count of all the <%= @model_name %> records.
func <%= @model_name %>Count() (c int64, err error) {
    err = DB.Get(&c, "SELECT count(*) FROM <%= table_name %>")
    if err != nil {
        log.Println(err)
        return 0, err
    }
    return c, nil
}

// <%= @model_name %>CountWhere get the count of all the <%= @model_name %> records with a where clause.
func <%= @model_name %>CountWhere(where string, args ...interface{}) (c int64, err error) {
    sql := "SELECT count(*) FROM <%= table_name %>"
    if len(where) > 0 {
        sql = sql + " WHERE " + where
    }
    stmt, err := DB.Preparex(DB.Rebind(sql))
    if err != nil {
        log.Println(err)
        return 0, err
    }
    err = stmt.Get(&c, args...)
    if err != nil {
        log.Println(err)
        return 0, err
    }
    return c, nil
}

// <%= @model_name %>IncludesWhere get the <%= @model_name %> associated models records, currently it's not same as the corresponding "includes" function but "preload" instead in Ruby on Rails. It means that the "sql" should be restricted on <%= @model_name %> model.
func <%= @model_name %>IncludesWhere(assocs []string, sql string, args ...interface{}) (_<%= var_pmn %> []<%= @model_name %>, err error) {
    _<%= var_pmn %>, err = Find<%= @model_name.pluralize %>Where(sql, args...)
    if err != nil {
        log.Println(err)
        return nil, err
    }
    if len(assocs) == 0 {
        log.Println("No associated fields ard specified")
        return _<%= var_pmn %>, err
    }
    if len(_<%= var_pmn %>) <= 0 {
        return nil, errors.New("No results available")
    }
    ids := make([]interface{}, len(_<%= var_pmn %>))
    for _, v := range _<%= var_pmn %> {
        ids = append(ids, interface{}(v.Id))
    }
    <%- if has_assoc -%>
    idsHolder := strings.Repeat(",?", len(ids)-1)
    for _, assoc := range assocs {
        switch assoc {
            <%- unless @struct_info[:assoc_info][:has_many].empty? -%>
                <%- has_many = @struct_info[:assoc_info][:has_many] -%>
                <%- has_many.each do |k, v| -%>
                case "<%= k.underscore.pluralize %>":
                    <%- if v[:through] || v[:as] -%>
                        // FIXME: optimize the query
                        for i, vvv := range  _<%= var_pmn %> {
                            _<%= k.underscore.pluralize %>, err := <%= @model_name %>Get<%= k.pluralize %>(vvv.Id)
                            if err != nil {
                                continue
                            }
                            vvv.<%= k %> = _<%= k.underscore.pluralize %>
                            _<%= var_pmn %>[i] = vvv
                        }
                    <%- else -%>
                        <%- if v[:foreign_key] -%>
                            where := fmt.Sprintf("<%= v[:foreign_key] %> IN (?%s)", idsHolder)
                        <%- else -%>
                            where := fmt.Sprintf("<%= var_umn %>_id IN (?%s)", idsHolder)
                        <%- end -%>
                        _<%= v[:class_name].underscore.pluralize %>, err := Find<%= v[:class_name].pluralize %>Where(where, ids...)
                        if err != nil {
                            log.Printf("Error when query associated objects: %v\n", assoc)
                            continue
                        }
                        for _, vv := range _<%= v[:class_name].underscore.pluralize %> {
                            for i, vvv := range  _<%= var_pmn %> {
                                <%- if v[:foreign_key] -%>
                                    if vv.<%= v[:foreign_key].to_s.camelize %> == vvv.Id {
                                        vvv.<%= k %> = append(vvv.<%= k %>, vv)
                                    }
                                <%- else -%>
                                    if vv.<%= @model_name.camelize %>Id == vvv.Id {
                                        vvv.<%= k %> = append(vvv.<%= k %>, vv)
                                    }
                                <%- end -%>
                                _<%= var_pmn %>[i].<%= k %> = vvv.<%= k %>
                            }
                        }
                    <%- end -%>
                <%- end -%>
            <%- end -%>
            <%- unless @struct_info[:assoc_info][:has_one].empty? -%>
                <%- has_one = @struct_info[:assoc_info][:has_one] -%>
                <%- has_one.each do |k, v| -%>
                case "<%= k.underscore %>":
                    <%- if v[:foreign_key] -%>
                        where := fmt.Sprintf("<%= v[:foreign_key] %> IN (?%s)", idsHolder)
                    <%- else -%>
                        where := fmt.Sprintf("<%= var_umn %>_id IN (?%s)", idsHolder)
                    <%- end -%>
                    _<%= v[:class_name].underscore.pluralize %>, err := Find<%= v[:class_name].pluralize %>Where(where, ids...)
                    if err != nil {
                        log.Printf("Error when query associated objects: %v\n", assoc)
                        continue
                    }
                    for _, vv := range _<%= v[:class_name].underscore.pluralize %> {
                        for i, vvv := range  _<%= var_pmn %> {
                            <%- if v[:foreign_key] -%>
                                if vv.<%= v[:foreign_key].to_s.camelize %> == vvv.Id {
                                    vvv.<%= k %> = vv
                                }
                            <%- else -%>
                                if vv.<%= @model_name.camelize %>Id == vvv.Id {
                                    vvv.<%= k %> = vv
                                }
                            <%- end -%>
                            _<%= var_pmn %>[i].<%= k %> = vvv.<%= k %>
                        }
                    }
                <%- end -%>
            <%- end -%>
        }
    }
    <%- end -%>
    return _<%= var_pmn %>, nil
}

// <%= @model_name %>Ids get all the IDs of <%= @model_name %> records.
func <%= @model_name %>Ids() (ids []int64, err error) {
    err = DB.Select(&ids, "SELECT id FROM <%= table_name %>")
    if err != nil {
        log.Println(err)
        return nil, err
    }
    return ids, nil
}

// <%= @model_name %>IdsWhere get all the IDs of <%= @model_name %> records by where restriction.
func <%= @model_name %>IdsWhere(where string, args ...interface{}) ([]int64, error) {
    ids, err := <%= @model_name %>IntCol("id", where, args...)
    return ids, err
}

// <%= @model_name %>IntCol get some int64 typed column of <%= @model_name %> by where restriction.
func <%= @model_name %>IntCol(col, where string, args ...interface{}) (intColRecs []int64, err error) {
    sql := "SELECT " + col + " FROM <%= table_name %>"
    if len(where) > 0 {
        sql = sql + " WHERE " + where
    }
    stmt, err := DB.Preparex(DB.Rebind(sql))
    if err != nil {
        log.Println(err)
        return nil, err
    }
    err = stmt.Select(&intColRecs, args...)
    if err != nil {
        log.Println(err)
        return nil, err
    }
    return intColRecs, nil
}

// <%= @model_name %>StrCol get some string typed column of <%= @model_name %> by where restriction.
func <%= @model_name %>StrCol(col, where string, args ...interface{}) (strColRecs []string, err error) {
    sql := "SELECT " + col + " FROM <%= table_name %>"
    if len(where) > 0 {
        sql = sql + " WHERE " + where
    }
    stmt, err := DB.Preparex(DB.Rebind(sql))
    if err != nil {
        log.Println(err)
        return nil, err
    }
    err = stmt.Select(&strColRecs, args...)
    if err != nil {
        log.Println(err)
        return nil, err
    }
    return strColRecs, nil
}

// Find<%= @model_name.pluralize %>Where query use a partial SQL clause that usually following after WHERE
// with placeholders, eg: FindUsersWhere("first_name = ? AND age > ?", "John", 18)
// will return those records in the table "users" whose first_name is "John" and age elder than 18.
func Find<%= @model_name.pluralize %>Where(where string, args ...interface{}) (<%= var_pmn %> []<%= @model_name %>, err error) {
    sql := "SELECT <%= fields %> FROM <%= table_name %>"
    if len(where) > 0 {
        sql = sql + " WHERE " + where
    }
    stmt, err := DB.Preparex(DB.Rebind(sql))
    if err != nil {
        log.Println(err)
        return nil, err
    }
    err = stmt.Select(&<%= var_pmn %>, args...)
    if err != nil {
        log.Println(err)
        return nil, err
    }
    return <%= var_pmn %>, nil
}

// Find<%= @model_name %>BySql query use a complete SQL clause
// with placeholders, eg: FindUserBySql("SELECT * FROM users WHERE first_name = ? AND age > ? ORDER BY DESC LIMIT 1", "John", 18)
// will return only One record in the table "users" whose first_name is "John" and age elder than 18.
func Find<%= @model_name %>BySql(sql string, args ...interface{}) (*<%= @model_name %>, error) {
    stmt, err := DB.Preparex(DB.Rebind(sql))
    if err != nil {
        log.Println(err)
        return nil, err
    }
    _<%= var_umn %> := &<%= @model_name %>{}
    err = stmt.Get(_<%= var_umn %>, args...)
    if err != nil {
        log.Println(err)
        return nil, err
    }
    return _<%= var_umn %>, nil
}

// Find<%= @model_name.pluralize %>BySql query use a complete SQL clause
// with placeholders, eg: FindUsersBySql("SELECT * FROM users WHERE first_name = ? AND age > ?", "John", 18)
// will return those records in the table "users" whose first_name is "John" and age elder than 18.
func Find<%= @model_name.pluralize %>BySql(sql string, args ...interface{}) (<%= var_pmn %> []<%= @model_name %>, err error) {
    stmt, err := DB.Preparex(DB.Rebind(sql))
    if err != nil {
        log.Println(err)
        return nil, err
    }
    err = stmt.Select(&<%= var_pmn %>, args...)
    if err != nil {
        log.Println(err)
        return nil, err
    }
    return <%= var_pmn %>, nil
}

// Create<%= @model_name %> use a named params to create a single <%= @model_name %> record.
// A named params is key-value map like map[string]interface{}{"first_name": "John", "age": 23} .
func Create<%= @model_name %>(am map[string]interface{}) (int64, error) {
    if len(am) == 0 {
        return 0, fmt.Errorf("Zero key in the attributes map!")
    }
<%- unless @struct_info[:timestamp_cols].empty? -%>
    t := time.Now()
    for _, v := range []string{<%= @struct_info[:timestamp_cols].map(&:inspect).join(", ") %>} {
        if am[v] == nil {
            am[v] = t
        }
    }
<%- end -%>
    keys := allKeys(am)
    sqlFmt := `INSERT INTO <%= table_name %> (%s) VALUES (%s)`
    sql := fmt.Sprintf(sqlFmt, strings.Join(keys, ","), ":"+strings.Join(keys, ",:"))
    result, err := DB.NamedExec(sql, am)
    if err != nil {
        log.Println(err)
        return 0, err
    }
    lastId, err := result.LastInsertId()
    if err != nil {
        log.Println(err)
        return 0, err
    }
    return lastId, nil
}

// Create is a method for <%= @model_name %> to create a record.
func (_<%= var_umn %> *<%= @model_name %>) Create() (int64, error) {
    ok, err := govalidator.ValidateStruct(_<%= var_umn %>)
    if !ok {
        errMsg := "Validate <%= @model_name %> struct error: Unknown error"
        if err != nil {
            errMsg = "Validate <%= @model_name %> struct error: " + err.Error()
        }
        log.Println(errMsg)
        return 0, errors.New(errMsg)
    }
<%- unless @struct_info[:timestamp_cols].empty? -%>
    t := time.Now()
    <%- @struct_info[:timestamp_cols].each do |c| -%>
    _<%= var_umn %>.<%= c.camelize %> = t
    <%- end -%>
<%- end -%>
    sql := `INSERT INTO <%= table_name %> (<%= col_names.join(",") %>) VALUES (:<%= col_names.join(",:") %>)`
    result, err := DB.NamedExec(sql, _<%= var_umn %>)
    if err != nil {
        log.Println(err)
        return 0, err
    }
    lastId, err := result.LastInsertId()
    if err != nil {
        log.Println(err)
        return 0, err
    }
    return lastId, nil
}

<%- unless @struct_info[:assoc_info][:has_many].empty? -%>
    <%- has_many = @struct_info[:assoc_info][:has_many] -%>
    <%- has_many.each do |k, v| -%>
// <%= k.pluralize %>Create is used for <%= @model_name %> to create the associated objects <%= k.pluralize %>
func (_<%= var_umn %> *<%= @model_name %>) <%= k.pluralize %>Create(am map[string]interface{}) error {
    <%- if v[:through] -%>
        // FIXME: use transaction to create these associated objects
        <%= v[:class_name].underscore %>Id, err := Create<%= v[:class_name] %>(am)
        if err != nil {
            return err
        }
        _, err = Create<%= v[:through].to_s.singularize.camelize %>(map[string]interface{}{"<%= var_umn %>_id": _<%= var_umn %>.Id, "<%= v[:class_name].underscore %>_id": <%= v[:class_name].underscore %>Id})
    <%- elsif v[:as] -%>
        am["<%= v[:as] %>_id"] = _<%= var_umn %>.Id
        am["<%= v[:as] %>_type"] = "<%= @model_name %>"
        _, err := Create<%= v[:class_name] %>(am)
    <%- else -%>
        <%- if v[:foreign_key] -%>
            am["<%= v[:foreign_key] %>"] = _<%= var_umn %>.Id
        <%- else -%>
            am["<%= var_umn %>_id"] = _<%= var_umn %>.Id
        <%- end -%>
        _, err := Create<%= v[:class_name] %>(am)
    <%- end -%>
    return err
}

// Get<%= k.pluralize %> is used for <%= @model_name %> to get associated objects <%= k.pluralize %>
// Say you have a <%= @model_name %> object named <%= var_umn %>, when you call <%= var_umn %>.Get<%= k.pluralize %>(),
// the object will get the associated <%= k.pluralize %> attributes evaluated in the struct.
func (_<%= var_umn %> *<%= @model_name %>) Get<%= k.pluralize %>() error {
    _<%= k.underscore.pluralize %>, err := <%= @model_name %>Get<%= k.pluralize %>(_<%= var_umn %>.Id)
    if err == nil {
        _<%= var_umn %>.<%= k %> = _<%= k.underscore.pluralize %>
    }
    return err
}

// <%= @model_name %>Get<%= k.pluralize %> a helper fuction used to get associated objects for <%= @model_name %>IncludesWhere().
func <%= @model_name %>Get<%= k.pluralize %>(id int64) ([]<%= v[:class_name] %>, error) {
    <%- if v[:through] -%>
        // FIXME: use transaction to create these associated objects
        <%- target_table = v[:class_name].underscore.pluralize -%>
        <%- through_table = v[:through].to_s.pluralize -%>
        sql := `SELECT <%= @all_structs_info[v[:class_name]][:select_fields] %>
                FROM   <%= target_table %>
                       INNER JOIN <%= through_table %>
                               ON <%= target_table %>.id = <%= through_table %>.<%= v[:class_name].underscore %>_id
                WHERE <%= through_table %>.<%= var_umn %>_id = ?`
        _<%= k.underscore.pluralize %>, err := Find<%= v[:class_name].pluralize %>BySql(sql, id)
    <%- elsif v[:as] -%>
        where := `<%= v[:as] %>_type = "<%= @model_name %>" AND <%= v[:as] %>_id = ?`
        _<%= k.underscore.pluralize %>, err := Find<%= v[:class_name].pluralize %>Where(where, id)
    <%- else -%>
        <%- if v[:foreign_key] -%>
            _<%= k.underscore.pluralize %>, err := Find<%= v[:class_name].pluralize %>By("<%= v[:foreign_key] %>", id)
        <%- else -%>
            _<%= k.underscore.pluralize %>, err := Find<%= v[:class_name].pluralize %>By("<%= var_umn %>_id", id)
        <%- end -%>
    <%- end -%>
    return _<%= k.underscore.pluralize %>, err
}

    <%- end -%>
<%- end -%>

<%- unless @struct_info[:assoc_info][:has_one].empty? -%>
    <%- has_one = @struct_info[:assoc_info][:has_one] -%>
    <%- has_one.each do |k, v| -%>
// Create<%= k %> is a method for the <%= @model_name %> model object to create an associated <%= k %> record.
func (_<%= var_umn %> *<%= @model_name %>) Create<%= k %>(am map[string]interface{}) error {
    <%- if v[:foreign_key] -%>
    am["<%= v[:foreign_key] %>"] = _<%= var_umn %>.Id
    <%- else -%>
    am["<%= var_umn %>_id"] = _<%= var_umn %>.Id
    <%- end -%>
    _, err := Create<%= v[:class_name] %>(am)
    return err
}
    <%- end -%>
<%- end -%>

<%- unless @struct_info[:assoc_info][:belongs_to].empty? -%>
    <%- belongs_to = @struct_info[:assoc_info][:belongs_to] -%>
    <%- belongs_to.each do |k, v| -%>
    <%-# don't create virtual table for polymorphic model -%>
    <%- next if v[:polymorphic] -%>
// Create<%= k %> is a method for a <%= @model_name %> object to create an associated <%= k %> record.
func (_<%= var_umn %> *<%= @model_name %>) Create<%= k %>(am map[string]interface{}) error {
    <%- if v[:foreign_key] -%>
    am["<%= v[:foreign_key] %>"] = _<%= var_umn %>.Id
    <%- else -%>
    am["<%= var_umn %>_id"] = _<%= var_umn %>.Id
    <%- end -%>
    _, err := Create<%= v[:class_name] %>(am)
    return err
}
    <%- end -%>
<%- end -%>

// Destroy is method used for a <%= @model_name %> object to be destroyed.
func (_<%= var_umn %> *<%= @model_name %>) Destroy() error {
    if _<%= var_umn %>.Id == 0 {
        return errors.New("Invalid Id field: it can't be a zero value")
    }
    err := Destroy<%= @model_name %>(_<%= var_umn %>.Id)
    return err
}

// Destroy<%= @model_name %> will destroy a <%= @model_name %> record specified by the id parameter.
func Destroy<%= @model_name %>(id int64) error {
    <%- if @struct_info[:has_assoc_dependent] -%>
    // Destroy association objects at first
    // Not care if exec properly temporarily
    destroy<%= @model_name %>Associations(id)
    <%- end -%>
    stmt, err := DB.Preparex(DB.Rebind(`DELETE FROM <%= table_name %> WHERE id = ?`))
    _, err = stmt.Exec(id)
    if err != nil {
        return err
    }
    return nil
}

// Destroy<%= @model_name.pluralize %> will destroy <%= @model_name %> records those specified by the ids parameters.
func Destroy<%= @model_name.pluralize %>(ids ...int64) (int64, error) {
    if len(ids) == 0 {
        msg := "At least one or more ids needed"
        log.Println(msg)
        return 0, errors.New(msg)
    }
    <%- if @struct_info[:has_assoc_dependent] -%>
    // Destroy association objects at first
    // Not care if exec properly temporarily
    destroy<%= @model_name %>Associations(ids...)
    <%- end -%>
    idsHolder := strings.Repeat(",?", len(ids)-1)
    sql := fmt.Sprintf(`DELETE FROM <%= table_name %> WHERE id IN (?%s)`, idsHolder)
    idsT := []interface{}{}
    for _,id := range ids {
        idsT = append(idsT, interface{}(id))
    }
    stmt, err := DB.Preparex(DB.Rebind(sql))
    result, err := stmt.Exec(idsT...)
    if err != nil {
        return 0, err
    }
    cnt, err := result.RowsAffected()
    if err != nil {
        return 0, err
    }
    return cnt, nil
}

// Destroy<%= @model_name.pluralize %>Where delete records by a where clause restriction.
// e.g. Destroy<%= @model_name.pluralize %>Where("name = ?", "John")
// And this func will not call the association dependent action
func Destroy<%= @model_name.pluralize %>Where(where string, args ...interface{}) (int64, error) {
    sql := `DELETE FROM <%= table_name %> WHERE `
    if len(where) > 0 {
        sql = sql + where
    } else {
        return 0, errors.New("No WHERE conditions provided")
    }
    <%- if @struct_info[:has_assoc_dependent] -%>
    ids, x_err := <%= @model_name %>IdsWhere(where, args...)
    if x_err != nil {
        log.Printf("Delete associated objects error: %v\n", x_err)
    } else {
        destroy<%= @model_name %>Associations(ids...)
    }
    <%- end -%>
    stmt, err := DB.Preparex(DB.Rebind(sql))
    result, err := stmt.Exec(args...)
    if err != nil {
        return 0, err
    }
    cnt, err := result.RowsAffected()
    if err != nil {
        return 0, err
    }
    return cnt, nil
}

<%- if @struct_info[:has_assoc_dependent] -%>
// destroy<%= @model_name %>Associations is a private function used to destroy a <%= @model_name %> record's associated objects.
// The func not return err temporarily.
func destroy<%= @model_name %>Associations(ids ...int64) {
    idsHolder := ""
    if len(ids) > 1 {
        idsHolder = strings.Repeat(",?", len(ids)-1)
    }
    idsT := []interface{}{}
    for _, id := range ids {
        idsT = append(idsT, interface{}(id))
    }
    var err error
    // make sure no declared-and-not-used exception
    _, _, _ = idsHolder, idsT, err
    <%- [:has_many, :has_one].each do |ass| -%>
        <%- ass_cols = @struct_info[:assoc_info][ass] -%>
        <%- unless ass_cols.empty? -%>
            <%- ass_cols.each_value do |opts| -%>
                <%- if opts.key? :dependent -%>
                    <%- case opts[:dependent] -%>
                    <%- when :destroy, :delete_all -%>
                        <%- if opts[:through] -%>
                            where := fmt.Sprintf("id IN (SELECT id FROM <%= opts[:through].to_s %> WHERE <%= var_umn %>_id IN (?%s))", idsHolder)
                            _, err = Destroy<%= opts[:class_name].pluralize %>Where(where, idsT...)
                            if err != nil {
                                log.Printf("Destroy associated object %s error: %v\n", "<%= opts[:class_name].pluralize %>", err)
                            }
                            where = fmt.Sprintf("<%= var_umn %>_id IN (?%s)", idsHolder)
                            _, err = Destroy<%= opts[:through].to_s.pluralize.camelize %>Where(where, idsT...)
                            if err != nil {
                                log.Printf("Destroy associated object %s error: %v\n", "<%= opts[:through].to_s.singularize.camelize %>", err)
                            }
                        <%- elsif opts[:as] -%>
                            where := fmt.Sprintf(`<%= opts[:as] %>_type = "<%= @model_name %>" AND <%= opts[:as] %>_id IN (?%s)`, idsHolder)
                            _, err = Destroy<%= opts[:class_name].pluralize %>Where(where, idsT...)
                            if err != nil {
                                log.Printf("Destroy associated object %s error: %v\n", "<%= opts[:class_name].pluralize %>", err)
                            }
                        <%- else -%>
                            <%- if opts.key? :foreign_key -%>
                                where := fmt.Sprintf("<%= opts[:foreign_key] %> IN (?%s)", idsHolder)
                            <%- else -%>
                                where := fmt.Sprintf("<%= var_umn %>_id IN (?%s)", idsHolder)
                            <%- end -%>
                            _, err = Destroy<%= opts[:class_name].pluralize %>Where(where, idsT...)
                            if err != nil {
                                log.Printf("Destroy associated object %s error: %v\n", "<%= opts[:class_name].pluralize %>", err)
                            }
                        <%- end -%>
                    <%- when :nullify -%>
                        // no sql.NULLType supported, just set the associated field to zero value of int64
                        <%- if opts[:through] -%>
                            sql := fmt.Sprintf("UPDATE <%= opts[:through].to_s %> SET <%= opts[:class_name].underscore %>_id = 0 WHERE <%= opts[:class_name].underscore %>_id IN (?%s)", idsHolder)
                            _, err = Update<%= opts[:through].to_s.camelize %>BySql(sql, idsT...)
                            if err != nil {
                                log.Printf("Delete associated object %s error: %v\n", "<%= opts[:class_name].pluralize %>", err)
                            }
                        <%- else -%>
                            <%- if opts.key? :foreign_key -%>
                                sql := fmt.Sprintf("UPDATE <%= opts[:class_name].underscore.pluralize %> SET <%= opts[:foreign_key] %> = 0 WHERE <%= opts[:foreign_key] %> IN (?%s)", idsHolder)
                            <%- else -%>
                                sql := fmt.Sprintf("UPDATE <%= opts[:class_name].underscore.pluralize %> SET <%= var_umn %>_id = 0 WHERE <%= var_umn %>_id IN (?%s)", idsHolder)
                            <%- end -%>
                            _, err = Update<%= opts[:class_name].pluralize %>BySql(sql, idsT...)
                            if err != nil {
                                log.Printf("Delete associated object %s error: %v\n", "<%= opts[:class_name].pluralize %>", err)
                            }
                        <%- end -%>
                    <%- end -%>
                <%- end -%>
            <%- end -%>
        <%- end -%>
    <%- end -%>
}
<%- end -%>

// Save method is used for a <%= @model_name %> object to update an existed record mainly.
// If no id provided a new record will be created. FIXME: A UPSERT action will be implemented further.
func (_<%= var_umn %> *<%= @model_name %>) Save() error {
    ok, err := govalidator.ValidateStruct(_<%= var_umn %>)
    if !ok {
        errMsg := "Validate <%= @model_name %> struct error: Unknown error"
        if err != nil {
            errMsg = "Validate <%= @model_name %> struct error: " + err.Error()
        }
        log.Println(errMsg)
        return errors.New(errMsg)
    }
    if _<%= var_umn %>.Id == 0 {
        _, err = _<%= var_umn %>.Create()
        return err
    }
<%- if @struct_info[:timestamp_cols].include? "updated_at" -%>
    _<%= var_umn %>.UpdatedAt = time.Now()
<%- end -%>
    sqlFmt := `UPDATE <%= table_name %> SET %s WHERE id = %v`
    <%- save_col_names = col_names - ["created_at"] -%>
    sqlStr := fmt.Sprintf(sqlFmt, "<%= save_col_names.zip(save_col_names).map{|c| c.join(" = :")}.join(", ") %>", _<%= var_umn %>.Id)
    _, err = DB.NamedExec(sqlStr, _<%= var_umn %>)
    return err
}

// Update<%= @model_name %> is used to update a record with a id and map[string]interface{} typed key-value parameters.
func Update<%= @model_name %>(id int64, am map[string]interface{}) error {
    if len(am) == 0 {
        return errors.New("Zero key in the attributes map!")
    }
<%- if @struct_info[:timestamp_cols].include? "updated_at" -%>
    am["updated_at"] = time.Now()
<%- end -%>
    keys := allKeys(am)
    sqlFmt := `UPDATE <%= table_name %> SET %s WHERE id = %v`
    setKeysArr := []string{}
    for _,v := range keys {
        s := fmt.Sprintf(" %s = :%s", v, v)
        setKeysArr = append(setKeysArr, s)
    }
    sqlStr := fmt.Sprintf(sqlFmt, strings.Join(setKeysArr, ", "), id)
    _, err := DB.NamedExec(sqlStr, am)
    if err != nil {
        log.Println(err)
        return err
    }
    return nil
}

// Update is a method used to update a <%= @model_name %> record with the map[string]interface{} typed key-value parameters.
func (_<%= var_umn %> *<%= @model_name %>) Update(am map[string]interface{}) error {
    if _<%= var_umn %>.Id == 0 {
        return errors.New("Invalid Id field: it can't be a zero value")
    }
    err := Update<%= @model_name %>(_<%= var_umn %>.Id, am)
    return err
}

// UpdateAttributes method is supposed to be used to update <%= @model_name %> records as corresponding update_attributes in Ruby on Rails.
func (_<%= var_umn %> *<%= @model_name %>) UpdateAttributes(am map[string]interface{}) error {
    if _<%= var_umn %>.Id == 0 {
        return errors.New("Invalid Id field: it can't be a zero value")
    }
    err := Update<%= @model_name %>(_<%= var_umn %>.Id, am)
    return err
}

// UpdateColumns method is supposed to be used to update <%= @model_name %> records as corresponding update_columns in Ruby on Rails.
func (_<%= var_umn %> *<%= @model_name %>) UpdateColumns(am map[string]interface{}) error {
    if _<%= var_umn %>.Id == 0 {
        return errors.New("Invalid Id field: it can't be a zero value")
    }
    err := Update<%= @model_name %>(_<%= var_umn %>.Id, am)
    return err
}

// Update<%= @model_name.pluralize %>BySql is used to update <%= @model_name %> records by a SQL clause
// using the '?' binding syntax.
func Update<%= @model_name.pluralize %>BySql(sql string, args ...interface{}) (int64, error) {
    if sql == "" {
        return 0, errors.New("A blank SQL clause")
    }
    stmt, err := DB.Preparex(DB.Rebind(sql))
    result, err := stmt.Exec(args...)
    if err != nil {
        return 0, err
    }
    cnt, err := result.RowsAffected()
    if err != nil {
        return 0, err
    }
    return cnt, nil
}