ContextKit - automatic CRUD operations in your context and more

ContextKit is a modular toolkit for building robust Phoenix/Ecto contexts with standardized CRUD operations. It helps reduce boilerplate code while providing powerful querying capabilities and built-in pagination support.

Features

  • :rocket: Automatic CRUD operation generation
  • :mag: Dynamic query building with extensive filtering options
  • :page_facing_up: Built-in pagination support
  • :wrench: Flexible and extensible design
  • :dart: Custom query options for complex filtering

Description

I always end up customising the generated context functions via mix phx.gen.... Typically I want to query records via the list_{resource} function, paginate records and more. With ContextKit I generate all of that.

Examples

Let’s say, we have a schema MyApp.Accounts.User. To get the auto-generated functions, you would need to add a small snippet to your context module:

defmodule MyApp.Accounts do
  use ContextKit.CRUD,
    repo: MyApp.Repo,
    schema: MyApp.Accounts.User,
    queries: __MODULE__
end

This generates some functions in your module, and you can do things like:

# List all users
Accounts.list_users()

# List with filters and pagination
{users, pagination} = Accounts.list_users(
  status: "active",
  paginate: [page: 1, per_page: 20]
)

# Get single user
user = Accounts.get_user(123)
user = Accounts.get_user!(123)  # Raises if not found

# Get one user by criteria
user = Accounts.one_user(email: "user@example.com")

# Create a new user
MyApp.Accounts.create_user(%{email: "new@example.com"})

# Update a user
MyApp.Accounts.update_user(user, %{email: "updated@example.com"})

# Get a changeset for updates
MyApp.Accounts.change_user(user, %{email: "changed@example.com"})

# Delete user
Accounts.delete_user(user)
Accounts.delete_user(email: "user@example.com")

All the fields in the schema can be queried on automatically. There are many operators for advanced queries.

Accounts.list_users(
  filters: [
    %{field: :email, op: :ilike, value: "@gmail.com"},
    %{field: :status, op: :in, value: ["active", "pending"]},
    %{field: :name, op: :like_or, value: ["john", "jane"]}
  ]
)

Additionally you can define any custom query:

defmodule MyApp.Accounts do
  def apply_query_option({:with_active_posts, true}, query) do
    query
    |> join(:inner, [u], p in assoc(u, :posts))
    |> where([_, p], p.status == "active")
  end
end

and then you can do: MyApp.Accounts.list_users(with_active_posts: true)

You can also define query functions in a different module. Then just pass it instead of queries: __MODULE__.

Links

10 Likes

Released version 0.3.0

The main feature is the support of Phoenix 1.8 scopes.

Use the new ContextKit.CRUD.Scoped in your context.

defmodule MyApp.Blog do
  use ContextKit.CRUD.Scoped,
    repo: MyApp.Repo,
    schema: MyApp.Blog.Comment,
    queries: MyApp.Blog.CommentQueries,
    pubsub: MyApp.PubSub,                 # For realtime notifications via PubSub
    scope: Application.compile_env(:my_app, :scopes)[:user] # To gain support for Phoenix 1.8 scopes.
  end

Now, besides all the functions from before, you will get all functions with the scope as first argument.

# List all comments
MyApp.Blog.list_comments()

# List all comments belonging to current user
MyApp.Blog.list_comments(socket.assigns.current_scope)
1 Like

What do you use on top of that to ensure that only the fields that can and should be allowed to be updated will be updated by the user? Let’s say you have User schema, with is_admin: boolean field. The generated code will allow updating that field. Do you build something on top of that that casts and validates only allowed fields/values before you pass params to your context CRUD functions?

It still calls the User.changeset function, so one way would be to not cast :is_admin in this changeset.

For the use-case when you do want to update the :is_admin field, you would need to create your own update_user_admin function.

This is how update_#{resource} function is generated context_kit/lib/context_kit/crud/scoped.ex at main Β· egze/context_kit Β· GitHub

2 Likes