{"id":13424472,"url":"https://github.com/anykeyh/clear","last_synced_at":"2025-04-05T16:09:18.804Z","repository":{"id":43752900,"uuid":"107003686","full_name":"anykeyh/clear","owner":"anykeyh","description":"Advanced ORM between postgreSQL and Crystal","archived":false,"fork":false,"pushed_at":"2024-08-24T10:10:27.000Z","size":2915,"stargazers_count":274,"open_issues_count":45,"forks_count":32,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-03-29T15:07:48.424Z","etag":null,"topics":["crystal","crystal-language","database","orm","postgres","sql"],"latest_commit_sha":null,"homepage":"https://github.com/anykeyh/clear","language":"Crystal","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/anykeyh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-15T10:47:24.000Z","updated_at":"2025-03-15T04:58:23.000Z","dependencies_parsed_at":"2024-10-24T10:51:27.467Z","dependency_job_id":"17bb74f8-f4fe-4f71-8766-b5ea59bc63e1","html_url":"https://github.com/anykeyh/clear","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anykeyh%2Fclear","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anykeyh%2Fclear/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anykeyh%2Fclear/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anykeyh%2Fclear/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anykeyh","download_url":"https://codeload.github.com/anykeyh/clear/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247361691,"owners_count":20926643,"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":["crystal","crystal-language","database","orm","postgres","sql"],"created_at":"2024-07-31T00:00:54.818Z","updated_at":"2025-04-05T16:09:18.781Z","avatar_url":"https://github.com/anykeyh.png","language":"Crystal","readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"design/logo1.png\" alt=\"clear\" height=\"200px\"\u003e\u003c/p\u003e\n\n# Clear\n[![Build Status](https://travis-ci.org/anykeyh/clear.svg?branch=master)](https://travis-ci.org/anykeyh/clear) [![Docs](https://img.shields.io/badge/docs-available-brightgreen.svg)](https://anykeyh.github.io/clear/) [![GitHub release](https://img.shields.io/github/release/anykeyh/clear.svg)](https://github.com/anykeyh/clear/releases)\n\u003c!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --\u003e\n[![All Contributors](https://img.shields.io/badge/all_contributors-20-orange.svg?style=flat-square)](#contributors-)\n\u003c!-- ALL-CONTRIBUTORS-BADGE:END --\u003e\n\nClear is an ORM built specifically for PostgreSQL in Crystal.\n\nIt's probably the most advanced ORM for PG on Crystal in term of features offered.\nIt features Active Record pattern models, and a low-level SQL builder.\n\nYou can deal out of the box with jsonb, tsvectors, cursors, CTE, bcrypt password,\narray, uuid primary key, foreign constraints... and other things !\nIt also has a powerful DSL to construct `where` and `having` clauses.\n\nThe philosophy beneath is to please me (and you !) with emphasis made on business\ncode readability and minimal setup.\n\nThe project is quite active and well maintened, too !\n\n## Resources\n\n- [Online Manual and Getting Started](https://clear.gitbook.io/project/)\n- [Auto-generated API Documentation](https://anykeyh.github.io/clear/)\n- [Changelog](https://github.com/anykeyh/clear/blob/master/CHANGELOG.md)\n\n## Why to use Clear ?\n\nIn few seconds, you want to use Clear if:\n\n- [X] You want an expressive ORM. Put straight your thought to your code !\n- [X] You'd like to use advanced Postgres features without hassle\n- [X] You are aware of the pros and cons of the Active Records pattern\n\nYou don't want to use Clear if:\n\n- [ ] You're not willing to use PostgreSQL\n- [ ] You're looking for a minimalist ORM / Data Mapper\n- [ ] You need something which doesn't evolve, with breaking changes.\n      Clear is still in alpha but starting to mature !\n\n\n## Features\n\n- Active Record pattern based ORM\n- Expressiveness as mantra - even with advanced features like jsonb, regexp... -\n```crystal\n  # Like ...\n  Product.query.where{ ( type == \"Book\" ) \u0026 ( metadata.jsonb(\"author.full_name\") == \"Philip K. Dick\" ) }\n  # ^--- will use @\u003e operator, to relay on your gin index. For real.\n\n  Product.query.where{ ( products.type == \"Book\" ) \u0026 ( products.metadata.jsonb(\"author.full_name\") != \"Philip K. Dick\" ) }\n  # ^--- this time will use -\u003e notation, because no optimizations possible :/\n\n  # Or...\n  User.query.where{ created_at.in? 5.days.ago .. 1.day.ago }\n\n  # Or even...\n  ORM.query.where{ ( description =~ /(^| )awesome($| )/i ) }.first!.name # Clear! :-)\n```\n- Proper debug information\n  - Log and colorize query. Show you the last query if your code crash !\n  - If failing on compile for a good reason, give proper explaination (or at least try)\n- Migration system\n- Validation system\n- N+1 query avoidance strategy\n- Transaction, rollback \u0026 savepoint\n- Access to CTE, locks, cursors, scope, pagination, join, window, multi-connection and many others features\n- Model lifecycle/hooks\n- JSONB, UUID, FullTextSearch\n\n### Installation\n\nIn `shards.yml`\n\n```yml\ndependencies:\n  clear:\n    github: anykeyh/clear\n    branch: master\n```\n\nThen:\n\n```crystal\n  require \"clear\"\n```\n\n### Model definition\n\nClear offers some mixins, just include them in your classes to *clear* them:\n\n#### Column mapping\n\n```crystal\n\nclass User\n  include Clear::Model\n\n  column id : Int64, primary: true\n\n  column email : String\n\n  column first_name : String?\n  column last_name : String?\n\n  column encrypted_password : Crypto::Bcrypt::Password\n\n  def password=(x)\n    self.encrypted_password = Crypto::Bcrypt::Password.create(password)\n  end\nend\n\n```\n\n#### Column types\n\n- `Number`, `String`, `Time`, `Boolean` and `Jsonb` structures are already mapped.\n- `Numeric` ([arbitrary precision number](https://www.postgresql.org/docs/9.6/datatype-numeric.html)) is also supported\n- `Array` of primitives too.\nFor other type of data, just create your own converter !\n\n```crystal\nclass Clear::Model::Converter::MyClassConversion\n  def self.to_column(x) : MyClass?\n    case x\n    when String\n      MyClass.from_string(x)\n    when Slice(UInt8)\n      MyClass.from_slice(x)\n    else\n      raise \"Cannot convert from #{x.class} to MyClass\"\n    end\n  end\n\n  def self.to_db(x : UUID?)\n    x.to_s\n  end\nend\n\nClear::Model::Converter.add_converter(\"MyClass\", Clear::Model::Converter::MyClassConversion)\n```\n\n##### Column presence\n\nMost of the ORM for Crystal are mapping column type as `Type | Nil` union.\nIt makes sense so we allow selection of some columns only of a model.\nHowever, this have a caveats: columns are still accessible, and will return nil,\neven if the real value of the column is not null!\n\nMoreover, most of the developers will enforce nullity only on their programming\nlanguage level via validation, but not on the database, leading to inconsistency.\n\nTherefore, we choose to throw exception whenever a column is accessed before\nit has been initialized and to enforce presence through the union system of\nCrystal.\n\nClear offers this through the use of column wrapper.\nWrapper can be of the type of the column as in postgres, or in `UNKNOWN` state.\nThis approach offers more flexibility:\n\n```crystal\nUser.query.select(\"last_name\").each do |usr|\n  puts usr.first_name #Will raise an exception, as first_name hasn't been fetched.\nend\n\nu = User.new\nu.first_name_column.defined? #Return false\nu.first_name_column.value(\"\") # Call the value or empty string if not defined :-)\nu.first_name = \"bonjour\"\nu.first_name_column.defined? #Return true now !\n```\n\nWrapper give also some pretty useful features:\n\n```crystal\nu = User.new\nu.email = \"me@myaddress.com\"\nu.email_column.changed? # TRUE\nu.email_column.revert\nu.email_column.defined? # No more\n```\n\n#### Associations\n\nClear offers `has_many`, `has_one`, `belongs_to` and `has_many through` associations:\n\n```crystal\nclass Security::Action\n  belongs_to role : Role\nend\n\nclass Security::Role\n  has_many user : User\nend\n\nclass User\n  include Clear::Model\n\n  has_one user_info : UserInfo\n  has_many posts : Post\n\n  belongs_to role : Security::Role\n\n  # Use of the standard keys (users_id \u003c=\u003e security_role_id)\n  has_many actions : Security::Action, through: Security::Role\nend\n```\n\n### Querying\n\nClear offers a collection system for your models. The collection system\ntakes origin to the lower API `Clear::SQL`, used to build requests.\n\n#### Simple query\n\n##### Fetch a model\n\nTo fetch one model:\n\n```crystal\n# 1. Get the first user\nUser.query.first #Get the first user, ordered by primary key\n\n# Get a specific user\nUser.find!(1) #Get the first user, or throw exception if not found.\n\n# Usage of query provides a `find_by` kind of method:\nu : User? = User.query.find{ email =~ /yacine/i }\n```\n\n##### Fetch multiple models\n\nTo prepare a collection, juste use `Model#query`.\nCollections include `SQL::Select` object, so all the low level API\n(`where`, `join`, `group_by`, `lock`...) can be used in this context.\n\n```crystal\n# Get multiple users\nUser.query.where{ (id \u003e= 100) \u0026 (id \u003c= 200) }.each do |user|\n  # Do something with user !\nend\n\n#In case you know there's millions of row, use a cursor to avoid memory issues !\nUser.query.where{ (id \u003e= 1) \u0026 (id \u003c= 20_000_000) }.each_cursor(batch: 100) do |user|\n  # Do something with user; only 100 users will be stored in memory\n  # This method is using pg cursor, so it's 100% transaction-safe\nend\n```\n\n##### Aggregate functions\n\nCall aggregate functions from the query is possible. For complex aggregation,\nI would recommend to use the `SQL::View` API (note: Not yet developed),\nand keep the model query for _fetching_ models only\n\n```crystal\n# count\nuser_on_gmail = User.query.where{ email.ilike \"@gmail.com%\" }.count #Note: count return is Int64\n# min/max\nmax_id = User.query.where{ email.ilike \"@gmail.com%\" }.max(\"id\", Int32)\n# your own aggregate\nweighted_avg = User.query.agg( \"SUM(performance_weight * performance_score) / SUM(performance_weight)\", Float64 )\n```\n\n##### Fetching associations\n\nAssociations are basically getter which create predefined SQL.\nTo access to an association, just call it !\n\n```crystal\nUser.query.each do |user|\n  puts \"User #{user.id} posts:\"\n  user.posts.each do |post| #Works, but will trigger a request for each user.\n    puts \"• #{post.id}\"\n  end\nend\n```\n\n###### Caching association for N+1 request\n\nFor every association, you can tell Clear to encache the results to avoid\nN+1 queries, using `with_XXX` on the collection:\n\n```crystal\n# Will call two requests only.\nUser.query.with_posts.each do |user|\n  puts \"User #{user.id} posts:\"\n  user.posts.each do |post|\n    puts \"• #{post.id}\"\n  end\nend\n```\n\nNote than Clear doesn't perform a join method, and the SQL produced will use\nthe operator `IN` on the association.\n\nIn the case above:\n\n- The first request will be\n\n```\n  SELECT * FROM users;\n```\n\n- Thanks to the cache, a second request will be called before fetching the users:\n\n```\n  SELECT * FROM posts WHERE user_id IN ( SELECT id FROM users )\n```\n\nI have plan in a late future to offer different query strategies for the cache (e.g. joins, unions...)\n\n###### Associations caching examples\n\nWhen you use the caching system of the association, using filters on association will\ninvalidate the cache, and N+1 query will happens.\n\nFor example:\n\n```crystal\nUser.query.with_posts.each do |user|\n  puts \"User #{user.id} published posts:\"\n  # Here: The cache system will not work. The cache on association\n  # is invalidated by the filter `where`.\n  user.posts.where({published: true}).each do |post|\n    puts \"• #{post.id}\"\n  end\nend\n```\n\nThe way to fix it is to filter on the association itself:\n\n```crystal\nUser.query.with_posts(\u0026.where({published: true})).each do |user|\n  puts \"User #{user.id} published posts:\"\n  # The posts collection of user is already encached with the published filter\n  user.posts.each do |post|\n    puts \"• #{post.id}\"\n  end\nend\n```\n\nNote than, of course in this example `user.posts` are not ALL the posts but only the\n`published` posts\n\nThanks to this system, we can stack it to encache long distance relations:\n\n```crystal\n# Will cache users\u003c=\u003eposts \u0026 posts\u003c=\u003ecategory\n# Total: 3 requests !\nUser.query.with_posts(\u0026.with_category).each do |user|\n  #...\nend\n```\n\n##### Querying computed or foreign columns\n\nIn case you want columns computed by postgres, or stored in another table, you can use `fetch_column`.\nBy default, for performance reasons, `fetch_columns` option is set to false.\n\n```crystal\nusers = User.query.select(email: \"users.email\",\n  remark: \"infos.remark\").join(\"infos\"){ infos.user_id == users.id }.to_a(fetch_columns: true)\n\n# Now the column \"remark\" will be fetched into each user object.\n# Access can be made using `[]` operator on the model.\n\nusers.each do |u|\n  puts \"email: `#{u.email}`, remark: `#{u[\"remark\"]?}`\"\nend\n```\n\n### Inspection \u0026 SQL logging\n\n#### Inspection\n\n`inspect` over model offers debugging insights:\n\n```text\n  p # =\u003e #\u003cPost:0x10c5f6720\n          @attributes={},\n          @cache=\n           #\u003cClear::Model::QueryCache:0x10c6e8100\n            @cache={},\n            @cache_activation=Set{}\u003e,\n          @content_column=\n           \"...\",\n          @errors=[],\n          @id_column=38,\n          @persisted=true,\n          @published_column=true,\n          @read_only=false,\n          @title_column=\"Lorem ipsum torquent inceptos\"*,\n          @user_id_column=5\u003e\n```\n\nIn this case, the `*` means a column is changed and the object is dirty and diverge from the database.\n\n#### SQL Logging\n\nOne thing very important for a good ORM is to offer vision of the SQL\ncalled under the hood.\nClear is offering SQL logging tools, with SQL syntax colorizing in your terminal.\n\nFor activation, simply setup the log to `:debug` level\n\n\n```\n::Log.builder.bind \"clear.*\", Log::Severity::Debug, Log::IOBackend.new\n```\n\n### Save \u0026 validation\n\n#### Save\n\nObject can be persisted, saved, updated:\n\n```crystal\nu = User.new\nu.email = \"test@example.com\"\nu.save! #Save or throw if unsavable (validation failed).\n```\n\nColumns can be checked \u0026 reverted:\n\n```crystal\nu = User.new\nu.email = \"test@example.com\"\nu.email_column.changed? # \u003c Return \"true\"\nu.email_column.revert # Return to #undef.\n```\n\n#### Validation\n\n##### Presence validator\n\nPresence validation is done using the type of the column:\n\n```crystal\nclass User\n  include Clear::Model\n\n  column first_name : String # Must be present\n  column last_name : String? # Can be null\nend\n```\n\n###### `NOT NULL DEFAULT ...` CASE\n\n\nThere's a case when a column CAN be null inside Crystal, if not persisted,\nbut CANNOT be null inside Postgres.\n\nFor example in the case of the `id` column, the value is generated during insert !\n\nIn this case, you can write:\n\n```crystal\nclass User\n    column id : Int64, primary: true, presence: false #id will be set using pg serial !\nend\n```\n\nThus, in all case this will fail:\n\n```\nu = User.new\nu.id # raise error\n```\n\n##### Other validators\n\nWhen you save your model, Clear will call first the presence validators, then\ncall your custom made validators. All you have to do is to reimplement\nthe `validate` method:\n\n```crystal\nclass MyModel\n#...\n  def validate\n    # Your code goes here\n  end\nend\n```\n\nValidation fails if `model#errors` is not empty:\n\n```crystal\n  class MyModel\n    #...\n    def validate\n      if first_name_column.defined? \u0026\u0026 first_name != \"ABCD\" #\u003c See below why `defined?` must be called.\n        add_error(\"first_name\", \"must be ABCD!\")\n      end\n    end\n  end\n```\n\n##### Unique validator\n\nPlease use the `unique` constraint feature of postgres. Unique validation outside of the database\ndoes not protect against race conditions between multiple fibers/threads/nodes/pods.\nIt's an anti-pattern and must be avoided at any cost.\n\n##### The validation and the presence system\n\nIf you try to validate a column which has not been initialized,\nClear will complain, telling you you cannot access to the column.\nLet's see an example here:\n\n```crystal\nclass MyModel\n  #...\n  def validate\n    add_error(\"first_name\", \"should not be empty\") if first_name == \"\"\n  end\nend\n\nMyModel.new.save! #\u003c Raise unexpected exception, not validation failure :(\n```\n\nThis validator will raise an exception, because first_name has never been initialized.\nTo avoid this, we have many options:\n```crystal\n# 1. Check presence:\n\ndef validate\n  if first_name_column.defined? #Ensure we have a value here.\n    add_error(\"first_name\", \"should not be empty\") if first_name == \"\"\n  end\nend\n\n# 2. Use column object + default value\ndef validate\n  add_error(\"first_name\", \"should not be empty\") if first_name_column.value(\"\") == \"\"\nend\n\n# 3. Use the helper macro `on_presence`\ndef validate\n  on_presence(first_name) do\n    add_error(\"first_name\", \"should not be empty\") if first_name == \"\"\n  end\nend\n\n#4. Use the helper macro `ensure_than`\ndef validate\n  ensure_than(first_name, \"should not be empty\", \u0026.!=(\"\"))\nend\n\n#5. Use the `ensure_than` helper (but with block notation) !\ndef validate\n  ensure_than(first_name, \"should not be empty\") do |column|\n    column != \"\"\n  end\nend\n\n```\n\nI recommend the 4th method in most scenarios.\nSimple to write and easy to read !\n\n### Migration\n\nClear offers of course a migration system.\n\nMigration should have an number at the end of the class name\nto define the order migrations should be ran in.\nThis number can be wrote at the end of the class itself:\n\n```crystal\nclass Migration1\n  include Clear::Migration\n\n  def change(dir)\n    #...\n  end\nend\n```\n\n#### Using filename\n\nAnother way is to write down all your migrations one file per migration, and\nnaming the file using the `[number]_migration_description.cr` pattern.\nIn this case, the migration class name doesn't need to have a number at the end of the class name.\n\n```crystal\n# in src/db/migrations/1234_create_table.cr\nclass CreateTable\n  include Clear::Migration\n\n  def change(dir)\n    #...\n  end\nend\n```\n\n#### Migration examples\n\nMigrations must implement the method `change(dir : Migration::Direction)`\n\nDirection is the current direction of the migration (up or down).\nIt provides few methods: `up?`, `down?`, `up(\u0026block)`, `down(\u0026block)`\n\nYou can create a table:\n\n```crystal\n  def change(dir)\n    create_table(:test) do |t|\n      t.column :first_name, :string, index: true\n      t.column :last_name, :string, unique: true\n\n      t.index \"lower(first_name || ' ' || last_name)\", using: :btree\n\n      t.timestamps\n    end\n  end\n```\n\n#### Constraints\n\nI strongly encourage use of postgres's foreign key constraints for your references:\n\n```crystal\n  t.references to: \"users\", on_delete: \"cascade\", null: false\n```\n\nThere's no plan to offer on Crystal level the `on_delete` feature, like\n`dependent` in ActiveRecord. That's a standard PG feature, just set it\nup in your migrations.\n\n## Performances\n\nModels add a layer of computation. Below is a sample with a very simple model\n(two integer columns), with fetching of 100k rows over 1M rows database, using --release flag:\n\n\n| Method                     |        | Total time            | Speed        |\n| --------------------------:|-------:|-----------------------|-------------:|\n|          Simple load 100k  |  12.04 |  ( 83.03ms) (± 3.87%) | 2.28× slower |\n|               With cursor  |   8.26 |  ( 121.0ms) (± 1.25%) | 3.32× slower |\n|           With attributes  |  10.30 |  ( 97.12ms) (± 4.07%) | 2.67× slower |\n| With attributes and cursor |   7.55 |  (132.52ms) (± 2.39%) | 3.64× slower |\n|                  SQL only  |  27.46 |  ( 36.42ms) (± 5.05%) |      fastest |\n\n\n- `Simple load 100k` is using an array to fetch the 100k rows.\n- `With cursor` is querying 1000 rows at a time\n- `With attribute` setup a hash to deal with unknown attributes in the model (e.g. aggregates)\n- `With attribute and cursor` is doing cursored fetch with hash attributes created\n- `SQL only` build and execute SQL using SQL::Builder\n\nAs you can see, it takes around 100ms to fetch 100k rows for this simple model (SQL included).\nIf for more complex model, it would take a bit more of time, I think the performances\nare quite reasonable, and tenfold or plus faster than Rails's ActiveRecord.\n\n## Licensing\n\nThis shard is provided under the MIT license.\n\n## Contribution\n\nAll contributions are welcome! As a specialized ORM for PostgreSQL,\nbe sure a great contribution on a very specific PG feature will be incorporated\nto this shard.\nI hope one day we will cover all the features of PG here !\n\n### Running Tests\n\nIn order to run the test suite, you will need to have the PostgresSQL service locally available via a socket for access with psql. psql will attempt to use the 'postgres' user to create the test database. If you are working with a newly installed database that may not have the postgres user, this can be created with `createuser -s postgres`.\n\n## Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"http://blog.bashme.org\"\u003e\u003cimg src=\"https://avatars3.githubusercontent.com/u/684?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eRuss Smith\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=russ\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"http://www.amberframework.org \"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/1685772?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eElias Perez\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=eliasjpr\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://namechk.com\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/2391?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eJeremy Woertink\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=jwoertink\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/mamantoha\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/61285?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eAnton Maminov\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=mamantoha\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/remydev\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/10176486?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eremydev\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=remydev\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"http://jack.codes\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/3958636?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eJack Turnbull\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/issues?q=author%3Ajackturnbull\" title=\"Bug reports\"\u003e🐛\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/Blacksmoke16\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/12136995?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eBlacksmoke16\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=Blacksmoke16\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/007lva\"\u003e\u003cimg src=\"https://avatars3.githubusercontent.com/u/1860816?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eluigi\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=007lva\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"http://zauner900.net\"\u003e\u003cimg src=\"https://avatars0.githubusercontent.com/u/30139?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eMatthias Zauner\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/issues?q=author%3Azauner\" title=\"Bug reports\"\u003e🐛\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://westonganger.com\"\u003e\u003cimg src=\"https://avatars3.githubusercontent.com/u/3414795?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eWeston Ganger\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=westonganger\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/pynixwang\"\u003e\u003cimg src=\"https://avatars0.githubusercontent.com/u/1189879?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003ePynix Wang\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=pynixwang\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/Vici37\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/1459505?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eVici37\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=Vici37\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"http://karoly.io\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/1334622?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eNiklas Karoly\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=nik736\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/Acciaiodigitale\"\u003e\u003cimg src=\"https://avatars0.githubusercontent.com/u/26814295?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eMassimiliano Bertinetti\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=Acciaiodigitale\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/batarian71\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/36008612?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003ebatarian71\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#design-batarian71\" title=\"Design\"\u003e🎨\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/anykeyh\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/2456898?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eYacine Petitprez\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#maintenance-anykeyh\" title=\"Maintenance\"\u003e🚧\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/alex-min\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/1898825?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eAlexandre\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=alex-min\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://duke-nguyen.netlify.app\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/58082199?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eAnh (Duke) Nguyen\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=dukeraphaelng\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://yujiri.xyz\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/16864184?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eRyan Westlund\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=yujiri8\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/caspiano\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/18466497?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eCaspian Baska\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/anykeyh/clear/commits?author=caspiano\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-enable --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","funding_links":[],"categories":["Crystal","ORM/ODM Extensions"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanykeyh%2Fclear","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanykeyh%2Fclear","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanykeyh%2Fclear/lists"}