STORM is Semantically Terse Object Relational Mapping for Go
This software is NOT ready for production use. It is currently an experimental proof of concept. The code is fragile, volatile and not reliable!
That said, please contribute your ideas and feedback!
go get github.com/brendensoares/storm
import "github.com/brendensoares/storm"
- Add
storm.Model
to a Go struct (e.g.type ModelName struct {...}
) - Create a
func NewModelName() *ModelName
that returnsstorm.Factory(&ModelName{}).(*ModelName)
import _ "github.com/brendensoares/storm/driver/mysql"
modelName := NewModelName()
and start using your new model!
STORM's object generator is important because it allows a developer to use the database schema as the authority for the design of the models. You can consider the generator as a tool to create a schema cache that would otherwise be done dynamically.
Data containers (e.g. SQL table) are mapped to Go Structs and container fields (e.g. SQL columns) are mapped to struct fields on each generated struct.
To generate a cache of your MySQL database's schema, simply run:
storm generate mysql -host localhost -user dbuser -p dbpass -name dbname
From there, you can customize the object's definition as long as the meta data remains so that STORM can properly translate object data to the database.
A generated STORM model is composed of typical Go data types along with tag-based meta data. A model could look something like this:
type Post struct {
storm.Model `container:"posts" driver:"mysql"`
Id int64 `alias:"post_id"`
Title string `length:"100" nullable:"true"`
TransientTitle string `ignore:"yes"`
Content string `type:"text"`
CreatedAt time.Time
UpdateAt time.Time
DeletedAt time.Time
Categories *Category `relation:"hasMany" through:"categories_posts/post_id"`
Comments *Comment `relation:"hasMany" key:"post_id"`
Author *Author `relation:"belongsTo" key:"author_id"`
ParentPost *Post `relation:"hasOne" key:"parent_post_id"`
}
func NewPost() *Post {
return storm.Factory(&Post{}).(*Post)
}
func (self *Post)Validate() bool {
if self.len(Content) < 10000 {
return true
}
return false
}
post := NewPost()
post.Title = "Hello, World!"
saveError := post.Save()
if post.IsLoaded() {
post.Title = "Hello, Earth!"
if saveError := post.Save(); saveError == nil {
// Do stuff!
}
}
post1 := NewPost()
// For numeric `Id`
post1.Get(1)
post2 := NewPost()
// For string `Id`
post2.Get("2")
post := NewPost()
post.Where("title", "like", "Hello%").And("title", "not like", "%earth%").Or("title").Get()
fmt.Println(post.Title)
post := NewPost()
matchedPosts := post.Where("title", "like", "hello%").Limit(5).Order("title", "asc").All()
for _, post := range matchedPosts {
fmt.Println(post.Title)
}
post.Delete()
NewPost().Has("author", commentAuthor).Delete()
if post.Load("Categories") {
categoryTitle := post.Categories[0].Title
}
if post.Load("Comments") {
commentAuthor := post.Comments[0].Author.DisplayName
}
if post.ParentPost != nil {
parentTitle := post.ParentPost.Title
}
if post.Author != nil {
authorName := post.Author.DisplayName
}
category1 := NewCategory()
category1.Title = "Test Category 1"
category2 := NewCategory()
category2.Title = "Test Category 2"
categoryCountBefore := len(post.Categories)
addError := post.Add("Categories", category1, category2)
categoryCountAfter := len(post.Categories)
if addError != nil || categoryCountAfter - categoryCountBefore != 2 {
// Something smells fishy.
}
author := NewAuthor()
author.DisplayName = "John Smith"
post.Author = author
parentPost := NewPost()
parentPost.Get(20)
post.ParentPost = parentPost
category := NewCategory()
category.Get(10)
if removeError := post.Remove("Categories", category); removeError != nil {
// Error...The game is afoot!
}
post.Author = nil
post.ParentPost = nil