{"id":13776208,"url":"https://github.com/eaigner/hood","last_synced_at":"2025-05-11T10:30:51.771Z","repository":{"id":5942039,"uuid":"7162561","full_name":"eaigner/hood","owner":"eaigner","description":"Database agnostic ORM for Go","archived":false,"fork":false,"pushed_at":"2017-06-01T20:47:51.000Z","size":1016,"stargazers_count":710,"open_issues_count":34,"forks_count":52,"subscribers_count":36,"default_branch":"master","last_synced_at":"2024-11-08T09:26:21.090Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eaigner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-12-14T08:54:45.000Z","updated_at":"2024-10-14T11:14:05.000Z","dependencies_parsed_at":"2022-08-31T11:00:23.091Z","dependency_job_id":null,"html_url":"https://github.com/eaigner/hood","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eaigner%2Fhood","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eaigner%2Fhood/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eaigner%2Fhood/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eaigner%2Fhood/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eaigner","download_url":"https://codeload.github.com/eaigner/hood/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253551486,"owners_count":21926302,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T18:00:19.184Z","updated_at":"2025-05-11T10:30:51.449Z","avatar_url":"https://github.com/eaigner.png","language":"Go","funding_links":[],"categories":["ORM"],"sub_categories":["Advanced Console UIs","Middlewares"],"readme":"If you are looking for something more lightweight and flexible, have a look at [jet](http://github.com/eaigner/jet)\n\nFor questions, suggestions and general topics visit the [group](https://groups.google.com/forum/#!forum/golang-hood).\n\n## Index\n\n- [Overview](#overview)\n- [Documentation](#documentation)\n- [Opening a Database](#opening-a-database)\n- [Schemas](#schemas)\n- [Migrations](#migrations)\n- [Validation](#validation)\n- [Hooks](#hooks)\n- [Basic Example](#basic-example)\n- [Contributors](https://github.com/eaigner/hood/contributors)\n\n## Overview\n\nHood is a database agnostic ORM for Go developed by [@eaignr](https://twitter.com/eaignr). It was written with following points in mind:\n\n- Chainable API\n- Transaction support\n- Migration and schema generation\n- Model validations\n- Model event hooks\n- Database dialect interface\n- No implicit fields\n- Clean and testable codebase\n\nDialects currently implemented\n\n- **Postgres** using [github.com/lib/pq](https://github.com/lib/pq)\n- **MySQL** using [github.com/ziutek/mymysql](https://github.com/ziutek/mymysql) (by [coocood](https://github.com/coocood))\n\nAdding a dialect is simple. Just create a new file named `\u003cdialect_name\u003e.go` and the corresponding struct type, and mixin the `Base` dialect. Then implement the methods that are specific to the new dialect (for an example see [`postgres.go`](https://github.com/eaigner/hood/blob/master/postgres.go)).\n\n## Documentation\n\nYou can find the documentation over at [GoDoc](http://godoc.org/github.com/eaigner/hood).\n**To get a sense of the API, it's best to take a quick look at the [unit tests](https://github.com/eaigner/hood/blob/master/dialects_test.go), as they are always up to date!**\n\n## Opening a Database\n\nIf the dialect is registered, you can open the database directly using\n\n    hd, err := hood.Open(\"postgres\", \"user=\u003cusername\u003e dbname=\u003cdatabase\u003e\")\n    \nor you can pass an existing database and dialect to `hood.New(*sql.DB, hood.Dialect)`\n\n    hd := hood.New(db, NewPostgres())\n\t\n## Schemas\n\nSchemas can be declared using the following syntax (only for demonstration purposes, would not produce valid SQL since it has 2 primary keys)\n\n```go\ntype Person struct {\n  // Auto-incrementing int field 'id'\n  Id hood.Id\n\n  // Custom primary key field 'first_name', with presence validation\n  FirstName string `sql:\"pk\" validate:\"presence\"`\n\n  // string field 'last_name' with size 128, NOT NULL\n  LastName string `sql:\"size(128),notnull\"`\n\n  // string field 'tag' with size 255, default value 'customer'\n  Tag string `sql:\"size(255),default('customer')\"`\n\n  // You can also combine tags, default value 'orange'\n  CombinedTags string `sql:\"size(128),default('orange')\"`\n  Birthday     time.Time    // timestamp field 'birthday'\n  Data         []byte       // data field 'data'\n  IsAdmin      bool         // boolean field 'is_admin'\n  Notes        string       // text field 'notes'\n\n  // You can alternatively define a var char as a string field by setting a size\n  Nick  string  `sql:\"size(128)\"`\n\n  // Validates number range\n  Balance int `validate:\"range(10:20)\"`\n\n  // These fields are auto updated on save\n  Created hood.Created\n  Updated hood.Updated\n\n  // ... and other built in types (int, uint, float...)\n}\n\n// Indexes are defined via the Indexed interface to avoid\n// polluting the table fields.\n\nfunc (table *Person) Indexes(indexes *hood.Indexes) {\n  indexes.Add(\"tag_index\", \"tag\") // params: indexName, unique, columns...\n  indexes.AddUnique(\"name_index\", \"first_name\", \"last_name\")\n}\n```\n\nSchema creation is completely optional, you can use any other tool you like.\n\nThe following built in field properties are defined (via `sql:` tag):\n\n- `pk` the field is a primary key\n- `notnull` the field must be NOT NULL\n- `size(x)` the field must have the specified size, e.g. for varchar `size(128)`\n- `default(x)` the field has the specified default value, e.g. `default(5)` or `default('orange')`\n- `-` ignores the field\n\n## Migrations\n\nTo use migrations, you first have to install the `hood` tool. To do that run the following:\n\n    go get github.com/eaigner/hood\n    cd $GOPATH/src/github.com/eaigner/hood\n    ./install.sh\n\nAssuming you have your `$GOPATH/bin` directory in your `PATH`, you can now invoke the hood tool with `hood`.\nBefore we can use migrations we have to create a database configuration file first. To do this type\n\n    hood create:config\n\nThis command will create a `db/config.json` file relative to your current directory. It will look something like this:\n\n```javascript\n{\n  \"development\": {\n    \"driver\": \"\",\n    \"source\": \"\"\n  },\n  \"production\": {\n    \"driver\": \"\",\n    \"source\": \"\"\n  },\n  \"test\": {\n    \"driver\": \"\",\n    \"source\": \"\"\n  }\n}\n```\n\nPopulate it with your database credentials. The `driver` and `source` fields are the strings you would pass\nto the `sql.Open(2)` function. Now hood knows about our database, so let's create our first migration with\n\n\thood create:migration CreateUserTable\n\nand another one\n\n\thood create:migration AddUserNameIndex\n\nThis command creates new migrations in `db/migrations`. Next we have to populate the\ngenerated migrations `Up` (and `Down`) methods like so:\n\n```go\nfunc (m *M) CreateUserTable_1357605106_Up(hd *hood.Hood) {\n  type Users struct {\n    Id\t\thood.Id\n    First\tstring\n    Last\tstring\n  }\n  hd.CreateTable(\u0026Users{})\n}\n```\n\n```go\nfunc (m *M) AddUserNameIndex_1357605116_Up(hd *hood.Hood) {\n  hd.CreateIndex(\"users\", \"name_index\", true, \"first\", \"last\")\n}\n```\n\nThe passed in `hood` instance is a transaction that will be committed after the method.\n\nNow we can run migrations with\n\n\thood db:migrate\n\nand roll back with\n\n\thood db:rollback\n\nFor a complete list of commands invoke `hood -help`\n\nA `schema.go` file is **automatically generated** in the `db` directory. This file reflects the\ncurrent state of the database! In our example, it will look like this:\n\n```go\npackage db\n\nimport (\n  \"github.com/eaigner/hood\"\n)\n\ntype Users struct {\n  Id    hood.Id\n  First string\n  Last  string\n}\n\nfunc (table *Users) Indexes(indexes *hood.Indexes) {\n  indexes.AddUnique(\"name_index\", \"first\", \"last\")\n}\n```\n\n## Validation\n\nBesides the `sql:` struct tag, you can specify a `validate:` tag for model validation:\n\n- `presence` validates that a field is set\n- `len(min:max)` validates that a `string` field’s length lies within the specified range\n\t- `len(min:)` validates that it has the specified min length, \n\t- `len(:max)` or max length\n- `range(min:max)` validates that an `int` value lies in the specific range\n\t- `range(min:)` validates that it has the specified min value,\n\t- `range(:max)` or max value\n- `\u003cregexp\u003e`, e.g. `^[a-z]+$`, validates that a `string` matches the regular expression\n    - the expression must start with `^`\n    - backslash and double quote should be escaped\n    - ***does not work with other validation methods on the same field***\n\nYou can also define multiple validations on one field, e.g. `validate:\"len(:12),presence\"`\n\nFor more complex validations you can use custom validation methods. The methods\nare added to the schema and must start with `Validate` and return an `error`.\n\nFor example:\n\n```go\nfunc (u *User) ValidateUsername() error {\n\trx := regexp.MustCompile(`[a-z0-9]+`)\n\tif !rx.MatchString(u.Name) {\n\t\treturn NewValidationError(1, \"username contains invalid characters\")\n\t}\n\treturn nil\n}\n```\n\n## Hooks\n\nYou can add hooks to a model to run on a specific action like so:\n\n```go\nfunc (u *User) BeforeUpdate() error {\n\tu.Updated = time.Now()\n\treturn nil\n}\n```\n\nIf the hook returns an error on a `Before-` action it **is not performed**!\n\nThe following hooks are defined:\n\n- `Before/AfterSave`\n- `Before/AfterInsert`\n- `Before/AfterUpdate`\n- `Before/AfterDelete`\n\n## Basic Example\n\n```go\n\npackage main\n\nimport (\n\t\"hood\"\n)\n\nfunc main() {\n\t// Open a DB connection, use New() alternatively for unregistered dialects\n\thd, err := hood.Open(\"postgres\", \"user=hood dbname=hood_test sslmode=disable\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Create a table\n\ttype Fruit struct {\n\t\tId    hood.Id\n\t\tName  string `validate:\"presence\"`\n\t\tColor string\n\t}\n\n\terr = hd.CreateTable(\u0026Fruit{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfruits := []Fruit{\n\t\tFruit{Name: \"banana\", Color: \"yellow\"},\n\t\tFruit{Name: \"apple\", Color: \"red\"},\n\t\tFruit{Name: \"grapefruit\", Color: \"yellow\"},\n\t\tFruit{Name: \"grape\", Color: \"green\"},\n\t\tFruit{Name: \"pear\", Color: \"yellow\"},\n\t}\n\n\t// Start a transaction\n\ttx := hd.Begin()\n\n\tids, err := tx.SaveAll(\u0026fruits)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(\"inserted ids:\", ids) // [1 2 3 4 5]\n\n\t// Commit changes\n\terr = tx.Commit()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Ids are automatically updated\n\tif fruits[0].Id != 1 || fruits[1].Id != 2 || fruits[2].Id != 3 {\n\t\tpanic(\"id not set\")\n\t}\n\n\t// If an id is already set, a call to save will result in an update\n\tfruits[0].Color = \"green\"\n\n\tids, err = hd.SaveAll(\u0026fruits)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(\"updated ids:\", ids) // [1 2 3 4 5]\n\n\tif fruits[0].Id != 1 || fruits[1].Id != 2 || fruits[2].Id != 3 {\n\t\tpanic(\"id not set\")\n\t}\n\n\t// Let's try to save a row that does not satisfy the required validations\n\t_, err = hd.Save(\u0026Fruit{})\n\tif err == nil || err.Error() != \"value not set\" {\n\t\tpanic(\"does not satisfy validations, should not save\")\n\t}\n\n\t// Find\n\t//\n\t// The markers are db agnostic, so you can always use '?'\n\t// e.g. in Postgres they are replaced with $1, $2, ...\n\tvar results []Fruit\n\terr = hd.Where(\"color\", \"=\", \"green\").OrderBy(\"name\").Limit(1).Find(\u0026results)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(\"results:\", results) // [{1 banana green}]\n\n\t// Delete\n\tids, err = hd.DeleteAll(\u0026results)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(\"deleted ids:\", ids) // [1]\n\n\tresults = nil\n\terr = hd.Find(\u0026results)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(\"results:\", results) // [{2 apple red} {3 grapefruit yellow} {4 grape green} {5 pear yellow}]\n\n\t// Drop\n\thd.DropTable(\u0026Fruit{})\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feaigner%2Fhood","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feaigner%2Fhood","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feaigner%2Fhood/lists"}