{"id":15063678,"url":"https://github.com/fuelen/composite","last_synced_at":"2025-04-10T11:25:18.042Z","repository":{"id":37857811,"uuid":"272285422","full_name":"fuelen/composite","owner":"fuelen","description":"A utility for building dynamic queries in Elixir","archived":false,"fork":false,"pushed_at":"2024-03-24T19:16:02.000Z","size":40,"stargazers_count":23,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-09-08T09:27:13.842Z","etag":null,"topics":["ecto","elixir","queries","rdbms","sql"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fuelen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2020-06-14T21:21:03.000Z","updated_at":"2024-09-04T17:17:56.000Z","dependencies_parsed_at":"2022-08-18T17:00:20.348Z","dependency_job_id":"f3d06891-e0d7-4598-be78-493558cd04fc","html_url":"https://github.com/fuelen/composite","commit_stats":{"total_commits":20,"total_committers":2,"mean_commits":10.0,"dds":"0.050000000000000044","last_synced_commit":"334467824bed092548cdf2a71779f8ebd6ba07ba"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuelen%2Fcomposite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuelen%2Fcomposite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuelen%2Fcomposite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuelen%2Fcomposite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fuelen","download_url":"https://codeload.github.com/fuelen/composite/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247909134,"owners_count":21016475,"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":["ecto","elixir","queries","rdbms","sql"],"created_at":"2024-09-25T00:05:56.119Z","updated_at":"2025-04-10T11:25:18.026Z","avatar_url":"https://github.com/fuelen.png","language":"Elixir","readme":"# Composite\n\n## Installation\n\nThe package can be installed from [hex.pm](https://hex.pm/packages/composite) by\nadding `composite` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:composite, \"~\u003e 0.5\"}\n  ]\nend\n```\nDocs can be found at [https://hexdocs.pm/composite](https://hexdocs.pm/composite).\n\n## About\n\nComposite is a versatile utility library for building dynamic queries in Elixir. It simplifies the process of\nconstructing complex queries based on input parameters, making your code more concise and readable. While the library\nwas with Ecto in mind, it can be used with any Elixir term, as it's essentially an advanced wrapper around Enum.reduce/3.\n\nThe majority of the features of the library can be expressed by this example:\n```elixir\ndef list_users(params) do\n  MyApp.User\n  |\u003e where(active: true)\n  |\u003e Composite.new(params)\n  |\u003e Composite.param(:name, \u0026where(\u00261, name: ^\u00262))\n  |\u003e Composite.param([:company, :name], \u0026where(\u00261, [companies: companies], companies.name == ^\u00262),\n    requires: :companies\n  )\n  |\u003e Composite.param(\n    :locations,\n    \u0026where(\u00261, [departments: departments], departments.location in ^\u00262),\n    requires: :departments,\n    ignore?: \u0026(\u00261 in [nil, []] or \"Worldwide\" in \u00261)\n  )\n  |\u003e Composite.param(\n    :order,\n    fn\n      query, :department_name_asc -\u003e\n        query |\u003e order_by([departments: departments], asc: departments.name)\n\n      query, :username_asc -\u003e\n        query |\u003e order_by(asc: :name)\n    end,\n    requires: fn\n      :department_name_asc -\u003e :departments\n      _ -\u003e nil\n    end\n  )\n  |\u003e Composite.dependency(:departments, fn query -\u003e\n    join(query, :inner, [users], assoc(users, :department), as: :departments)\n  end)\n  |\u003e Composite.dependency(\n    :companies,\n    fn query -\u003e\n      join(query, :inner, [departments: departments], assoc(departments, :company), as: :companies)\n    end,\n    requires: :departments\n  )\n  |\u003e MyApp.Repo.all()\nend\n```\n\nLet's move anonymous functions to named functions, so it doesn't look so scary:\n```elixir\ndef list_users(params) do\n  MyApp.User\n  |\u003e where(active: true)\n  |\u003e Composite.new(params)\n  |\u003e Composite.param(:name, \u0026where(\u00261, name: ^\u00262))\n  |\u003e Composite.param([:company, :name], \u0026filter_users_by_company_name/2, requires: :companies)\n  |\u003e Composite.param(:locations, \u0026filter_users_by_department_locations/2, requires: :departments, ignore?: \u0026(\u00261 in [nil, []] or \"Worldwide\" in \u00261))\n  |\u003e Composite.param(:order, \u0026order_users/2, requires: \u0026if(\u00261 == :department_name_asc, do: :departments))\n  |\u003e Composite.dependency(:departments, \u0026join_departments/1)\n  |\u003e Composite.dependency(:companies, \u0026join_companies/1, requires: :departments)\n  |\u003e MyApp.Repo.all()\nend\n```\nYes, it looks like a router.\nThe example above starts with Ecto query. \nThe query is wrapped into `Composite` struct by calling `Composite.new/2`. It will be unwrapped automatically during\n`MyApp.Repo.all/1` call, as `Composite` implements `Ecto.Queryable` protocol.\nParameter handlers are defined using `Composite.param/3` and `Composite.param/4`. These instructions define\nhow the query should be modified if given parameters are present. By default parameters are ignored if they have\none of the following values: `nil`, `\"\"`, `[]`, `%{}`. However, this behaviour can be adjusted by using `:ignore?` option.\n\nParameter keys are not limited to atoms only, they can have any type. Lists have special meaning: if list is specified,\nthen this is a path to nested structure.\nHere is an example with all parameters for our `list_users/1` function:\n```elixir\n# all keys are optional\n%{name: \"John\", company: %{name: \"GitHub\"}, order: :department_name_asc, locations: [\"USA\"]}\n```\n\nAs you may noticed, there is a concept of dependencies.\nDependencies can be declared with `Composite.dependency/3` and `Composite.dependency/4` functions by specifying loader function.\nThe loader function is invoked before invoking parameter handler.\nDependencies can have other dependencies as well. Dependencies are loaded only when they're needed and only once.\nSo, if multiple parameter handlers require the same table to be joined to the query, it will be joined only once without any errors.\n\n## Links\n\n* Article: [Writing dynamic Ecto queries with Composite](https://dev.to/arturplysiuk/writing-dynamic-ecto-queries-with-composite-26g4)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffuelen%2Fcomposite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffuelen%2Fcomposite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffuelen%2Fcomposite/lists"}