{"id":30058914,"url":"https://github.com/solnic/drops_relation","last_synced_at":"2025-08-19T11:08:51.058Z","repository":{"id":303815901,"uuid":"1016376851","full_name":"solnic/drops_relation","owner":"solnic","description":"🔋-included relation abstraction on top of Ecto with schema inference and composable query API + more ✨","archived":false,"fork":false,"pushed_at":"2025-07-30T12:00:20.000Z","size":774,"stargazers_count":26,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-10T15:27:02.213Z","etag":null,"topics":["databases","ecto","ecto-sql","elixir","elixir-lang","postgresql","sqlite"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/solnic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2025-07-08T23:33:30.000Z","updated_at":"2025-08-04T01:16:22.000Z","dependencies_parsed_at":"2025-07-15T20:57:02.003Z","dependency_job_id":null,"html_url":"https://github.com/solnic/drops_relation","commit_stats":null,"previous_names":["solnic/ecto_relation","solnic/drops_relation"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/solnic/drops_relation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solnic%2Fdrops_relation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solnic%2Fdrops_relation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solnic%2Fdrops_relation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solnic%2Fdrops_relation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/solnic","download_url":"https://codeload.github.com/solnic/drops_relation/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solnic%2Fdrops_relation/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271143400,"owners_count":24706346,"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","status":"online","status_checked_at":"2025-08-19T02:00:09.176Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["databases","ecto","ecto-sql","elixir","elixir-lang","postgresql","sqlite"],"created_at":"2025-08-08T00:37:14.969Z","updated_at":"2025-08-19T11:08:51.028Z","avatar_url":"https://github.com/solnic.png","language":"Elixir","readme":"# Drops.Relation\n\n[![CI](https://github.com/solnic/drops_relation/actions/workflows/ci.yml/badge.svg)](https://github.com/solnic/drops_relation/actions/workflows/ci.yml) [![Hex pm](https://img.shields.io/hexpm/v/drops_relation.svg?style=flat)](https://hex.pm/packages/drops_relation) [![hex.pm downloads](https://img.shields.io/hexpm/dt/drops_relation.svg?style=flat)](https://hex.pm/packages/drops_relation)\n\nHigh-level API for defining database relations with automatic schema inference and composable queries.\n\nDrops.Relation automatically introspects database tables, generates Ecto schemas, and provides a convenient query API that feels like working directly with Ecto.Repo while adding powerful composition features.\n\n## Installation\n\nAdd `drops_relation` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:drops_relation, \"~\u003e 0.1.0\"}\n  ]\nend\n```\n\nThen run installation task:\n\n```bash\nmix drops.relation.install\n```\n\n## Configuration\n\nConfigure Drops.Relation in your application config:\n\n```elixir\nconfig :my_app, :drops,\n  relation: [\n    repo: MyApp.Repo\n  ]\n```\n\n## Quick Start\n\n```elixir\n# Define a relation\ndefmodule MyApp.Users do\n  use Drops.Relation, otp_app: :my_app\n\n  schema(\"users\", infer: true)\nend\n\n# Use it like Ecto.Repo\n{:ok, user} = MyApp.Users.insert(%{name: \"John\", email: \"john@example.com\"})\n\nuser = MyApp.Users.get(1)\nusers = MyApp.Users.all()\n```\n\n## Automatic Schemas\n\nDrops.Relation automatically introspects your database tables and generates Ecto schemas:\n\n```elixir\ndefmodule MyApp.Users do\n  use Drops.Relation, otp_app: :my_app\n\n  # Automatically infers all columns, types, primary keys, and foreign keys\n  schema(\"users\", infer: true)\nend\n\n# Access the generated schema\nschema = MyApp.Users.schema()\n\nschema[:id]\n# %Drops.Relation.Schema.Field{\n#   name: :id,\n#   type: :integer,\n#   source: :id,\n#   meta: %{\n#     default: nil,\n#     index: false,\n#     type: :integer,\n#     primary_key: true,\n#     foreign_key: false,\n#     check_constraints: [],\n#     index_name: nil,\n#     nullable: true\n#   }\n# }\n\nschema[:email]\n# %Drops.Relation.Schema.Field{\n#   name: :email,\n#   type: :string,\n#   source: :email,\n#   meta: %{\n#     default: nil,\n#     index: true,\n#     type: :string,\n#     primary_key: false,\n#     foreign_key: false,\n#     check_constraints: [],\n#     index_name: \"users_email_index\",\n#     nullable: false\n#   }\n# }\n```\n\nYou can also define schemas manually or customize inferred ones:\n\n```elixir\ndefmodule MyApp.Users do\n  use Drops.Relation, otp_app: :my_app\n\n  schema(\"users\") do\n    field(:name, :string)\n    field(:email, :string)\n    field(:active, :boolean, default: true)\n\n    timestamps()\n  end\nend\n```\n\n## Relation Query API\n\nDrops.Relation provides all the familiar Ecto.Repo functions:\n\n```elixir\n# Reading data\nuser = Users.get(1)                           # Get by primary key\nuser = Users.get!(1)                          # Get by primary key, raise if not found\nuser = Users.get_by(email: \"john@example.com\") # Get by attributes\nusers = Users.all()                           # Get all records\nusers = Users.all_by(active: true)            # Get all matching attributes\n\n# Aggregations\ncount = Users.count()                         # Count all records\navg_age = Users.aggregate(:avg, :age)         # Aggregate functions\n\n# Writing data\n{:ok, user} = Users.insert(%{name: \"John\"})   # Insert with map\n{:ok, user} = Users.insert!(changeset)        # Insert with changeset\n{:ok, user} = Users.update(user, %{name: \"Jane\"}) # Update record\n{:ok, user} = Users.delete(user)              # Delete record\n\n# Changesets\nchangeset = Users.changeset(%{name: \"John\"})  # Create changeset\nchangeset = Users.changeset(user, %{name: \"Jane\"}) # Update changeset\n\n# Bulk operations\nUsers.insert_all([%{name: \"Alice\"}, %{name: \"Bob\"}])\nUsers.update_all([active: false])\nUsers.delete_all()\n```\n\n## Composable Queries\n\nChain operations together for powerful query composition:\n\n```elixir\n# Basic composition\nactive_users = Users\n               |\u003e Users.restrict(active: true)\n               |\u003e Users.order(:name)\n               |\u003e Enum.to_list()\n\n# Complex restrictions\nadmins = Users\n         |\u003e Users.restrict(role: [\"admin\", \"super_admin\"])\n         |\u003e Users.restrict(active: true)\n         |\u003e Users.order([{:last_login, :desc}, :name])\n\n# Works with any Enum function\nuser_names = Users\n             |\u003e Users.restrict(active: true)\n             |\u003e Enum.map(\u0026 \u00261.name)\n\n# Preload associations\nusers_with_posts = Users\n                   |\u003e Users.restrict(active: true)\n                   |\u003e Users.preload(:posts)\n                   |\u003e Enum.to_list()\n```\n\n### Available Operations\n\n- `restrict/2` - Add WHERE conditions (supports lists for IN queries)\n- `order/2` - Add ORDER BY clauses (supports atoms, lists, and tuples)\n- `preload/2` - Preload associations\n- Auto-generated finders like `get_by_email/1`, `get_by_name/1` based on indices\n\n## Custom Queries\n\nDefine reusable query functions with the `defquery` macro:\n\n```elixir\ndefmodule MyApp.Users do\n  use Drops.Relation, otp_app: :my_app\n\n  schema(\"users\", infer: true)\n\n  defquery active() do\n    from(u in relation(), where: u.active == true)\n  end\n\n  defquery by_role(role) when is_binary(role) do\n    from(u in relation(), where: u.role == ^role)\n  end\n\n  defquery by_role(roles) when is_list(roles) do\n    from(u in relation(), where: u.role in ^roles)\n  end\n\n  defquery recent(days \\\\ 7) do\n    cutoff = DateTime.utc_now() |\u003e DateTime.add(-days, :day)\n    from(u in relation(), where: u.inserted_at \u003e= ^cutoff)\n  end\n\n  defquery with_posts() do\n    from(u in relation(),\n         join: p in assoc(u, :posts),\n         distinct: u.id)\n  end\nend\n```\n\n### Query Composition\n\nCustom queries are fully composable with built-in operations:\n\n```elixir\n# Compose custom queries\nrecent_admins = Users\n                |\u003e Users.active()\n                |\u003e Users.by_role(\"admin\")\n                |\u003e Users.recent(30)\n                |\u003e Users.order(:name)\n                |\u003e Enum.to_list()\n\n# Mix with restrict operations\nactive_users_with_email = Users\n                          |\u003e Users.active()\n                          |\u003e Users.restrict(email: {:not, nil})\n                          |\u003e Users.order(:email)\n\n# Chain multiple custom queries\npower_users = Users\n              |\u003e Users.active()\n              |\u003e Users.with_posts()\n              |\u003e Users.recent(90)\n              |\u003e Users.count()\n```\n\nThe `relation()` function inside `defquery` blocks returns the relation module, allowing you to reference the current relation in your Ecto queries.\n\n## Advanced Query Composition\n\nFor complex query logic involving multiple conditions and boolean operations, use the `query` macro from `Drops.Relation.Query`:\n\n```elixir\ndefmodule MyApp.Users do\n  use Drops.Relation, otp_app: :my_app\n  import Drops.Relation.Query\n\n  schema(\"users\", infer: true)\n\n  defquery active() do\n    from(u in relation(), where: u.active == true)\n  end\n\n  defquery inactive() do\n    from(u in relation(), where: u.active == false)\n  end\n\n  defquery adult() do\n    from(u in relation(), where: u.age \u003e= 18)\n  end\n\n  defquery with_email() do\n    from(u in relation(), where: not is_nil(u.email))\n  end\nend\n```\n\n### Boolean Logic with AND/OR\n\nThe `query` macro supports complex boolean expressions using `and` and `or` operators:\n\n```elixir\n# Simple AND operation\nadult_active_users = Users\n                     |\u003e query([u], u.active() and u.adult())\n                     |\u003e Enum.to_list()\n\n# Simple OR operation\nactive_or_adult = Users\n                  |\u003e query([u], u.active() or u.adult())\n                  |\u003e Enum.to_list()\n\n# Complex nested conditions\ncomplex_query = Users\n                |\u003e query([u],\n                  (u.active() and u.adult()) or\n                  (u.inactive() and u.with_email())\n                )\n                |\u003e Users.order(:name)\n                |\u003e Enum.to_list()\n```\n\n### Mixing Built-in and Custom Operations\n\nCombine auto-generated functions like `restrict/2` and `get_by_*/1` with custom queries:\n\n```elixir\n# Mix restrict with custom queries\nfiltered_users = Users\n                 |\u003e query([u], u.active() and u.restrict(role: [\"admin\", \"user\"]))\n                 |\u003e Enum.to_list()\n\n# Combine auto-generated finders with custom logic\nspecific_users = Users\n                 |\u003e query([u],\n                   u.get_by_name(\"John\") or\n                   (u.active() and u.restrict(email: \"admin@example.com\"))\n                 )\n                 |\u003e Enum.to_list()\n\n# Multiple field restrictions with boolean logic\nadmin_users = Users\n              |\u003e query([u],\n                u.restrict(name: [\"Alice\", \"Bob\"]) and\n                u.active() and\n                u.with_email()\n              )\n              |\u003e Users.order(:name)\n              |\u003e Enum.to_list()\n```\n\n### Advanced Composition Patterns\n\nChain multiple OR operations and apply ordering:\n\n```elixir\n# Multiple OR conditions\npriority_users = Users\n                 |\u003e query([u],\n                   u.get_by_name(\"CEO\") or\n                   u.get_by_name(\"CTO\") or\n                   u.restrict(role: \"admin\")\n                 )\n                 |\u003e Users.order([{:role, :desc}, :name])\n                 |\u003e Enum.to_list()\n\n# Complex nested AND/OR with post-query operations\nresult = Users\n         |\u003e query([u],\n           ((u.active() and u.adult()) or (u.inactive() and u.with_email())) and\n           u.restrict(department: [\"engineering\", \"product\"])\n         )\n         |\u003e Users.order(desc: :created_at)\n         |\u003e Enum.take(10)\n```\n\n### Query Syntax\n\nThe `query` macro uses Ecto-style variable bindings:\n\n- `[u]` - Single binding variable for the relation\n- `u.function_name()` - Calls relation functions on the binding\n- `and`/`or` - Boolean operators for combining conditions\n- Parentheses for grouping complex expressions\n\nAll query operations return relation structs that can be further composed with other operations like `order/2`, `preload/2`, or used with `Enum` functions.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolnic%2Fdrops_relation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsolnic%2Fdrops_relation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolnic%2Fdrops_relation/lists"}