Aurora CTX - A DSL to reduce code boilerplate for context / Ecto schemas

Aurora.Ctx is a code generation library that implements the Context pattern in Phoenix applications.

It automatically generates standardized database interaction functions following Phoenix’s architectural guidelines, while maintaining full compatibility with Ecto’s query composition and changesets.

The library supports both standard CRUD operations and advanced query features like filtering, sorting, and pagination.

See the documentation in hex

6 Likes

Nice. I like the def new_{resource} function. I’ll add it to ContextKit as well, before I was using plain structs to init them. How does it work in your library? Does it go through the Ecto.Changeset or you give the params straight to the struct?

Thanks

It goes thru the changeset, by default to function :changeset, but the name of the changeset to use can be passed as an option.

I never understood the use-case for these libraries, but I’ve seen a lot of them in my career.

First of all, it is not saving any typing, essentially providing oneliners replacing other onelines

list_products()                    # Repo.all(Products)
list_products(opts)                # Repo.all(where(Products, ...))
list_products_paginated()          # Repo.paginate(Products)
list_products_paginated(opts)      # Repo.paginate(where(Products))
count_products()                   # Repo.aggregate(Products, :count)
count_products(opts)               # Repo.aggregate(where(Products, ...), :count)

# Create operations
create_product(%{name: "Item"})    # Repo.insert(Product.changeset(...))
create_product!(%{name: "Item"})   # Repo.insert!(Product.changeset(...))
create_product()                   # Repo.insert(%Product{})
create_product!()                  # Repo.insert!(%Product{})

# Read operations
get_product(1)                     # Repo.get(Product, 1)
get_product!(1)                    # Repo.fetch!(Product, 1)
get_product(1, preload: [:items])  # Repo.get(Product, 1) |> Repo.preload()
get_product!(1, preload: [:items]) # Repo.fetch!(Product, 1) |> Repo.preload()

# Update operations
update_product(product, attrs)     # Repo.update(Product.changeset(product, attrs))
update_product(product)            # Update without new attributes (??????? why would anyone needs this)
change_product(product, attrs)     # Product.changeset(product, attrs)
change_product(product)            # Product.changeset(product, attrs)

# Delete operations
delete_product(product)            # Product.delete(product)
delete_product!(product)           # Product.delete!(product)

# Initialize operations
new_product()                      # %Product{}
new_product(attrs)                 # struct(Product, attrs)
new_product(attrs, opts)           # ??????

Second, it makes it impossible to search for definition. When I will go to Product module, I won’t be able to find what the list_products function does, because I can’t see it

Third, every time I used this approach, it quickly became unmaintainable, because CRUD operations which just do plain CRUD are extremely rare, and most of them usually contain some business logic which is reflected in query (like, for example, product ownership checks, soft-deletion checks, category checks, normalized form preloading, etc.)

So every time I used one of these libraries, they became a huge frustration for me and other devs

3 Likes

Cool! Good that people are trying things in this area. Have you compared your work to Ash? conceptually it seems like where Ash began?

Is the idea to keep the generated code and commit it, and then modify it or keep regenerating it and never deal with the finished module..

Asd

The purpose of this library (as clearly stated) is to reduce the amount of lines of code.

About the usefulness of it, depends on the level of maturity that this library reaches.

For example, with this library (this is the first version) you’ll get controlled pagination, preloading, filtering and sorting with just one line of code.

Of course, you might have use cases when the use of AuroraCtx is not worth it, and that is ok.

Lawik

I think this is different, Ash provides a DSL framework for all aspects of coding. There are new elements (like records) and several abstractions (like actions).

AuroraCtx is a simple DSL to produce common functions for working with Repos and Schemas. How is that useful? When you work with several schemas you end up repeating similar code for each of them, with AuroraCtx you can avoid that and focus on your business logic.

Huh. So same set of context functions for a few different schema definitions? I mean, I’ve seen it but I’m not sure it is something I would bring in a library to deduplicate.

When have you had the need? :slight_smile:

Lawik

These context functions are to be used, for example, in Phoenix applications. In that way, the view (Phoenix) does not have to know about how the data is accessed (Repo).

That is one intended use case.

1 Like