{"id":18646768,"url":"https://github.com/metabase/toucan","last_synced_at":"2025-05-15T10:02:13.039Z","repository":{"id":39636902,"uuid":"80256233","full_name":"metabase/toucan","owner":"metabase","description":"A classy high-level Clojure library for defining application models and retrieving them from a DB","archived":false,"fork":false,"pushed_at":"2023-10-04T22:44:49.000Z","size":687,"stargazers_count":572,"open_issues_count":23,"forks_count":49,"subscribers_count":55,"default_branch":"master","last_synced_at":"2025-04-14T16:53:42.586Z","etag":null,"topics":["clojure","flexible","honeysql","hydration","metabase","orm","sql","toucan"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/metabase.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-01-27T23:58:45.000Z","updated_at":"2025-04-13T06:23:43.000Z","dependencies_parsed_at":"2023-10-05T05:36:19.005Z","dependency_job_id":null,"html_url":"https://github.com/metabase/toucan","commit_stats":{"total_commits":152,"total_committers":18,"mean_commits":8.444444444444445,"dds":0.4407894736842105,"last_synced_commit":"9035024fd8e693234b745f10ea3d650e12f72bab"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metabase%2Ftoucan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metabase%2Ftoucan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metabase%2Ftoucan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metabase%2Ftoucan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/metabase","download_url":"https://codeload.github.com/metabase/toucan/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254319717,"owners_count":22051072,"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":["clojure","flexible","honeysql","hydration","metabase","orm","sql","toucan"],"created_at":"2024-11-07T06:22:29.694Z","updated_at":"2025-05-15T10:02:12.182Z","avatar_url":"https://github.com/metabase.png","language":"Clojure","readme":"[![Downloads](https://versions.deps.co/metabase/toucan/downloads.svg)](https://versions.deps.co/metabase/toucan)\n[![Dependencies Status](https://versions.deps.co/metabase/toucan/status.svg)](https://versions.deps.co/metabase/toucan)\n[![Circle CI](https://circleci.com/gh/metabase/toucan.svg?style=svg)](https://circleci.com/gh/metabase/toucan)\n[![License](https://img.shields.io/badge/license-Eclipse%20Public%20License-blue.svg)](https://raw.githubusercontent.com/metabase/toucan/master/LICENSE.txt)\n[![cljdoc badge](https://cljdoc.org/badge/toucan/toucan)](https://cljdoc.org/d/toucan/toucan/CURRENT)\n\n[![Clojars Project](https://clojars.org/toucan/latest-version.svg)](http://clojars.org/toucan)\n\n\u003e [!WARNING]\n\u003e This version of Toucan is deprecated. Please use [Toucan 2](https://github.com/camsaul/toucan2) instead; it's a\n\u003e complete rewrite with many improvements over Toucan 1. If you're already using Toucan 1, check out the\n\u003e [toucan2-toucan1](https://github.com/camsaul/toucan2#toucan2-toucan1) migration helper.\n\n# Toucan\n\n![Toucan](https://github.com/metabase/toucan/blob/master/assets/toucan-logo.png)\n\n## Overview\n\n\u003e There are no SQL/Relational DB ORMs for Clojure for obvious reasons. -- Andrew Brehaut\n\nToucan provides the better parts of an ORM for Clojure, like simple DB queries, flexible custom behavior\nwhen inserting or retrieving objects, and easy *hydration* of related objects, all in a powerful and classy way.\n\nToucan builds on top of [clojure.java.jdbc](https://github.com/clojure/java.jdbc) and the excellent\n[HoneySQL](https://github.com/jkk/honeysql). The code that inspired this library was originally written to bring some of the\nsorely missed conveniences of [Korma](https://github.com/korma/Korma) to HoneySQL when we transitioned from the former to the latter\nat [Metabase](http://metabase.com). Over the last few years, I've continued to build upon and refine the interface of Toucan,\nmaking it simpler, more powerful, and more flexible, all while maintaining the function-based approach of HoneySQL.\n\nView the complete documentation [here](docs/table-of-contents.md), or continue below for a brief tour.\n\n### Simple Queries:\n\nToucan greatly simplifies the most common queries without limiting your ability to express more complicated ones. Even something\nrelatively simple can take quite a lot of code to accomplish in HoneySQL:\n\n\n#### clojure.java.jdbc + HoneySQL\n\n```clojure\n;; select the :name of the User with ID 100\n(-\u003e (jdbc/query my-db-details\n       (sql/format\n         {:select [:name]\n          :from   [:user]\n          :where  [:= :id 100]\n          :limit  1}\n         :quoting :ansi))\n    first\n    :name)\n```\n\n#### Toucan\n\n```clojure\n;; select the :name of the User with ID 100\n(db/select-one-field :name User :id 100)\n```\n\nToucan keeps the simple things simple. Read more about Toucan's database functions [here](docs/db-functions.md).\n\n### Flexible custom behavior when inserting and retrieving objects:\n\nToucan makes it easy to define custom behavior for inserting, retrieving, updating, and deleting objects on a model-by-model basis.\nFor example, suppose you want to convert the `:status` of a User to a keyword whenever it comes out of the database, and back into\na string when it goes back in:\n\n#### clojure.java.jdbc + HoneySQL\n\n```clojure\n;; insert new-user into the DB, converting the value of :status to a String first\n(let [new-user {:name \"Cam\", :status :new}]\n  (jdbc/insert! my-db-details :user (update new-user :status name)))\n\n;; fetch a user, converting :status to a Keyword\n(-\u003e (jdbc/query my-db-details\n      (sql/format\n        {:select [:*]\n         :from   [:user]\n         :where  [:= :id 200]}))\n    first\n    (update :status keyword))\n```\n\n#### Toucan\n\nWith Toucan, you just need to define the model, and tell that you want `:status` automatically converted:\n\n```clojure\n;; define the User model\n(defmodel User :user\n  IModel\n  (types [this] ;; tell Toucan to automatically do Keyword \u003c-\u003e String conversion for :status\n    {:status :keyword}))\n```\n\nAfter that, whenever you fetch, insert, or update a User, `:status` will automatically be converted appropriately:\n\n```clojure\n;; Insert a new User\n(db/insert! User :name \"Cam\", :status :new) ; :status gets stored in the DB as \"new\"\n\n;; Fetch User 200\n(User 200) ; :status is converted to a keyword when User is fetched\n```\n\nRead more about defining and customizing the behavior of models [here](docs/defining-models.md).\n\n### Easy hydration of related objects\n\nIf you're developing something like a REST API, there's a good chance at some point you'll want to *hydrate* some related objects\nand return them in your response. For example, suppose we wanted to return a Venue with its Category hydrated:\n\n```clojure\n;; No hydration\n{:name        \"The Tempest\"\n :category_id 120\n ...}\n\n;; w/ hydrated :category\n{:name        \"The Tempest\"\n :category_id 120\n :category    {:name \"Dive Bar\"\n               ...}\n ...}\n```\n\nThe code to do something like this is enormously hairy in vanilla JDBC + HoneySQL. Luckily, Toucan makes this kind of thing a piece\nof cake:\n\n```clojure\n(hydrate (Venue 120) :category)\n```\n\nToucan is clever enough to automatically figure out that Venue's `:category_id` corresponds to the `:id` of a Category, and constructs efficient queries\nto fetch it. Toucan can hydrate single objects, sequences of objects, and even objects inside hydrated objects, all in an efficient way that minimizes\nthe number of DB calls. You can also define custom functions to hydrate different keys, and add additional mappings for automatic hydration. Read more\nabout hydration [here](docs/hydration.md).\n\n\n## Getting Started\n\nTo get started with Toucan, all you need to do is tell it how to connect to your DB by calling `set-default-db-connection!`:\n\n```clojure\n(require '[toucan.db :as db])\n\n(db/set-default-db-connection!\n  {:classname   \"org.postgresql.Driver\"\n   :subprotocol \"postgresql\"\n   :subname     \"//localhost:5432/my_db\"\n   :user        \"cam\"})\n```\n\nPass it the same connection details that you'd pass to any `clojure.java.jdbc` function; it can be a simple connection details\nmap like the example above or some sort of connection pool. This function only needs to be called once, and can done in your app's normal\nentry point (such as `-main`) or just as a top-level function call outside any other function in a namespace that will get loaded at launch.\n\nRead more about setting up the DB and configuring options such as identifier quoting style [here](docs/setup.md).\n\n\n## Test Utilities\n\nToucan provides several utility macros that making writing tests easy. For example, you can easily create temporary objects so your tests don't affect the state of your test DB:\n\n```clojure\n(require '[toucan.util.test :as tt])\n\n;; create a temporary Venue with the supplied values for use in a test.\n;; the object will be removed from the database afterwards (even if the macro body throws an Exception)\n;; which makes it easy to write tests that don't change the state of the DB\n(expect\n  \"hos_bootleg_tavern\"\n  (tt/with-temp Venue [venue {:name \"Ho's Bootleg Tavern\"}]\n    (venue-slug venue))\n```\n\nRead more about Toucan test utilities [here](docs/test-utils.md).\n\n\n## Annotated Source Code\n\n[View the annotated source code here](https://rawgit.com/metabase/toucan/master/docs/uberdoc.html),\ngenerated with [Marginalia](https://github.com/gdeer81/marginalia).\n\n\n## Contributing\n\nPull requests for bugfixes, improvements, more documentaton, and other enhancements are always welcome.\nToucan code is written to the strict standards of the [Metabase Clojure Style Guide](https://github.com/metabase/metabase/wiki/Metabase-Clojure-Style-Guide),\nso take a moment to familiarize yourself with the style guidelines before submitting a PR.\n\nIf you're interested in contributing, be sure to check out [issues tagged \"help wanted\"](https://github.com/metabase/toucan/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22).\nThere's lots of ways Toucan can be improved and we need people like you to make it even better.\n\n\n### Tests \u0026 Linting\n\nBefore submitting a PR, you should also make sure tests and the linters pass. You can run tests and linters as follows:\n\n```bash\nlein test \u0026\u0026 lein lint\n```\n\nTests assume you have Postgres running locally and have a test DB set up with read/write permissions. Toucan will populate this database with appropriate test data.\n\nIf you don't have Postgres running locally, you can instead the provided shell script to run Postgres via docker. Use `lein start-db` to start the database and `lein stop-db` to stop it.\nNote the script is a Bash script and won't work on Windows unless you're using the Windows Subsystem for Linux.\n\nTo configure access to this database, set the following env vars as needed:\n\n| Env Var | Default Value | Notes |\n| --- | --- | --- |\n| `TOUCAN_TEST_DB_HOST` | `localhost` | |\n| `TOUCAN_TEST_DB_PORT` | `5432` | |\n| `TOUCAN_TEST_DB_NAME` | `toucan_test` | |\n| `TOUCAN_TEST_DB_USER` | | *Optional when running Postgres locally* |\n| `TOUCAN_TEST_DB_PASS` | | *Optional when running Postgres locally* |\n\nThese tests and linters also run on [CircleCI](https://circleci.com/) for all commits and PRs.\n\nWe try to keep Toucan well-tested, so new features should include new tests for them; bugfixes should include failing tests.\nMake sure to properly document new features as well! :yum:\n\nA few more things: please carefully review your own changes and revert any superfluous ones. (A good example would be moving\nwords in the Markdown documentation to different lines in a way that wouldn't change how the rendered\npage itself would appear. These sorts of changes make a PR bigger than it needs to be, and, thus, harder\nto review.) And please include a detailed explanation of what changes you're making and why you've made them. This will help us understand what's going on while we review it. Thanks! :heart_eyes_cat:\n\n### YourKit\n\n![YourKit](https://www.yourkit.com/images/yklogo.png)\n\nYourKit has kindly given us an open source license for their profiler, helping us profile and improve Toucan performance.\n\nYourKit supports open source projects with innovative and intelligent tools\nfor monitoring and profiling Java and .NET applications.\nYourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/),\n[YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/),\nand [YourKit YouMonitor](https://www.yourkit.com/youmonitor/).\n\n### License\n\nCode and documentation copyright © 2018-2019 Metabase, Inc. Artwork copyright © 2018-2019 Cam Saul.\n\nDistributed under the [Eclipse Public License](https://raw.githubusercontent.com/metabase/toucan/master/LICENSE.txt), same as Clojure.\n","funding_links":[],"categories":["ORM and SQL generation"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetabase%2Ftoucan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmetabase%2Ftoucan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetabase%2Ftoucan/lists"}