/argen

An ORM code-generation tool for Go, provides ActiveRecord-like functionality for your types.

Primary LanguageGoMIT LicenseMIT

argen

argen is an ORM code-generation tool for Go, provides ActiveRecord-like functionality for your types.

Installation

$ go get -u github.com/monochromegane/argen/...

Quick start

  1. Define a table mapping struct in main.go.
  2. 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]

ActiveRecord-like functions

Setup

db, _ := sql.Open("sqlite3", "foo.db")

// Set db
Use(db)

Create record

u := User{Name: "test", Age: 20}
u.Save()

or

// XxxParams has same fields to original type.
User{}.Create(UserParams{Name: "test", Age: 20})

Query

// 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;

Query with conditions

// 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]

Update

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]

Delete

user, _ := User{}.Find(1)

// Delete an existing struct
user.Destroy()
//// DELETE FROM users WHERE id = ?; [1]

Associations

Has One

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;

Has Many

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;

Belongs To

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;

Validation

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.

Rules

// presence
Presence()

// format
Formart().With("regex")

// numericality
Numericality().OnlyInteger().GreaterThan(10)

Trigger

// OnCreate
OnCreate()

// OnUpdate
OnUpdate()

Method chain

Validation rules has a chainable API. you could use it like this.

ar.MakeRule().
	Presence().
	Format().With("[a-z]").
	OnCreate()

Custom validation

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.

Error

u := User{Name: "invalid name"}
_, errs := u.IsValid()
if errs != nil {
	fmt.Errorf("%v\n", errs.Messages["name"])
}

Log

LogMode(true)

go generate

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

Code status

Build Status

TODO

  • Transaction
  • Callbacks (before/after save)
  • Conditions for callbacks and validations.
  • AS clause
  • Log options

Author

monochromegane

License

argen is licensed under the MIT