{"id":32166520,"url":"https://github.com/leanpanda-com/phoenix_bricks","last_synced_at":"2026-05-28T01:31:19.951Z","repository":{"id":57531790,"uuid":"340785214","full_name":"leanpanda-com/phoenix_bricks","owner":"leanpanda-com","description":null,"archived":false,"fork":false,"pushed_at":"2022-09-16T23:25:31.000Z","size":60,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-05-08T05:08:02.860Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/leanpanda-com.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-02-21T00:37:43.000Z","updated_at":"2022-08-08T18:38:14.000Z","dependencies_parsed_at":"2022-09-06T23:11:24.797Z","dependency_job_id":null,"html_url":"https://github.com/leanpanda-com/phoenix_bricks","commit_stats":null,"previous_names":["davidlibrera/phoenix_bricks"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/leanpanda-com/phoenix_bricks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanpanda-com%2Fphoenix_bricks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanpanda-com%2Fphoenix_bricks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanpanda-com%2Fphoenix_bricks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanpanda-com%2Fphoenix_bricks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leanpanda-com","download_url":"https://codeload.github.com/leanpanda-com/phoenix_bricks/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanpanda-com%2Fphoenix_bricks/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33590884,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"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":[],"created_at":"2025-10-21T15:06:39.440Z","updated_at":"2026-05-28T01:31:19.933Z","avatar_url":"https://github.com/leanpanda-com.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PhoenixBricks [![Circle CI](https://circleci.com/gh/leanpanda-com/phoenix_bricks/tree/master.svg?style=svg)](https://circleci.com/gh/leanpanda-com/phoenix_bricks/tree/master) [![Coverage Status](https://coveralls.io/repos/github/leanpanda-com/phoenix_bricks/badge.png?branch=master)](https://coveralls.io/github/leanpanda-com/phoenix_bricks?branch=master)\nA set of proposed patterns to improve code organization for [Phoenix](https://phoenixframework.org) application\n\n## Installation\n\nTo install PhoenixBricks, add it to your list of dependencies in `mix.exs`.\n\n```elixir\ndef deps do\n  [\n    {:phoenix_bricks, \"~\u003e 0.3.0\"}\n  ]\nend\n```\n\nOnce you've added PhoenixBricks to your list, update your dependencies by running:\n\n```bash\n$ mix deps.get\n```\n\n## Getting started\n**PhoenixBricks** it is intented as a set of proposed pattern to organize code into a Phoenix application in a modular way.\n\n### Queries\nA query is a module that receives a list of atom/keywords and returns an Ecto.Query improved with provided scopes\n\n```elixir\n# lib/phoenix_bricks/catalogue/product_query.ex\ndefmodule PhoenixBricks.Catalogue.ProductQuery do\n  use PhoenixBricks.Query\nend\n\n[title_matches: \"a value\", price_is_greater_than: 1000]\n|\u003e ProductQuery.scope()\n=\u003e #Ecto.Query\u003cfrom p0 in PhoenixBricks.Catalogue.Product,\n where: ilike(p0.title, ^\"%a value%\"), where: p0.price \u003e= ^1000\n```\n\nThe idea is to have a query composer that could be easy to use like [ActiveRecord](https://github.com/rails/rails/tree/main/activerecord).\n\n\nTo generate a query for the `Catalogue.Product` schema with some additional custom scopes, simply run\n```elixir\n$ mix phx.bricks.gen.query Catalogue.Product name:matches:string price:lte:integer\n```\n\nThe generated module will contain a set of default scopes for simple column matchers and the additional scopes provided in the command.\n\n```elixir\ndefmodule PhoenixBricks.Catalogue.ProductQuery do\n  @moduledoc false\n\n  import Ecto.Query, warn: false\n\n  def starting_scope do\n    PhoenixBricks.Catalogue.Product\n  end\n\n  def scope(scopes, starting_scope) do\n    scopes\n    |\u003e Enum.reduce(starting_scope, fn scope, query -\u003e\n      apply_scope(query, scope)\n    end)\n  end\n\n  def scope(scopes) do\n    scope(scopes, starting_scope())\n  end\n\n  def scope do\n    starting_scope()\n  end\n\n  def apply_scope(query, {column, {:eq, value}}) do\n    where(query, [q], field(q, ^column) == ^value)\n  end\n\n  def apply_scope(query, {column, {:neq, value}}) do\n    where(query, [q], field(q, ^column) != ^value)\n  end\n\n  def apply_scope(query, {column, {:lte, value}}) do\n    where(query, [q], field(q, ^column) \u003c= ^value)\n  end\n\n  def apply_scope(query, {column, {:lt, value}}) do\n    where(query, [q], field(q, ^column) \u003c ^value)\n  end\n\n  def apply_scope(query, {column, {:gte, value}}) do\n    where(query, [q], field(q, ^column) \u003e= ^value)\n  end\n\n  def apply_scope(query, {column, {:gt, value}}) do\n    where(query, [q], field(q, ^column) \u003e ^value)\n  end\n\n  def apply_scope(query, {column, {:matches, value}}) do\n    value = \"%#{value}%\"\n    where(query, [q], ilike(field(q, ^column), ^value))\n  end\n\n  def apply_scope(query, {:title_matches, value}) do\n    apply_scope(query, {:title, {:matches, value}})\n  end\n\n  def apply_scope(query, {:price_gte, value}) do\n    apply_scope(query, {:price, {:gte, value}})\n  end\nend\n```\n\nUsing the module `ProductQuery` it's possible to rewrite the `list_products/0` method of the context this way:\n```elixir\ndefmodule PhoenixBricks.Catalogue do\n  ...\n  def list_products(scopes \\\\ []) do\n    ProductQuery.scope(scopes)\n    |\u003e Repo.all()\n  end\n  ...\nend\n\n[title_matches: \"a value\"]\n|\u003e Catalogue.list_products()\n```\n\nIf you want to add custom scopes you simply have to add new definitions of the `apply_scope/2` method:\n```elixir\ndef apply_scope(query, :active) do\n  from(q in query, where: q.active == true)\nend\n\ndef apply_scope(query, {:some_scope_name, value}) do\n  from(query in q, ...)\nend\n...\nCatalogue.list_products([\n  :active,\n  title_matches: \"a value\",\n  some_scope_name: \"some value\"\n])\n```\n\n### Filters\nA filter is a module that receives a map of filters (for instance coming from a search form) and returns a list of allowed filters.\n\n```elixir\n%{\"filters\" =\u003e %{\"title_matches\" =\u003e \"a value\", \"not_allowed\" =\u003e \"xxx\"}}\n|\u003e ProductFilter.params_to_scopes()\n=\u003e [title_matches: \"a value\"]\n```\n\nTo generate a module filter for the schema `Catalogue.Product` you can run\n```elixir\n$ mix phx.bricks.gen.filters Catalogue.Product title:matches:string price:gte:integer\n```\n\nThe generated module is a schema that allow you to handle filters from a search form\n```elixir\ndefmodule CrudExample.Catalogue.ProductFilter do\n  @moduledoc false\n\n  use Ecto.Schema\n\n  import Ecto.Changeset\n\n  alias CrudExample.Catalogue.ProductFilter\n\n  embedded_schema do\n    field :title_matches, :string\n    field :price_gte, :integer\n  end\n\n  def changeset(filter, attrs) do\n    filter\n    |\u003e cast(attrs, [:title_matches, :price_gte])\n  end\n\n  def params_to_scopes(params) do\n    filters = Map.get(params, \"filters\", %{})\n\n    filter_changeset = changeset(%ProductFilter{}, filters)\n\n    filter_changeset.changes\n    |\u003e Enum.map(fn {name, value} -\u003e {name, value} end)\n  end\nend\n```\n\n```elixir\n# lib/phoenix_bricks/catalogue.ex\ndef filter_products(params) do\n  ProductFilter.changeset(%ProductFilter{}, params)\nend\n\n\n# lib/phoenix_bricks_web/controllers/products_controller.ex\ndef index(conn, params) do\n  filter_changeset =\n    ProductFilter.changeset(%ProductFilter{}, params)\n\n  products =\n    params\n    |\u003e ProductFilter.params_to_scopes()\n    |\u003e ProductsQuery.scope()\n    |\u003e Repo.all()\n\n    # or\n    # params\n    # |\u003e ProductsFilter.params_to_scopes()\n    # |\u003e Products.list_products()\n\n  render(\n    conn,\n    \"index.html\",\n    products: products,\n    filter_changeset: filter_changeset\n  )\nend\n\n\n# lib/phoenix_bricks_web/templates/products/index.html.eex\n\u003c%= form_for @filter_changeset, Routes.product_index_path(@conn, :index) method: :get do %\u003e\n  \u003c%= label :title_matches %\u003e\n  \u003c%= text_input :title_matches %\u003e\n\u003c% end %\u003e\n\n\u003c%= for product \u003c- @products do %\u003e\n  ...\n\u003c% end %\u003e\n```\n\n### Query and Filters\nThe 2 modules can be used in pipeline since the output format for filters are the input format of queries.\n\n```elixir\n%{\"filters\" =\u003e %{\"title_matches\" =\u003e \"title\", \"price_gte\" =\u003e \"32\"}}\n|\u003e ProductFilter.params_to_scopes()\n|\u003e ProductQuery.scope()\n|\u003e Repo.all()\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleanpanda-com%2Fphoenix_bricks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleanpanda-com%2Fphoenix_bricks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleanpanda-com%2Fphoenix_bricks/lists"}