{"id":28505101,"url":"https://github.com/curiosum-dev/lemon_crud","last_synced_at":"2026-02-25T17:34:08.577Z","repository":{"id":286186541,"uuid":"960426790","full_name":"curiosum-dev/lemon_crud","owner":"curiosum-dev","description":"Create uniform yet flexible CRUD functions for your Phoenix contexts to reduce generated boilerplate.","archived":false,"fork":false,"pushed_at":"2025-04-07T08:10:10.000Z","size":37,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-14T21:49:46.837Z","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/curiosum-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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-04-04T12:16:47.000Z","updated_at":"2025-04-07T08:10:14.000Z","dependencies_parsed_at":"2025-04-04T21:22:24.396Z","dependency_job_id":"51c65b31-8dff-4224-9af1-365a4a264c1a","html_url":"https://github.com/curiosum-dev/lemon_crud","commit_stats":null,"previous_names":["curiosum-dev/lemon_crud"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/curiosum-dev/lemon_crud","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curiosum-dev%2Flemon_crud","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curiosum-dev%2Flemon_crud/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curiosum-dev%2Flemon_crud/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curiosum-dev%2Flemon_crud/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/curiosum-dev","download_url":"https://codeload.github.com/curiosum-dev/lemon_crud/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curiosum-dev%2Flemon_crud/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280783783,"owners_count":26390274,"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-10-24T02:00:06.418Z","response_time":73,"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-06-08T19:06:38.890Z","updated_at":"2025-10-27T10:02:30.374Z","avatar_url":"https://github.com/curiosum-dev.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LemonCrud\n\n[![Hex.pm](https://img.shields.io/hexpm/v/lemon_crud.svg)](https://hex.pm/packages/lemon_crud)\n[![CI](https://github.com/curiosum-dev/lemon_crud/actions/workflows/ci.yml/badge.svg)](https://github.com/curiosum-dev/lemon_crud/actions/workflows/ci.yml)\n[![Coverage Status](https://codecov.io/gh/curiosum-dev/lemon_crud/branch/main/graph/badge.svg)](https://codecov.io/gh/curiosum-dev/lemon_crud)\n[![License](https://img.shields.io/hexpm/l/lemon_crud.svg)](https://github.com/curiosum-dev/lemon_crud/blob/main/LICENSE)\n\nLemonCrud is a small library creating uniform yet flexible CRUD functions for your Phoenix contexts to reduce generated boilerplate.\n\nPaired with [Contexted](https://github.com/curiosum-dev/contexted), it adds a productivity benefit on top of Contexted's help in organizing Phoenix contexts in a nice and tidy way.\n\n## Installation\n\nThe package can be installed\nby adding `lemon_crud` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:lemon_crud, \"~\u003e 0.1.0\"}\n  ]\nend\n```\n\n## Usage\n\nIn most web apps CRUD operations are very common. Most of these, have the same pattern. Most of the time, they are used with preloading associated resources as well as filtering based on conditions such as search, pagination, etc.\n\nWhy not autogenerate them?\n\nHere is how you can generate common CRUD operations for `App.Account.Users`:\n\n```elixir\ndefmodule App.Account.Users do\n  use LemonCrud,\n    repo: App.Repo,\n    schema: App.Accounts.User\nend\n```\n\nThis will generate the following functions:\n\n```elixir\niex\u003e App.Accounts.Users.__info__(:functions)\n[\n  change_user: 1,\n  change_user: 2,\n  create_user: 0,\n  create_user: 1,\n  create_user!: 0,\n  create_user!: 1,\n  delete_user: 1,\n  delete_user!: 1,\n  get_user: 1,\n  get_user!: 1,\n  get_user_by: 1,\n  get_user_by!: 1,\n  get_user_by: 2,\n  get_user_by!: 2,\n  list_users: 0,\n  list_users: 1,\n  list_users: 2,\n  update_user: 1,\n  update_user: 2,\n  update_user!: 1,\n  update_user!: 2\n]\n```\n\nGenerated creation and updating functions default to the corresponding schema's `changeset/1` and `changeset/2` functions, respectively, whereas list and get functions provide a means to manipulate the result by:\n\n* filtering conditions (via plain exact match condition lists or by passing an Ecto.Query)\n* preloads\n* orderings\n* limits\n* offsets\n\nExamples:\n\n```elixir\n# List all users with posts preloaded\niex\u003e App.Accounts.Users.list_users(preload: [:posts])\n\n# Use an Ecto.Query to filter users, and a keyword list of options to manipulate the result\niex\u003e App.Accounts.Users.list_users(\n  App.Accounts.User |\u003e where([u], u.status == \"active\"),\n  preload: [:posts],\n  order_by: [desc: :inserted_at],\n  limit: 10,\n  offset: 0\n)\n\n# Use a keyword list of exact match conditions and manipulation options\niex\u003e App.Accounts.Users.list_users(\n  status: \"active\",\n  subscription: [plan: \"free\"],\n  order_by: [desc: :inserted_at]\n)\n\n# Get a user by ID with subscription preloaded\niex\u003e App.Accounts.Users.get_user!(10, preload: [:subscription])\n\n# Get a user by profile email with profile and subscription preloaded\niex\u003e App.Accounts.Users.get_user_by!(profile: [email: \"user@example.com\"], preload: [:profile, :subscription])\n\n# Use an Ecto.Query to get a specific user\niex\u003e App.Accounts.Users.get_user_by!(App.Accounts.User |\u003e where([u], u.id == 10), preload: [:profile, :subscription])\n```\n\n## Using Ecto Queries\n\nLemonCrud provides built-in support for using Ecto queries directly as arguments to the generated functions. This gives you more flexibility and power when you need more complex filtering than what the simple keyword list conditions can provide.\n\n### List Functions with Ecto Queries\n\nYou can pass an Ecto query as the first argument to any `list_*` function:\n\n```elixir\n# Basic query with a WHERE clause\niex\u003e ItemContext.list_items(from(i in Item, where: like(i.name, \"Item 1.2.%\")))\n\n# Combining a query with additional options.\n#\n# Note that the second dargument can still take options as described earlier -\n# they will be appended to the base queryable provided in the first argument.\niex\u003e ItemContext.list_items(\n  from(i in Item, where: like(i.name, \"Item 1.2.%\")),\n  limit: 1,\n  offset: 1,\n  order_by: [desc: :name]\n)\n\n# The schema module can also be used directly, as it's also a queryable.\niex\u003e ItemContext.list_items(Item, limit: 2, offset: 1, order_by: [desc: :name])\n```\n\n### Get Functions with Ecto Queries\n\nSimilarly, the `get_*_by` and `get_*_by!` functions can accept Ecto queries - behaving in line with their underlying calls to `Ecto.Repo`'s `get_by` and `get_by!` functions, respectively:\n\n```elixir\n# Find a record with a complex WHERE condition\niex\u003e ItemContext.get_item_by(\n  from(i in Item,\n    where: like(i.name, \"Item 1.1.%\") and i.serial_number == \"1234567890\"\n  )\n)\n\n# Using a query with preloads\niex\u003e CategoryContext.get_category_by(\n  from(c in Category, where: c.id == ^category_id),\n  preload: [:subcategories]\n)\n```\n\nThese query-based approaches are especially useful when:\n\n1. You need complex filtering logic (multiple conditions, OR clauses, etc.)\n2. You want to use SQL functions (like `like`, `in`, etc.)\n3. You need to join with tables that aren't directly accessible via associations\n4. You want to dynamically build queries based on user input\n\nA pattern we use and recommend is keeping contexts clear of bloat from query-building code, which is delegated to specialized query modules - see [our article on query module pattern](https://curiosum.com/blog/composable-elixir-ecto-queries-modules).\n\n## Query Options\n\nWhen using plain keyword lists for filtering, in addition to `preload`, LemonCrud provides several options for manipulating the constructed Ecto query covering most common use cases: `limit` and `offset` for pagination purposes, and `order_by` for sorting.\n\nAnother option is `count` that joins the base query with subqueries counting related records in a specific association.\n\nIt should be noted that, when more advanced query manipulations are needed, pre-constructed Ecto queries should be used instead of keyword lists in the arguments of the functions.\n\n### limit\n\nThe `limit` option restricts the number of results returned by the query. Under the hood, it applies `Ecto.Query.limit/2` to your query.\n\n```elixir\n# Get only the first item sorted by name in descending order\niex\u003e ItemContext.list_items(limit: 1, order_by: [desc: :name])\n\n# Apply limit combined with other options\niex\u003e ItemContext.list_items(\n  subcategory_id: subcategory.id,\n  preload: [subcategory: :category],\n  limit: 1,\n  offset: 1,\n  order_by: [desc: :name]\n)\n```\n\n### offset\n\nThe `offset` option skips a specific number of results before returning the rest. This is commonly used with `limit` for pagination. Under the hood, it applies `Ecto.Query.offset/2` to your query.\n\n```elixir\n# Skip the first item and get the next two, sorted by name in descending order\niex\u003e ItemContext.list_items(limit: 2, offset: 1, order_by: [desc: :name])\n\n# Can be used with Ecto.Query as well\niex\u003e ItemContext.list_items(\n  from(i in Item, where: like(i.name, \"Item 1.2.%\")),\n  limit: 1,\n  offset: 1,\n  order_by: [desc: :name]\n)\n```\n\n### order_by\n\nThe `order_by` option sorts the results based on specified fields and directions. Under the hood, it applies `Ecto.Query.order_by/3` to your query.\n\n```elixir\n# Sort items by name in descending order\niex\u003e ItemContext.list_items(order_by: [desc: :name])\n\n# Can be used with filtering conditions\niex\u003e CategoryContext.list_categories(\n  count: [:subcategories, :items],\n  order_by: [desc: :id]\n)\n```\n\n### count\n\nThe `count` option adds virtual fields to your results with counts of associated records. Under the hood, it adds left-joined subqueries to count the associations and adds the counts as values virtual fields named `[association_name]_count`.\n\nThese virtual fields need to be defined in the schema, preferably defaulting to nil.\n\n```elixir\n# Count associations for all categories\niex\u003e CategoryContext.list_categories(count: [:subcategories, :items])\n# Result example:\n# [\n#   %Category{id: 1, name: \"Category 1\", subcategories_count: 2, items_count: 4},\n#   %Category{id: 2, name: \"Category 2\", subcategories_count: 2, items_count: 4}\n# ]\n\n# Can be combined with other options\niex\u003e CategoryContext.list_categories(\n  id: category.id,\n  count: [:subcategories, :items],\n  order_by: [desc: :id]\n)\n\n# Works with get_by as well\niex\u003e CategoryContext.get_category_by(\n  name: \"Category 1\",\n  count: [:subcategories, :items],\n  preload: [:subcategories]\n)\n```\n\n## Contributing\n\nPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.\n\n## Further reading\n\nFull documentation can be found at \u003chttps://hexdocs.pm/lemon_crud\u003e.\n\n## License\n\n[Curiosum](https://curiosum.com)\n\nDistributed under the MIT License. See [LICENSE](https://github.com/curiosum-dev/lemon_crud/blob/main/LICENSE) for more information.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcuriosum-dev%2Flemon_crud","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcuriosum-dev%2Flemon_crud","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcuriosum-dev%2Flemon_crud/lists"}