├── .credo.exs ├── .formatter.exs ├── .gitignore ├── .scripts └── inch_report.sh ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config ├── config.exs ├── dev.exs └── test.exs ├── lib └── ecto_nanoid.ex ├── mix.exs ├── mix.lock ├── priv └── test_repo │ └── migrations │ └── 20180612113352_create_posts_table_with_nanoid.exs └── test ├── ecto_nanoid_test.exs ├── support ├── post_with_nanoid.ex └── test_repo.ex └── test_helper.exs /.credo.exs: -------------------------------------------------------------------------------- 1 | # This file contains the configuration for Credo and you are probably reading 2 | # this after creating it with `mix credo.gen.config`. 3 | # 4 | # If you find anything wrong or unclear in this file, please report an 5 | # issue on GitHub: https://github.com/rrrene/credo/issues 6 | # 7 | %{ 8 | # 9 | # You can have as many configs as you like in the `configs:` field. 10 | configs: [ 11 | %{ 12 | # 13 | # Run any exec using `mix credo -C `. If no exec name is given 14 | # "default" is used. 15 | # 16 | name: "default", 17 | # 18 | # These are the files included in the analysis: 19 | files: %{ 20 | # 21 | # You can give explicit globs or simply directories. 22 | # In the latter case `**/*.{ex,exs}` will be used. 23 | # 24 | included: ["lib/", "src/", "test/", "web/", "apps/"], 25 | excluded: [~r"/_build/", ~r"/deps/"] 26 | }, 27 | # 28 | # If you create your own checks, you must specify the source files for 29 | # them here, so they can be loaded by Credo before running the analysis. 30 | # 31 | requires: [], 32 | # 33 | # If you want to enforce a style guide and need a more traditional linting 34 | # experience, you can change `strict` to `true` below: 35 | # 36 | strict: false, 37 | # 38 | # If you want to use uncolored output by default, you can change `color` 39 | # to `false` below: 40 | # 41 | color: true, 42 | # 43 | # You can customize the parameters of any check by adding a second element 44 | # to the tuple. 45 | # 46 | # To disable a check put `false` as second element: 47 | # 48 | # {Credo.Check.Design.DuplicatedCode, false} 49 | # 50 | checks: [ 51 | # 52 | ## Consistency Checks 53 | # 54 | {Credo.Check.Consistency.ExceptionNames}, 55 | {Credo.Check.Consistency.LineEndings}, 56 | {Credo.Check.Consistency.ParameterPatternMatching}, 57 | {Credo.Check.Consistency.SpaceAroundOperators}, 58 | {Credo.Check.Consistency.SpaceInParentheses}, 59 | {Credo.Check.Consistency.TabsOrSpaces}, 60 | 61 | # 62 | ## Design Checks 63 | # 64 | # You can customize the priority of any check 65 | # Priority values are: `low, normal, high, higher` 66 | # 67 | {Credo.Check.Design.AliasUsage, priority: :low}, 68 | # For some checks, you can also set other parameters 69 | # 70 | # If you don't want the `setup` and `test` macro calls in ExUnit tests 71 | # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just 72 | # set the `excluded_macros` parameter to `[:schema, :setup, :test]`. 73 | # 74 | {Credo.Check.Design.DuplicatedCode, excluded_macros: []}, 75 | # You can also customize the exit_status of each check. 76 | # If you don't want TODO comments to cause `mix credo` to fail, just 77 | # set this value to 0 (zero). 78 | # 79 | {Credo.Check.Design.TagTODO, exit_status: 2}, 80 | {Credo.Check.Design.TagFIXME}, 81 | 82 | # 83 | ## Readability Checks 84 | # 85 | {Credo.Check.Readability.AliasOrder}, 86 | {Credo.Check.Readability.FunctionNames}, 87 | {Credo.Check.Readability.LargeNumbers}, 88 | {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 80}, 89 | {Credo.Check.Readability.ModuleAttributeNames}, 90 | {Credo.Check.Readability.ModuleDoc}, 91 | {Credo.Check.Readability.ModuleNames}, 92 | {Credo.Check.Readability.ParenthesesOnZeroArityDefs}, 93 | {Credo.Check.Readability.ParenthesesInCondition}, 94 | {Credo.Check.Readability.PredicateFunctionNames}, 95 | {Credo.Check.Readability.PreferImplicitTry}, 96 | {Credo.Check.Readability.RedundantBlankLines}, 97 | {Credo.Check.Readability.StringSigils}, 98 | {Credo.Check.Readability.TrailingBlankLine}, 99 | {Credo.Check.Readability.TrailingWhiteSpace}, 100 | {Credo.Check.Readability.VariableNames}, 101 | {Credo.Check.Readability.Semicolons}, 102 | {Credo.Check.Readability.SpaceAfterCommas}, 103 | 104 | # 105 | ## Refactoring Opportunities 106 | # 107 | {Credo.Check.Refactor.DoubleBooleanNegation}, 108 | {Credo.Check.Refactor.CondStatements}, 109 | {Credo.Check.Refactor.CyclomaticComplexity}, 110 | {Credo.Check.Refactor.FunctionArity}, 111 | {Credo.Check.Refactor.LongQuoteBlocks}, 112 | {Credo.Check.Refactor.MatchInCondition}, 113 | {Credo.Check.Refactor.NegatedConditionsInUnless}, 114 | {Credo.Check.Refactor.NegatedConditionsWithElse}, 115 | {Credo.Check.Refactor.Nesting}, 116 | {Credo.Check.Refactor.PipeChainStart, 117 | excluded_argument_types: [:atom, :binary, :fn, :keyword], excluded_functions: []}, 118 | {Credo.Check.Refactor.UnlessWithElse}, 119 | 120 | # 121 | ## Warnings 122 | # 123 | {Credo.Check.Warning.BoolOperationOnSameValues}, 124 | {Credo.Check.Warning.ExpensiveEmptyEnumCheck}, 125 | {Credo.Check.Warning.IExPry}, 126 | {Credo.Check.Warning.IoInspect}, 127 | {Credo.Check.Warning.LazyLogging}, 128 | {Credo.Check.Warning.OperationOnSameValues}, 129 | {Credo.Check.Warning.OperationWithConstantResult}, 130 | {Credo.Check.Warning.UnusedEnumOperation}, 131 | {Credo.Check.Warning.UnusedFileOperation}, 132 | {Credo.Check.Warning.UnusedKeywordOperation}, 133 | {Credo.Check.Warning.UnusedListOperation}, 134 | {Credo.Check.Warning.UnusedPathOperation}, 135 | {Credo.Check.Warning.UnusedRegexOperation}, 136 | {Credo.Check.Warning.UnusedStringOperation}, 137 | {Credo.Check.Warning.UnusedTupleOperation}, 138 | {Credo.Check.Warning.RaiseInsideRescue}, 139 | 140 | # 141 | # Controversial and experimental checks (opt-in, just remove `, false`) 142 | # 143 | {Credo.Check.Refactor.ABCSize, false}, 144 | {Credo.Check.Refactor.AppendSingleItem, false}, 145 | {Credo.Check.Refactor.VariableRebinding, false}, 146 | {Credo.Check.Warning.MapGetUnsafePass, false}, 147 | {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, 148 | 149 | # 150 | # Deprecated checks (these will be deleted after a grace period) 151 | # 152 | {Credo.Check.Readability.Specs, false} 153 | 154 | # 155 | # Custom checks can be created using `mix credo.gen.check`. 156 | # 157 | ] 158 | } 159 | ] 160 | } 161 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["mix.exs", "{config,lib,test,priv}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | ecto_identifier-*.tar 24 | 25 | -------------------------------------------------------------------------------- /.scripts/inch_report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | bold=$(tput bold) 5 | purple='\e[106m' 6 | normal=$(tput sgr0) 7 | allowed_branches="^(master)|(develop)$" 8 | 9 | echo -e "${bold}${purple}" 10 | if [ $TRAVIS_PULL_REQUEST = false ]; then 11 | if [[ $TRAVIS_BRANCH =~ $allowed_branches ]]; then 12 | env MIX_ENV=docs mix deps.get 13 | env MIX_ENV=docs mix inch.report 14 | else 15 | echo "Skipping Inch CI report because this branch does not match on /$allowed_branches/" 16 | fi 17 | else 18 | echo "Skipping Inch CI report because this is a PR build" 19 | fi 20 | echo -e "${normal}" 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | services: 3 | - postgresql 4 | 5 | before_install: 6 | - mix local.hex --force 7 | - mix local.rebar --force 8 | - mix deps.get 9 | script: 10 | - set -e 11 | - MIX_ENV=test mix format --check-formatted 12 | - set +e 13 | - set -e 14 | - mix credo --strict 15 | - set +e 16 | - mix coveralls.json 17 | after_script: 18 | - bash <(curl -s https://codecov.io/bash) 19 | - bash .scripts/inch_report.sh 20 | 21 | matrix: 22 | include: 23 | - elixir: 1.10.4 24 | otp_release: 21.0 25 | 26 | notifications: 27 | email: 28 | on_success: never 29 | recipients: 30 | - ananya95+travis@gmail.com 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # v0.2.0 Upgrade to [Nanoid][nanoid] v2 and support Ecto v3 4 | 5 | **Note!** Default alphabet of nanoid was changed in 6 | [v2.0.0](https://github.com/railsmechanic/nanoid/commit/662f67bab8bf7843f49e985147a36ca7f89a22e0), 7 | the tilde (`~`) was replaced by dash (`-`). Refer upstream 8 | [changelog](https://github.com/railsmechanic/nanoid#introducing-a-new-generator) about the new secure nanoids. 9 | 10 | `ecto_identifier` uses the secure nanoids by default. Nanoid size and alphabet can be configured 11 | [using `config.exs`](https://github.com/oyeb/ecto_identifier#how-should-i-configure-the-nanoids). 12 | 13 | ### Miscellaneous 14 | - Remove `git_hooks` dependency. 15 | 16 | # v0.1.0 BigBang! 17 | 18 | New custom `Ecto.Type` 19 | ---------------------- 20 | 21 | `Ecto.Nanoid`, uses [`nanoid`][nanoid] to do its magic. 22 | 23 | [nanoid]: https://github.com/railsmechanic/nanoid 24 | [livedocs]: https://hexdocs.pm/ecto_identifier/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EctoIdentifier :dart: 2 | 3 | _When UUID is not enough!_ 4 | 5 | [![codecov](https://codecov.io/gh/oyeb/ecto_identifier/branch/master/graph/badge.svg)](https://codecov.io/gh/oyeb/ecto_identifier) 6 | [![Build Status](https://travis-ci.org/oyeb/ecto_identifier.svg?branch=master)](https://travis-ci.org/oyeb/ecto_identifier) 7 | 8 | Provides some custom [`Ecto`][ecto] [type][ecto-type]s to work with identifier 9 | fields that are generated using: 10 | 11 | 1. [`nanoid`][nanoid]s (`Ecto.Nanoid`) 12 | 13 | [ecto]: https://github.com/elixir-ecto/ 14 | [ecto-type]: https://hexdocs.pm/ecto/Ecto.Type.html 15 | [nanoid]: https://github.com/railsmechanic/nanoid 16 | [hashids]: https://github.com/alco/hashids-elixir 17 | 18 | Full docs available on [hexdocs.pm](https://hexdocs.pm/ecto_identifier) 19 | 20 | ## Ecto.Nanoid 21 | 22 | [Nanoid are as safe and sound as UUIDs, but more 23 | compact](https://github.com/ai/nanoid). Check them out [in real 24 | life!](https://alex7kom.github.io/nano-nanoid-cc/) :rainbow: 25 | 26 | The type defines an `autogenerate/0` that can generate a nanoid for you at 27 | "insert-time". You don't have to worry about adding the field to your changeset 28 | `cast`/`validate_required` (just like those auto-generated timestamps)! 29 | 30 | ``` elixir 31 | defmodule Post do 32 | use Ecto.Schema 33 | alias Ecto.Nanoid 34 | 35 | schema "posts" do 36 | field(:number, Ecto.Nanoid, autogenerate: true) 37 | field(:data, :string) 38 | end 39 | end 40 | ``` 41 | And voila! 42 | 43 | ``` elixir 44 | iex> changeset = Post.changeset(%Post{}, %{data: "the answer to everything"}) 45 | iex> changeset.changes.number 46 | nil # coz it's autogenerated! 47 | iex> a_tough_one = Repo.insert!(changeset) 48 | iex> %Post{ 49 | id: 42, 50 | number: "swv-u7bIyewzBFOKuFbbD", # there we have it! 51 | } 52 | ``` 53 | > If you pass in a `:number` in the `params`, a nanoid will not be autogenerated 54 | > for you. For more juicy details see `Ecto.Schema`. 55 | 56 | #### Why a "type" 57 | 58 | Well, to make Ecto compute the ID only at "insert-time", you gotta define an 59 | `autogenerate/0` for that field's `type`. Which means, this nanoid field needs 60 | to be of _a custom `Ecto.Type`_, ala `Ecto.Nanoid`. 61 | 62 | #### Can I use this as my primary key? 63 | 64 | Well of course! Example: 65 | 66 | ``` elixir 67 | @primary_key {:id, :string, autogenerate: {Ecto.Nanoid, :autogenerate, []}} 68 | schema "users" do 69 | field :email, :string 70 | field :name, :string 71 | 72 | timestamps() 73 | end 74 | 75 | ``` 76 | 77 | ``` elixir 78 | def change do 79 | create table(:users, primary_key: false) do 80 | add :id, :string, primary_key: true 81 | add :email, :string 82 | add :name, :string 83 | 84 | timestamps() 85 | end 86 | 87 | end 88 | ``` 89 | 90 | #### How should I configure the Nanoids? 91 | You [can use `config.exs`](https://hexdocs.pm/nanoid/readme.html#configuration) to configure all the 92 | nanoids of your application. 93 | 94 | Make sure you recompile `nanoid` in the relevant "environment" (see 95 | [this thread](https://github.com/railsmechanic/nanoid/issues/6#issuecomment-644702147)) 96 | 97 | ## Ecto.Hashids 98 | 99 | ~~_Coming shortly folks!_~~ 100 | Maybe not... Take a look at [peek-travel/ecto_hashids](https://github.com/peek-travel/ecto_hashids). 101 | 102 | ## Installation 103 | 104 | The package can be installed by adding `ecto_identifier` to your list of 105 | dependencies in `mix.exs`: 106 | 107 | ```elixir 108 | def deps do 109 | [ 110 | {:ecto_identifier, "~> 0.2.0"} 111 | ] 112 | end 113 | ``` 114 | 115 | # License 116 | 117 | Copyright 2018-2021 Ananya Bahadur 118 | 119 | Licensed under the Apache License, Version 2.0 (the "License"); 120 | you may not use this file except in compliance with the License. 121 | You may obtain a copy of the License at 122 | 123 | http://www.apache.org/licenses/LICENSE-2.0 124 | 125 | Unless required by applicable law or agreed to in writing, software 126 | distributed under the License is distributed on an "AS IS" BASIS, 127 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 128 | See the License for the specific language governing permissions and 129 | limitations under the License. 130 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :ecto_identifier, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:ecto_identifier, :key) 18 | # 19 | # You can also configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | 30 | import_config "#{Mix.env()}.exs" 31 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :git_hooks, 4 | verbose: true, 5 | hooks: [ 6 | pre_commit: [ 7 | mix_tasks: [ 8 | "format --check-formatted" 9 | ] 10 | ], 11 | post_commit: [ 12 | verbose: true, 13 | mix_tasks: [ 14 | "credo --strict" 15 | ] 16 | ] 17 | ] 18 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :ecto_identifier, EctoIdentifier.TestRepo, 4 | adapter: Ecto.Adapters.Postgres, 5 | username: "postgres", 6 | password: "", 7 | database: "ecto_identifier", 8 | hostname: "localhost", 9 | pool: Ecto.Adapters.SQL.Sandbox 10 | 11 | config :ecto_identifier, ecto_repos: [EctoIdentifier.TestRepo] 12 | -------------------------------------------------------------------------------- /lib/ecto_nanoid.ex: -------------------------------------------------------------------------------- 1 | defmodule Ecto.Nanoid do 2 | @moduledoc """ 3 | Custom type that can auto-generate nanoids. 4 | 5 | ## Usage 6 | ``` 7 | defmodule Post do 8 | use Ecto.Schema 9 | alias Ecto.Nanoid 10 | 11 | schema "posts" do 12 | field(:number, Ecto.Nanoid, autogenerate: true) 13 | field(:data, :string) 14 | end 15 | end 16 | ``` 17 | 18 | In your migrations for the `posts` table, 19 | ``` 20 | create table(:posts) do 21 | add :number, :string 22 | add :data, :string 23 | end 24 | ``` 25 | """ 26 | 27 | use Ecto.Type 28 | 29 | def type, do: :string 30 | 31 | def cast(value) when is_binary(value), do: {:ok, value} 32 | def cast(_), do: :error 33 | 34 | def load(value), do: {:ok, value} 35 | 36 | def dump(value) when is_binary(value), do: {:ok, value} 37 | def dump(_), do: :error 38 | 39 | @doc """ 40 | Delegates generation of the field to `Nanoid.generate/0`. 41 | 42 | To change the default nanoid size and alphabet, use `config.exs` as indicated 43 | [here](https://hexdocs.pm/nanoid/readme.html#configuration). 44 | """ 45 | @spec autogenerate() :: String.t() 46 | def autogenerate, do: Nanoid.generate() 47 | end 48 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule EctoIdentifier.MixProject do 2 | use Mix.Project 3 | 4 | @version "0.2.0" 5 | 6 | def project do 7 | [ 8 | app: :ecto_identifier, 9 | version: @version, 10 | elixir: "~> 1.5", 11 | start_permanent: Mix.env() == :prod, 12 | elixirc_paths: elixirc_paths(Mix.env()), 13 | source_url: "https://github.com/oyeb/ecto_identifier", 14 | description: description(), 15 | deps: deps(), 16 | package: package(), 17 | docs: docs(), 18 | deps: deps(), 19 | aliases: aliases(), 20 | test_coverage: [tool: ExCoveralls], 21 | preferred_cli_env: [coveralls: :test, "coveralls.json": :test, "coveralls.html": :test] 22 | ] 23 | end 24 | 25 | defp description do 26 | "Provides some custom Ecto types to manage autogenerated unique identifiers." 27 | end 28 | 29 | # Run "mix help compile.app" to learn about applications. 30 | def application do 31 | [ 32 | extra_applications: [:logger] 33 | ] 34 | end 35 | 36 | # Run "mix help deps" to learn about dependencies. 37 | defp deps do 38 | [ 39 | {:ecto, "~> 2.0 or ~> 3.0"}, 40 | {:nanoid, "~> 2.0"}, 41 | {:ecto_sql, "~> 3.0", only: [:dev, :test]}, 42 | {:credo, "~> 0.9", only: :dev, runtime: false}, 43 | {:excoveralls, "~> 0.7", only: :test}, 44 | {:postgrex, "~> 0.13", only: :test}, 45 | {:ex_doc, "~> 0.18", only: :dev, runtime: false}, 46 | {:git_hooks, "~> 0.6", only: :dev} 47 | ] 48 | end 49 | 50 | # Specifies which paths to compile per environment. 51 | defp elixirc_paths(:test), do: ["lib", "test/support"] 52 | defp elixirc_paths(_), do: ["lib"] 53 | 54 | defp package do 55 | [ 56 | contributors: ["Ananya Bahadur"], 57 | maintainers: ["Ananya Bahadur"], 58 | licenses: ["Apache 2.0"], 59 | links: %{ 60 | "GitHub" => "https://github.com/oyeb/ecto_identifier", 61 | "Readme" => "https://github.com/oyeb/ecto_identifier/blob/v#{@version}/README.md", 62 | "Changelog" => "https://github.com/oyeb/ecto_identifier/blob/v#{@version}/CHANGELOG.md" 63 | } 64 | ] 65 | end 66 | 67 | defp docs do 68 | [ 69 | extras: ["README.md", "CHANGELOG.md"], 70 | main: "readme", 71 | source_ref: "v#{@version}" 72 | ] 73 | end 74 | 75 | # Aliases are shortcuts or tasks specific to the current project. 76 | # For example, to create, migrate and run the seeds file at once: 77 | # 78 | # $ mix ecto.setup 79 | # 80 | # See the documentation for `Mix` for more info on aliases. 81 | defp aliases do 82 | [ 83 | "ecto.setup": ["ecto.create", "ecto.migrate"], 84 | "ecto.reset": ["ecto.drop", "ecto.setup"], 85 | test: ["ecto.create --quiet", "ecto.migrate", "test"] 86 | ] 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "blankable": {:hex, :blankable, "1.0.0", "89ab564a63c55af117e115144e3b3b57eb53ad43ba0f15553357eb283e0ed425", [:mix], [], "hexpm", "7cf11aac0e44f4eedbee0c15c1d37d94c090cb72a8d9fddf9f7aec30f9278899"}, 3 | "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, 4 | "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, 5 | "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, 6 | "credo": {:hex, :credo, "0.10.2", "03ad3a1eff79a16664ed42fc2975b5e5d0ce243d69318060c626c34720a49512", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "539596b6774069260d5938aa73042a2f5157e1c0215aa35f5a53d83889546d14"}, 7 | "db_connection": {:hex, :db_connection, "2.4.0", "d04b1b73795dae60cead94189f1b8a51cc9e1f911c234cc23074017c43c031e5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad416c21ad9f61b3103d254a71b63696ecadb6a917b36f563921e0de00d7d7c8"}, 8 | "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, 9 | "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm", "c57508ddad47dfb8038ca6de1e616e66e9b87313220ac5d9817bc4a4dc2257b9"}, 10 | "earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"}, 11 | "ecto": {:hex, :ecto, "3.6.2", "efdf52acfc4ce29249bab5417415bd50abd62db7b0603b8bab0d7b996548c2bc", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efad6dfb04e6f986b8a3047822b0f826d9affe8e4ebdd2aeedbfcb14fd48884e"}, 12 | "ecto_sql": {:hex, :ecto_sql, "3.6.2", "9526b5f691701a5181427634c30655ac33d11e17e4069eff3ae1176c764e0ba3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.6.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5ec9d7e6f742ea39b63aceaea9ac1d1773d574ea40df5a53ef8afbd9242fdb6b"}, 13 | "ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"}, 14 | "excoveralls": {:hex, :excoveralls, "0.14.1", "14140e4ef343f2af2de33d35268c77bc7983d7824cb945e6c2af54235bc2e61f", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4a588f9f8cf9dc140cc1f3d0ea4d849b2f76d5d8bee66b73c304bb3d3689c8b0"}, 15 | "git_hooks": {:hex, :git_hooks, "0.6.2", "5665c7c9acf4ef0daa447e0913ecbcecc2cbdaf55e5663fb45fb906d54a70cb1", [:mix], [{:blankable, "~> 1.0.0", [hex: :blankable, repo: "hexpm", optional: false]}, {:recase, "~> 0.7.0", [hex: :recase, repo: "hexpm", optional: false]}], "hexpm", "0ff2928d35fae60136d2f9177c8e59c9baadfb3e7db1d2ce03ded2cdcf394048"}, 16 | "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"}, 17 | "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, 18 | "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, 19 | "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, 20 | "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, 21 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, 22 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, 23 | "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, 24 | "nanoid": {:hex, :nanoid, "2.0.5", "1d2948d8967ef2d948a58c3fef02385040bd9823fc6394bd604b8d98e5516b22", [:mix], [], "hexpm", "956e8876321104da72aa48770539ff26b36b744cd26753ec8e7a8a37e53d5f58"}, 25 | "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, 26 | "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, 27 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, 28 | "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, 29 | "postgrex": {:hex, :postgrex, "0.15.9", "46f8fe6f25711aeb861c4d0ae09780facfdf3adbd2fb5594ead61504dd489bda", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "610719103e4cb2223d4ab78f9f0f3e720320eeca6011415ab4137ddef730adee"}, 30 | "recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"}, 31 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, 32 | "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, 33 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, 34 | } 35 | -------------------------------------------------------------------------------- /priv/test_repo/migrations/20180612113352_create_posts_table_with_nanoid.exs: -------------------------------------------------------------------------------- 1 | defmodule EctoIdentifier.TestRepo.Migrations.CreatePostsTableWithNanoid do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create(table(:posts_nanoid)) do 6 | add(:number, :string, comments: "the nanoid field") 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/ecto_nanoid_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ecto.NanoidTest do 2 | use ExUnit.Case 3 | 4 | alias Ecto.{Changeset, Nanoid} 5 | alias EctoIdentifier.PostWithNanoid 6 | alias EctoIdentifier.TestRepo 7 | 8 | test "type/0" do 9 | assert :string == Nanoid.type() 10 | end 11 | 12 | test "cast" do 13 | assert {:ok, "pinkfloyd"} = Nanoid.cast("pinkfloyd") 14 | assert :error = Nanoid.cast(:pinkfloyd) 15 | assert :error = Nanoid.cast(123_456) 16 | end 17 | 18 | test "dump" do 19 | assert {:ok, "pinkfloyd"} = Nanoid.dump("pinkfloyd") 20 | assert :error = Nanoid.dump(:pinkfloyd) 21 | assert :error = Nanoid.dump(123_456) 22 | end 23 | 24 | test "load" do 25 | assert {:ok, "pinkfloyd"} = Nanoid.load("pinkfloyd") 26 | end 27 | 28 | describe "autogenerate" do 29 | test "struct doesn't come with an 'number' at creation" do 30 | post = %PostWithNanoid{} 31 | assert is_nil(post.id) 32 | assert is_nil(post.number) 33 | end 34 | 35 | test "'number' generated at insertion" do 36 | post = TestRepo.insert!(%PostWithNanoid{}) 37 | refute is_nil(post.id) 38 | assert post.number =~ ~r/[-_A-Za-z0-9]{21}/ 39 | end 40 | 41 | test "'number' not generated if provided in changeset/struct/map/keyword" do 42 | post = TestRepo.insert!(%PostWithNanoid{number: "pinkfloyd"}) 43 | refute is_nil(post.id) 44 | assert post.number == "pinkfloyd" 45 | 46 | post = 47 | %PostWithNanoid{} 48 | |> Changeset.change(%{number: "pinkfloyd"}) 49 | |> TestRepo.insert!() 50 | 51 | refute is_nil(post.id) 52 | assert post.number == "pinkfloyd" 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /test/support/post_with_nanoid.ex: -------------------------------------------------------------------------------- 1 | defmodule EctoIdentifier.PostWithNanoid do 2 | @moduledoc false 3 | use Ecto.Schema 4 | 5 | alias Ecto.Nanoid 6 | 7 | schema("posts_nanoid") do 8 | field(:number, Nanoid, autogenerate: true) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/support/test_repo.ex: -------------------------------------------------------------------------------- 1 | defmodule EctoIdentifier.TestRepo do 2 | @moduledoc false 3 | use Ecto.Repo, otp_app: :ecto_identifier, adapter: Ecto.Adapters.Postgres 4 | end 5 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | {:ok, _} = EctoIdentifier.TestRepo.start_link() 3 | --------------------------------------------------------------------------------