structset.go

Summary

Maintainability
A
35 mins
Test Coverage
A
100%
package rel

import (
    "fmt"
    "time"
)

var (
    Now NowFunc = func() time.Time {
        return time.Now().Truncate(time.Second)
    }
)

// NowFunc is the type of function that returns the current time.
type NowFunc func() time.Time

// Structset can be used as mutation for repository insert or update operation.
// This will save every field in struct and it's association as long as it's loaded.
// This is the default mutator used by repository.
type Structset struct {
    doc      *Document
    skipZero bool
}

// Apply mutation.
func (s Structset) Apply(doc *Document, mut *Mutation) {
    var (
        pFields = s.doc.PrimaryFields()
        t       = Now()
    )

    for _, field := range s.doc.Fields() {
        switch field {
        case "created_at", "inserted_at":
            if doc.Flag(HasCreatedAt) {
                if value, ok := doc.Value(field); ok && value.(time.Time).IsZero() {
                    s.set(doc, mut, field, t, true)
                    continue
                }
            }
        case "updated_at":
            if doc.Flag(HasUpdatedAt) {
                s.set(doc, mut, field, t, true)
                continue
            }
        }

        if len(pFields) == 1 && pFields[0] == field {
            // allow setting primary key as long as it's not zero.
            s.applyValue(doc, mut, field, true)
        } else {
            s.applyValue(doc, mut, field, s.skipZero)
        }
    }

    if mut.Cascade {
        s.applyAssoc(mut)
    }
}

func (s Structset) set(doc *Document, mut *Mutation, field string, value any, force bool) {
    if (force || doc.v != s.doc.v) && !doc.SetValue(field, value) {
        panic(fmt.Sprint("rel: cannot assign ", value, " as ", field, " into ", doc.Table()))
    }

    mut.Add(Set(field, value))
}

func (s Structset) applyValue(doc *Document, mut *Mutation, field string, skipZero bool) {
    if value, ok := s.doc.Value(field); ok {
        if skipZero && isZero(value) {
            return
        }

        s.set(doc, mut, field, value, false)
    }
}

func (s Structset) applyAssoc(mut *Mutation) {
    for _, field := range s.doc.BelongsTo() {
        s.buildAssoc(field, mut)
    }

    for _, field := range s.doc.HasOne() {
        s.buildAssoc(field, mut)
    }

    for _, field := range s.doc.HasMany() {
        s.buildAssocMany(field, mut)
    }
}

func (s Structset) buildAssoc(field string, mut *Mutation) {
    assoc := s.doc.Association(field)
    if assoc.IsZero() {
        return
    }

    var (
        doc, _ = assoc.Document()
    )

    mut.SetAssoc(field, Apply(doc, newStructset(doc, s.skipZero)))
}

func (s Structset) buildAssocMany(field string, mut *Mutation) {
    assoc := s.doc.Association(field)
    if assoc.IsZero() {
        return
    }

    var (
        col, _ = assoc.Collection()
        muts   = make([]Mutation, col.Len())
    )

    for i := range muts {
        var (
            doc = col.Get(i)
        )

        muts[i] = Apply(doc, newStructset(doc, s.skipZero))
    }

    mut.SetAssoc(field, muts...)
}

func newStructset(doc *Document, skipZero bool) Structset {
    return Structset{
        doc:      doc,
        skipZero: skipZero,
    }
}

// NewStructset from a struct.
func NewStructset(entity any, skipZero bool) Structset {
    return newStructset(NewDocument(entity), skipZero)
}