argen
is an ORM code-generation tool for Go, provides ActiveRecord-like functionality for your types.
$ go get -u github.com/monochromegane/argen/...
- Define a table mapping struct in
main.go
. - Mark it up with a
+AR
annotation in an adjacent comment like so:
//+AR
type User struct{
Id int `db:"pk"`
Name string
Age int
}
And at the command line, simply type:
$ argen main.go
You should see new files, named *_gen.go
.
And you can use ActiveRecord-like functions.
db, _ := sql.Open("sqlite3", "foo.db")
Use(db)
u := User{Name: "test", Age: 20}
u.Save()
//// INSERT INTO users (name, age) VALUES (?, ?); [test 20]
User{}.Where("name", "test").And("age", ">", 20).Query()
//// SELECT users.id, users.name, users.age FROM users WHERE name = ? AND age > ?; [test 20]
db, _ := sql.Open("sqlite3", "foo.db")
// Set db
Use(db)
u := User{Name: "test", Age: 20}
u.Save()
or
// XxxParams has same fields to original type.
User{}.Create(UserParams{Name: "test", Age: 20})
// Get the first record
User{}.First()
//// SELECT users.id, users.name, users.age FROM users ORDER BY id ASC LIMIT ?; [1]
// Get the last record
User{}.Last()
//// SELECT users.id, users.name, users.age FROM users ORDER BY id DESC LIMIT ?; [1]
// Get the record by Id
User{}.Find(1)
//// SELECT users.id, users.name, users.age FROM users WHERE id = ? LIMIT ?; [1 1]
// Get all record
User{}.All().Query()
//// SELECT users.id, users.name, users.age FROM users;
// Get the first matched record
User{}.FindBy("name", "test")
//// SELECT users.id, users.name, users.age FROM users WHERE name = ? LIMIT ?; [test 1]
// Get the first matched record
User{}.Where("name", "test").QueryRow()
//// SELECT users.id, users.name, users.age FROM users WHERE name = ?; [test]
// Get the all matched records
User{}.Where("name", "test").Query()
//// SELECT users.id, users.name, users.age FROM users WHERE name = ?; [test]
// And
User{}.Where("name", "test").And("age", ">", 20).Query()
//// SELECT users.id, users.name, users.age FROM users WHERE name = ? AND age > ?; [test 20]
// Count
User{}.Count()
//// SELECT COUNT(*) FROM users;
// Exists
User{}.Exists()
//// SELECT 1 FROM users LIMIT ?; [1]
// Select
User{}.Select("id", "name").Query()
//// SELECT users.id, users.name FROM users;
// Order
User{}.Order("name", "ASC").Query()
//// SELECT users.id, users.name, users.age FROM users ORDER BY name ASC;
// Limit and offset
User{}.Limit(1).Offset(2).Query()
//// SELECT users.id, users.name, users.age FROM users LIMIT ? OFFSET ?; [1 2]
// GroupBy and having
User{}.Group("name").Having("count(name)", 2).Query()
//// SELECT users.id, users.name, users.age FROM users GROUP BY name HAVING count(name) = ?; [2]
user, _ := User{}.Find(1)
// Update an existing struct
user.Name = "a"
user.Save()
//// UPDATE users SET id = ?, name = ?, age = ? WHERE id = ?; [1 a 20 1]
// Update attributes with validation
user.Update(UserParams{Name: "b"})
//// UPDATE users SET id = ?, name = ?, age = ? WHERE id = ?; [1 b 20 1]
// Update attributes without validation
user.UpdateColumns(UserParams{Name: "c"})
//// UPDATE users SET id = ?, name = ?, age = ? WHERE id = ?; [1 b 20 1]
user, _ := User{}.Find(1)
// Delete an existing struct
user.Destroy()
//// DELETE FROM users WHERE id = ?; [1]
Add association function to your type:
func (m User) hasOnePost() *ar.Association {
return nil
}
And type argen
or go generate
on your command line.
user, _ := User{}.Create(UserParams{Name: "user1"})
user.BuildPost(PostParams{Name: "post1"}).Save()
// Get the related record
user.Post()
//// SELECT posts.id, posts.user_id, posts.name FROM posts WHERE user_id = ?; [1]
// Join
user.JoinsPost().Query()
//// SELECT users.id, users.name, users.age FROM users INNER JOIN posts ON posts.user_id = users.id;
Add association function to your type:
func (m User) hasManyPosts() *ar.Association {
return nil
}
And type argen
or go generate
on your command line.
user, _ := User{}.Create(UserParams{Name: "user1"})
user.BuildPost(PostParams{Name: "post1"}).Save()
// Get the related records
user.Posts()
//// SELECT posts.id, posts.user_id, posts.name FROM posts WHERE user_id = ?; [1]
// Join
user.JoinsPosts()
//// SELECT users.id, users.name, users.age FROM users INNER JOIN posts ON posts.user_id = users.id;
Add association function to your type:
func (p Post) belongsToUser() *ar.Association {
return nil
}
And type argen
or go generate
on your command line.
u, _ := User{}.Create(UserParams{Name: "user1"})
post, _ := u.BuildPost(PostParams{Name: "post1"})
post.Save()
// Get the related record
post.User()
//// SELECT users.id, users.name, users.age FROM users WHERE id = ?; [1]
// Join
post.JoinsUser().Query()
//// SELECT posts.id, posts.user_id, posts.name FROM posts INNER JOIN users ON users.id = posts.user_id;
Add validation function to your type:
func (u User) validatesName() ar.Rule {
// Define rule for "name" column
return ar.MakeRule().Format().With("[a-z]")
}
You specify target column by naming validatesXxx
.
// presence
Presence()
// format
Formart().With("regex")
// numericality
Numericality().OnlyInteger().GreaterThan(10)
// OnCreate
OnCreate()
// OnUpdate
OnUpdate()
Validation rules has a chainable API. you could use it like this.
ar.MakeRule().
Presence().
Format().With("[a-z]").
OnCreate()
Add custom validation function to your type:
func (p Post) validateCustom() ar.Rule {
return ar.CustomRule(func(errors *ar.Errors) {
if p.Name != "name" {
errors.Add("name", "must be name")
}
}).OnUpdate()
}
And type argen
or go generate
on your command line.
u := User{Name: "invalid name"}
_, errs := u.IsValid()
if errs != nil {
fmt.Errorf("%v\n", errs.Messages["name"])
}
LogMode(true)
If you want to generate it by using go generate
, you mark it up an annotation at top of file.
//go:generate argen
and type:
$ go generate
- Transaction
- Callbacks (before/after save)
- Conditions for callbacks and validations.
AS
clause- Log options
monochromegane
argen is licensed under the MIT