{"id":13809189,"url":"https://github.com/WTTJ/ecto_anon","last_synced_at":"2025-05-14T05:33:22.499Z","repository":{"id":41313614,"uuid":"437049192","full_name":"WTTJ/ecto_anon","owner":"WTTJ","description":"Data anonymization for your Ecto models !","archived":false,"fork":false,"pushed_at":"2025-03-19T05:23:09.000Z","size":119,"stargazers_count":92,"open_issues_count":8,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-12T18:53:52.940Z","etag":null,"topics":["anon","anonymization","database","ecto","elixir"],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/WTTJ.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":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-12-10T16:47:58.000Z","updated_at":"2025-03-13T07:39:11.000Z","dependencies_parsed_at":"2024-11-10T21:36:10.782Z","dependency_job_id":"cfea3a9f-4062-43bb-b0ad-cf2ba10e5710","html_url":"https://github.com/WTTJ/ecto_anon","commit_stats":{"total_commits":44,"total_committers":6,"mean_commits":7.333333333333333,"dds":0.6590909090909092,"last_synced_commit":"917868513eaa77f6b12241f46e9c8ac57224e345"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WTTJ%2Fecto_anon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WTTJ%2Fecto_anon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WTTJ%2Fecto_anon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WTTJ%2Fecto_anon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WTTJ","download_url":"https://codeload.github.com/WTTJ/ecto_anon/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254077008,"owners_count":22010637,"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":["anon","anonymization","database","ecto","elixir"],"created_at":"2024-08-04T01:02:05.766Z","updated_at":"2025-05-14T05:33:22.176Z","avatar_url":"https://github.com/WTTJ.png","language":"Elixir","funding_links":[],"categories":["ORM and Datamapping"],"sub_categories":[],"readme":"# ecto_anon\n\n[![Module Version](https://img.shields.io/hexpm/v/ecto_anon.svg)](https://hex.pm/packages/ecto_anon)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/ecto_anon/)\n[![Total Download](https://img.shields.io/hexpm/dt/ecto_anon.svg)](https://hex.pm/packages/ecto_anon)\n[![License](https://img.shields.io/hexpm/l/ecto_anon)](https://github.com/WTTJ/ecto_anon/blob/main/LICENSE.md)\n[![Last Updated](https://img.shields.io/github/last-commit/WTTJ/ecto_anon.svg)](https://github.com/WTTJ/ecto_anon/commits/main)\n\nSimple way to handle data anonymization directly in your [Ecto](https://github.com/elixir-ecto/ecto) schemas\n\n---\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Options](#options)\n  - [Default values](#default-values)\n  - [Native functions](#native-functions)\n  - [Custom functions](#custom-functions)\n  - [Migrations](#migrations)\n  - [Filtering](#filtering)\n- [License](#copyright-and-license)\n\n# Installation\n\nAdd `:ecto_anon` to your `mix.exs` dependencies:\n\n```elixir\ndef deps do\n  [\n    {:ecto_anon, \"~\u003e 0.6.0\"}\n  ]\nend\n```\n\n# Usage\n\nDefine an `anon_schema` in your schema module and specify every fields you want to anonymize (regular fields, associations, embeds):\n\n```elixir\ndefmodule User do\n  use Ecto.Schema\n  use EctoAnon.Schema\n\n  anon_schema [\n    :name,\n    :email\n  ]\n\n  schema \"users\" do\n    field :name, :string\n    field :age, :integer\n    field :email, :string\n\n    anonymized()\n  end\nend\n```\n\nThen use `EctoAnon.run` to apply anonymization on desired resource\n\n```elixir\nuser = Repo.get(User, id)\n%User{name: \"jane\", age: 24, email: \"jane@email.com\"}\n\nEctoAnon.run(user, Repo)\n{:ok, %User{name: \"redacted\", age: 24, email: \"redacted\"}}\n```\n\n## Options\n\n### `cascade`\n\nWhen set to `true`, it allows `ecto_anon` to preload and anonymize\nall associations (and associations of these associations) automatically in cascade.\nCould be used to anonymize all data related to a struct in a single call.\n\nNote that this won't traverse `belongs_to` associations.\n\nDefault: `false`\n\n```elixir\ndefmodule User do\n  use Ecto.Schema\n  use EctoAnon.Schema\n\n  anon_schema [\n    :lastname,\n    :email,\n    :followers,\n    :favorite_quote,\n    :quotes,\n    last_sign_in_at: [:anonymized_date, options: [:only_year]]\n  ]\n\n  schema \"users\" do\n    field(:firstname, :string)\n    field(:lastname, :string)\n    field(:email, :string)\n    field(:last_sign_in_at, :utc_datetime)\n\n    has_many(:comments, Comment, foreign_key: :author_id, references: :id)\n    embeds_one(:favorite_quote, Quote)\n    embeds_many(:quotes, Quote)\n\n    many_to_many(\n      :followers,\n      __MODULE__,\n      join_through: Follower,\n      join_keys: [follower_id: :id, followee_id: :id]\n    )\n\n    anonymized()\n  end\nend\n\ndefmodule Quote do\n  use Ecto.Schema\n  use EctoAnon.Schema\n\n  anon_schema([\n    :quote,\n    :author\n  ])\n\n  embedded_schema do\n    field(:quote, :string)\n    field(:author, :string)\n  end\nend\n\ndefmodule Follower do\n  use Ecto.Schema\n\n  schema \"followers\" do\n    field(:follower_id, :id)\n    field(:followee_id, :id)\n    timestamps()\n  end\nend\n```\n\n```elixir\nRepo.get(User, id)\n|\u003e EctoAnon.run(Repo, cascade: true)\n\n{:ok,\n   %User{\n     email: \"redacted\",\n     firstname: \"John\",\n     last_sign_in_at: ~U[2022-01-01 00:00:00Z],\n     lastname: \"redacted\",\n     favorite_quote: %Quote{\n       quote: \"redacted\",\n       author: \"redacted\"\n     },\n     quotes: [\n       %Quote{\n         quote: \"redacted\",\n         author: \"redacted\"\n       },\n       %Quote{\n         quote: \"redacted\",\n         author: \"redacted\"\n       }\n     ]\n   }\n }\n```\n\n### `log`\n\nWhen set to `true`, it will set `anonymized` field accordingly when `EctoAnon.run` is called on a ressource.\n\nDefault: `true`\n\n## Default values\n\nBy default, a field will be anonymized with those valuee, based on its type:\n\n| type                | value                             |\n| ------------------- | --------------------------------- |\n| integer             | 0                                 |\n| float               | 0.0                               |\n| string              | redacted                          |\n| map                 | %{}                               |\n| decimal             | Decimal . new ( \" 0.0 \" )         |\n| date                | ~D[ 1970-01-01 ]                  |\n| datetime            | ~U[ 1970-01-01 00:00:00Z ]        |\n| datetime_usec       | ~U[ 1970-01-01 00:00:00.000000Z ] |\n| naive_datetime      | ~N[ 1970-01-01 00:00:00 ]         |\n| naive_datetime_usec | ~N[ 1970-01-01 00:00:00.000000 ]  |\n| time                | ~T[ 00:00:00 ]                    |\n| time_usec           | ~T[ 00:00:00.000000 ]             |\n| boolean             | no change                         |\n| id                  | no change                         |\n| binary_id           | no change                         |\n| binary              | no change                         |\n\n## Native functions\n\n```elixir\nanon_schema([\n  email: :anonymized_email,\n  birthdate: [:anonymized_date, options: [:only_year]]\n])\n```\n\nNatively, `ecto_anon` embeds differents functions to suit your needs\n\n| function          | role                                               | options    |\n| ----------------- | -------------------------------------------------- | ---------- |\n| :anonymized_date  | Anonymizes partially or completely a date/datetime | :only_year |\n| :anonymized_email | Anonymizes partially or completely an email        | :partial   |\n| :anonymized_phone | Anonymizes a phone number (currently only FR)      |            |\n| :random_uuid      | Returns a random UUID                              |            |\n\n## Custom functions\n\n```elixir\nanon_schema([\n    address: \u0026__MODULE__.anonymized_address/3\n])\n\ndef anonymized_address(:map, %{} = address, _opts \\\\ []) do\n  address\n  |\u003e Map.drop([\"street\"])\nend\n```\n\nYou can also pass custom functions with the following signature: `function(type, value, options)`\n\n## Migrations\n\nBy importing `EctoAnon.Migration` in your ecto migration file, you can add an `anonymized()` macro that will generate an `anonymized` boolean field in your table:\n\n```elixir\ndefmodule MyApp.Repo.Migrations.CreateUser do\n  use Ecto.Migration\n  import EctoAnon.Migration\n\n  def change do\n    create table(:users) do\n      add :firstname, :string\n      add :lastname, :string\n      timestamps()\n      anonymized()\n    end\n  end\nend\n```\n\nCombined with `log` option when executing the anonymization, it will allow you to identify anonymized rows and exclude them in your queries with `EctoAnon.Query.not_anonymized/1`.\n\n## Filtering\n\nAs you can create an [anonymized field in your migration](#migrations), you can add `anonymized()` in your schema, just like `timestamps()`.\n\nBy adding this field, you can use it to filter your resources and exclude anonymized data easily:\n\n```elixir\nimport EctoAnon.Query\nimport Ecto.Query\n\nfrom(u in User, select: u)\n|\u003e not_anonymized()\n|\u003e Repo.all()\n```\n\n# Copyright and License\n\n_Copyright (c) 2022 CORUSCANT (Welcome to the Jungle) - https://www.welcometothejungle.com_\n\n_This library is licensed under the [MIT](LICENSE.md)_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWTTJ%2Fecto_anon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FWTTJ%2Fecto_anon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWTTJ%2Fecto_anon/lists"}