Note: Weasel is just now reaching a stable API. Most necessary features are available or easily integratable. You may still expect a few API changes, but the majority should be usable.
Weasel is the last ORM for Golang you'll ever need. Built with Generics, so it requires at least Go 1.18+.
See the docs
Here's an example of the API:
package main
import (
"github.com/ztcollazo/weasel" // Main package
"github.com/ztcollazo/weasel/use" // Use package contains middleware and validations
"github.com/lib/pq" // Or whatever db you're using
)
func main() {
// Connect is a wrapper around SQLX's connect that takes a custom Opts strcut; see that for more details.
conn := weasel.Connect("postgres", weasel.Opts{
Database: "postgres",
User: "whoever",
})
// Let's create the schema now
type PersonSchema struct {
weasel.Document[*PersonSchema] // Note the pointer!!!
// PK denotes it as the primary key
Id int `db:"id" pk:"" type:"serial"`
FirstName string `db:"first_name" type:"text"`
LastName string `db:"last_name" type:"text"`
Email string `db:"email" type:"text"`
PlaceId int `db:"place_id" type:"integer"`
// Relations
Place weasel.BelongsTo[*PlaceSchema] `belongsto:"place"` // Again with the required pointer
Hello string
}
// You can define an `Init` function that is called whenever a document is created.
// Again, the method MUST have a pointer receiver.
func (p *PersonSchema) Init() {
p.Hello = "world"
// Relations, supports: BelongsTo, HasMany (and through), HasOne
// Note: This format has been changed.
// Old: weasel.UseBelongsTo(p, Place)
p.Use(use.BelongsTo[*PersonSchema](Place))
// The other relations are fairly straightforward:
// HasOne is basically the same as BelongsTo
// HasMany is a Group (see below)
// HasMany through is slightly different
// In that case, you would still use the HasMany function
// But in the schema, you would add a `through` tag
// with the intermittent model
// this is also where you would do your validations
// d.Errors is an []error
if p.FirstName == "" {
p.AddError(errors.New("missing first name"))
}
// or
p.Use(use.ValidatePresenceOf[string /* validate presence requires data type */]("first_name"))
// Also supports:
// Custom: Validate(field, func(val type) bool)
// Unique: ValidateUniquenessOf(field)
// Unique Combination: ValidateUniqueCombination(...fields)
// Format: ValidateFormatOf(field, regexp)
}
// Now for the fun part
// Types are inferred from the second parameter; it's only there so that we can copy it
Person := weasel.Create(conn, &PersonSchema{}, "person") // returns *Model[*PersonSchema]
// Or you can define an init (or multiple) function also
Person := weasel.Create(conn, &PersonSchema, "person", func (m *Model[*PersonSchema]) {
// You can define properties on the model
m.Set("key", "value")
m.Get("key") //=> "value"
})
// Done! use it like you would Active Record
p, _ := Person.Find(1)
p.FirstName // 🤯 🥳
p.Hello //=> "world"
john, err /* error handling also */ = Person.Create(&PersonSchema{
FirstName: "John",
LastName: "Doe",
Email: "[email protected]",
PlaceId: 1,
})
john.Email //=> [email protected]
john.Email = "[email protected]"
john.Save() // Pretty intuitive
// And then when you're done
john.Delete()
// You can also do batch queries
people, _ := Person.All().Where(weasel.Eq{"first_name": "John"}).Limit(3).Offset(6).Exec() // For built queries, make sure that you append exec.
// people => []*PersonSchema{...}
// Or specific queries
jane := Person.FindBy("first_name", "Jane")
// Now let's get the place
jane.Place() //=> *PlaceSchema{...}
// You can also check if a document is valid
jane.IsValid() //=> true
jane.FirstName = ""
jane.IsValid() //=> false
jane.IsInvalid() //=> true
// You can add groups to group together documents with certain properties
Person.AddGroup("FromUS", weasel.Eq{"place_id": 1})
// And now
Person.FromGroup("FromUS").All().Exec() // Same API as Model
// To learn more about groups, please see below
// You can do many other useful features such as:
Person.Exists(1)
Person.First() // Up to fifth
Person.Last() // up to second
Person.Nth(7)
Person.NthFromLast(3)
// To change to order of the documents, you can do:
Person.Order("first_name DESC") // Etc.
// You can also serialize documents:
p, _ := Person.First()
json, _ := Person.ToJSON()
// Or for custom serialization:
mp := p.ToMap() // Creates a map of all of the fields
}
Groups are an extremely valuable feature in ORMs. They allow you to combine documents with similar features, all without having to repeat your queries over and over. Groups in weasel are the foundation of not only themselves, but also models. A few rules:
-
Groups depend on models
Models are what give groups the data about the table itself. This is not left to the groups.
-
Models depend on groups
If you look in the code, you will find that
Model[Doc]
actually extends*Group[Doc]
. This is interesting, because that means that, while models give groups all of the data, groups give models all of the functionality.
- Connection + multiple drivers
- Query builder
- Create
- Read
- Update
- Delete
- Relations
- Validations
- Check valid
- Errors
- Validate Presence
- Validate Format
- Validate Custom
- Validate Uniqueness
- Model Groups
- Model utilities
- Find nth document
- Find nth to last document
- Check if document exists
- Count of documents
- Serialize documents
- CLI?
- Create model files
- Migrations?
-
Better config formatMany drivers include their own structs that you can format into an opts string for a better UX.
...and any that may come up in the future.
Weasel is licensed under the MIT license. View the LICENSE.txt for more information.