Skip to content

Instantly share code, notes, and snippets.

@nathanborror
Created October 25, 2017 21:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nathanborror/7d0ccc7b22370761ad26cba05c09abdc to your computer and use it in GitHub Desktop.
Save nathanborror/7d0ccc7b22370761ad26cba05c09abdc to your computer and use it in GitHub Desktop.
package ledger
import (
"encoding/json"
"time"
"github.com/jmoiron/sqlx"
"github.com/jmoiron/sqlx/types"
uuid "github.com/satori/go.uuid"
)
// Identifider is the interface that wraps methods used to identify items.
type Identifider interface {
IdentifyID() string
IdentifyType() string
}
// Applyer is the interface that wraps methods for applying ledger values to
// the underlying types they wrap.
type Applyer interface {
ApplyID(id string)
ApplyTime(created, modified time.Time)
}
// Record is a storable record that typically wraps a given blob of data.
// It maintains an unique identifier and a creation time.
type Record struct {
db *sqlx.DB
err error
DataType string `db:"datatype"`
Data types.JSONText `db:"data"`
Time time.Time `db:"time"`
ID string `db:"id"`
}
// Options describes options for Record.
type Options struct {
DB *sqlx.DB
}
// Schema describes the SQL table used to store records.
const Schema = `
CREATE TABLE IF NOT EXISTS record (
added_id serial PRIMARY KEY,
id uuid NOT NULL,
datatype varchar(32) NOT NULL,
data jsonb,
time timestamp NOT NULL DEFAULT current_timestamp
)`
// NewRecord returns a new Record that wraps data.
func NewRecord(data Identifider, options Options) *Record {
r := Record{
db: options.DB,
ID: data.IdentifyID(),
DataType: data.IdentifyType(),
}
if r.ID == "" {
r.ID = uuid.NewV4().String()
}
r.Data, r.err = json.Marshal(data)
return &r
}
// Read returns an existing Record that matches id.
func Read(id string, options Options) *Record {
r := Record{ID: id, db: options.DB}
row := r.db.QueryRow(`SELECT id,datatype,data,time FROM record WHERE id = $1 ORDER BY time DESC LIMIT 1`, r.ID)
r.err = row.Scan(&r.ID, &r.DataType, &r.Data, &r.Time)
return &r
}
// Write stores a copy of the current Record.
func (r *Record) Write() {
if r.err != nil {
return
}
row := r.db.QueryRow(`INSERT INTO record (id, datatype, data) VALUES ($1, $2, $3) RETURNING time`, r.ID, r.DataType, r.Data)
r.err = row.Scan(&r.Time)
}
// Delete clears the record data.
func (r *Record) Delete() {
if r.err != nil {
return
}
r.Data = []byte("{}")
}
// Restore restores the record to a given time.
func (r *Record) Restore(t time.Time) {
if r.err != nil {
return
}
row := r.db.QueryRow(`SELECT id,datatype,data FROM record WHERE id = $1 AND time = $2 LIMIT 1`, r.ID, t)
r.err = row.Scan(&r.ID, &r.DataType, &r.Data)
}
// Unmarshal parses the JSON-encoded data and stores the result in the
// value pointed to by v. The Record ID is applied to the data as well
// as the Created and Modified times.
func (r *Record) Unmarshal(v Applyer) {
if r.err != nil {
return
}
err := r.Data.Unmarshal(v)
if err != nil {
r.err = err
return
}
var created time.Time
row := r.db.QueryRow(`SELECT time FROM record WHERE id = $1 ORDER BY time ASC LIMIT 1`, r.ID)
err = row.Scan(&created)
if err != nil {
r.err = err
return
}
v.ApplyID(r.ID)
v.ApplyTime(created, r.Time)
}
// Err returns the first error that was encountered by the Record.
func (r *Record) Err() error {
return r.err
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment