Aurora.Ctx.Core (Aurora.Ctx v0.1.9)

View Source

Provides comprehensive database operations using Ecto with advanced query building, pagination, and CRUD functionality.

This module serves as the core interface for database operations, offering:

  • CRUD Operations: Create, read, update, and delete records with flexible changeset support
  • Advanced Pagination: Navigate through large datasets with configurable page sizes and safe boundary handling
  • Query Building: Filter, sort, and preload associations using Aurora.Ctx.QueryBuilder options
  • Record Management: Initialize new records and build changesets with custom functions
  • Query Utilities: Count records and exclude specific query clauses

Configuration

Default pagination settings can be configured in your application:

config :aurora_ctx, :paginate,
  page: 1,
  per_page: 40

Query Options

Most functions accept query options that are processed by Aurora.Ctx.QueryBuilder:

  • where: keyword() - Filter conditions
  • order_by: keyword() - Sorting specifications
  • preload: atom() | list() - Associations to preload

  • paginate: map() - Pagination parameters
  • select: list() - List of fields to load

Examples

alias MyApp.{Repo, Product, Category}

# List all products with filtering and preloading
products = Core.list(Repo, Product,
  where: [status: :active],
  order_by: [desc: :inserted_at],
  preload: [:category]
)

# Paginated listing with navigation
page1 = Core.list_paginated(Repo, Product,
  paginate: %{page: 1, per_page: 20},
  where: [category_id: 1]
)

page2 = Core.next_page(page1)

# CRUD operations
{:ok, product} = Core.create(Repo, Product, %{name: "Widget", price: 99.99})
{:ok, updated} = Core.update(Repo, product, %{price: 89.99})
{:ok, _deleted} = Core.delete(Repo, updated)

Summary

Functions

Creates a changeset for the given entity using the default changeset function.

Creates a changeset for the given entity using a specific changeset function.

Counts total records matching the specified query conditions.

Creates a new record using the default changeset function.

Creates a new record using the default changeset function, raising on errors.

Creates a new record with a custom changeset function, raising on errors.

Deletes the given entity from the database.

Deletes the given entity from the database, raising on errors.

Excludes specific query clauses from an Ecto query.

Gets a single record by its primary key.

Gets a single record by its primary key, raising if not found.

Gets a single record by filtering clauses.

Gets a single record by filtering clauses, raising if not found or multiple found.

Lists all records for a schema with optional filtering and sorting.

Lists records with pagination metadata.

Initializes a new schema struct with optional attributes.

Initializes a new schema struct with attributes and optional preloads.

Moves to the next page in paginated results.

Moves to the previous page in paginated results.

Navigates to a specific page in paginated results.

Updates an entity with given attributes using the default changeset function.

Functions

change(entity_or_changeset, attrs)

@spec change(Ecto.Schema.t() | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()

Creates a changeset for the given entity using the default changeset function.

Convenience function that calls change/3 with the default :changeset function and provided attributes.

Parameters

  • entity_or_changeset (Ecto.Schema.t() | Ecto.Changeset.t()) - Existing record or changeset to create changeset from

  • attrs (map()) - Changeset attributes

Returns

Ecto.Changeset.t() - Changeset with applied attributes and validations

Examples

product = %Product{name: "Widget"}
changeset = Core.change(product, %{name: "New Widget", price: 99.99})
#=> #Ecto.Changeset<changes: %{name: "New Widget", price: 99.99}>

change(entity_or_changeset, changeset_function \\ :changeset, attrs \\ %{})

@spec change(Ecto.Schema.t() | Ecto.Changeset.t(), atom() | function(), map()) ::
  Ecto.Changeset.t()

Creates a changeset for the given entity using a specific changeset function.

Provides flexibility to use custom changeset functions for specialized validation or transformation logic when building changesets.

Parameters

  • entity_or_changeset (Ecto.Schema.t() | Ecto.Changeset.t()) - Existing record or changeset to create changeset from

  • changeset_function (atom() | function()) - Changeset function to apply:

    • atom() - Function name assumed to exist in the schema module
    • function() - Direct function reference with arity 2
  • attrs (map()) - Changeset attributes

Returns

Ecto.Changeset.t() - Changeset with applied attributes and validations

Examples

product = %Product{name: "Widget"}
changeset = Core.change(product, :update_changeset, %{price: 89.99})
#=> #Ecto.Changeset<changes: %{price: 89.99}>

# Using function reference
custom_fn = &Product.custom_changeset/2
changeset = Core.change(product, custom_fn, %{status: :active})
#=> #Ecto.Changeset<changes: %{status: :active}>

count(repo_module, schema_module, opts \\ [])

@spec count(module(), module(), keyword()) :: non_neg_integer()

Counts total records matching the specified query conditions.

Efficiently counts records by excluding unnecessary query clauses like select, limit, offset, and order_by that don't affect the count result.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • schema_module (module()) - Schema module to query
  • opts (keyword()) - Query filtering options supported by Aurora.Ctx.QueryBuilder

Returns

non_neg_integer() - Total count of records matching the query conditions

Examples

# Count all products
Core.count(Repo, Product)
#=> 1250

# Count with filtering
Core.count(Repo, Product, where: [status: :active, category_id: 1])
#=> 45

create(repo_module, schema_module_or_changeset, attrs)

@spec create(module(), module() | Ecto.Changeset.t(), map()) ::
  {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}

Creates a new record using the default changeset function.

Convenience function that calls create/4 with the default :changeset function and provided attributes.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • schema_module_or_changeset (module() | Ecto.Changeset.t()) - Schema module or existing changeset to create from

  • attrs (map()) - Attributes for the new record

Returns

  • {:ok, Ecto.Schema.t()} - Successfully created record
  • {:error, Ecto.Changeset.t()} - Validation or constraint errors

Examples

# Create with schema module
Core.create(Repo, Product, %{name: "Widget", price: 99.99})
#=> {:ok, %Product{name: "Widget", price: 99.99}}

# Create with existing changeset
changeset = Product.changeset(%Product{}, %{name: "Gadget"})
Core.create(Repo, changeset, %{price: 49.99})
#=> {:ok, %Product{name: "Gadget", price: 49.99}}

create(repo_module, schema_module_or_changeset, changeset_function \\ :changeset, attrs \\ %{})

@spec create(module(), module() | Ecto.Changeset.t(), atom() | function(), map()) ::
  {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}

Creates a new record with a custom changeset function.

Provides flexibility to use custom changeset functions for specialized validation or transformation logic during record creation.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • schema_module (module() | Ecto.Changeset.t()) - Schema module or existing changeset to create from

  • changeset_function (atom() | function()) - Changeset function to apply:

    • atom() - Function name assumed to exist in the schema module
    • function() - Direct function reference with arity 2
  • attrs (map()) - Attributes for the new record

Returns

  • {:ok, Ecto.Schema.t()} - Successfully created record
  • {:error, Ecto.Changeset.t()} - Validation or constraint errors

Examples

# Using custom changeset function by name
Core.create(Repo, Product, :registration_changeset, %{
  name: "Widget",
  code: "WDG001"
})
#=> {:ok, %Product{name: "Widget", code: "WDG001"}}

# Using function reference
custom_fn = &Product.custom_changeset/2
Core.create(Repo, Product, custom_fn, %{name: "Gadget"})
#=> {:ok, %Product{name: "Gadget"}}

create!(repo_module, schema_module_or_changeset, attrs)

@spec create!(module(), module() | Ecto.Changeset.t(), map()) :: Ecto.Schema.t()

Creates a new record using the default changeset function, raising on errors.

Convenience function that calls create!/4 with the default :changeset function and provided attributes.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • schema_module_or_changeset (module() | Ecto.Changeset.t()) - Schema module or existing changeset to create from

  • attrs (map()) - Attributes for the new record

Returns

Ecto.Schema.t() - Successfully created record

Raises

Examples

product = Core.create!(Repo, Product, %{name: "Widget", price: 99.99})
#=> %Product{name: "Widget", price: 99.99}

create!(repo_module, schema_module_or_changeset, changeset_function \\ :changeset, attrs \\ %{})

@spec create!(module(), module() | Ecto.Changeset.t(), atom() | function(), map()) ::
  Ecto.Schema.t()

Creates a new record with a custom changeset function, raising on errors.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • schema_module_or_changeset (module() | Ecto.Changeset.t()) - Schema module or existing changeset to create from

  • changeset_function (atom() | function()) - Changeset function to apply:

    • atom() - Function name assumed to exist in the schema module
    • function() - Direct function reference with arity 2
  • attrs (map()) - Attributes for the new record

Returns

Ecto.Schema.t() - Successfully created record

Raises

Examples

product = Core.create!(Repo, Product, :registration_changeset, %{
  name: "Widget",
  code: "WDG001"
})
#=> %Product{name: "Widget", code: "WDG001"}

delete(repo_module, entity)

@spec delete(module(), Ecto.Schema.t()) ::
  {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}

Deletes the given entity from the database.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • entity (Ecto.Schema.t()) - Entity struct to delete

Returns

  • {:ok, Ecto.Schema.t()} - Successfully deleted entity
  • {:error, Ecto.Changeset.t()} - Delete operation failed

Examples

product = Core.get!(Repo, Product, 123)
{:ok, deleted_product} = Core.delete(Repo, product)
#=> {:ok, %Product{id: 123, ...}}

delete!(repo_module, entity)

@spec delete!(module(), Ecto.Schema.t()) :: Ecto.Schema.t()

Deletes the given entity from the database, raising on errors.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • entity (Ecto.Schema.t()) - Entity struct to delete

Returns

Ecto.Schema.t() - Successfully deleted entity

Raises

Ecto.StaleEntryError - Entity has been modified or deleted by another process

Examples

product = Core.get!(Repo, Product, 123)
deleted_product = Core.delete!(Repo, product)
#=> %Product{id: 123, ...}

exclude_clauses(query, clauses)

@spec exclude_clauses(Ecto.Query.t(), atom() | [atom()]) :: Ecto.Query.t()

Excludes specific query clauses from an Ecto query.

Removes unwanted clauses from a query, which is useful for operations like counting where certain clauses (select, limit, offset, order_by) are not needed and may interfere with the operation.

Parameters

  • query (Ecto.Query.t()) - Query to modify by excluding clauses
  • clauses (atom() | list(atom())) - Clause(s) to exclude. Available clauses:

    • :where - WHERE conditions
    • :select - SELECT clauses
    • :order_by - ORDER BY clauses
    • :group_by - GROUP BY clauses
    • :having - HAVING conditions
    • :limit - LIMIT clause
    • :offset - OFFSET clause
    • :preload - Preload associations
    • :lock - Lock clauses

Returns

Ecto.Query.t() - Modified query with specified clauses excluded

Examples

query = from(p in Product, where: p.status == :active, order_by: p.name)

# Exclude single clause
exclude_clauses(query, :order_by)
#=> query without ORDER BY clause

# Exclude multiple clauses
exclude_clauses(query, [:select, :order_by, :limit])
#=> query without SELECT, ORDER BY, or LIMIT clauses

get(repo_module, schema_module, id, opts \\ [])

@spec get(module(), module(), term(), keyword()) :: Ecto.Schema.t() | nil

Gets a single record by its primary key.

Supports additional query options for preloading associations and other query modifications processed by Aurora.Ctx.QueryBuilder.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • schema_module (module()) - Schema module to query
  • id (term()) - Primary key value to look up
  • opts (keyword()) - Query options:

Returns

  • Ecto.Schema.t() - Found record
  • nil - No record found with the given primary key

Examples

# Get by ID
Core.get(Repo, Product, 123)
#=> %Product{id: 123, name: "Widget"}

# Get with preloaded associations
Core.get(Repo, Product, 123, preload: [:category, :reviews])
#=> %Product{id: 123, category: %Category{}, reviews: [%Review{}]}

get!(repo_module, schema_module, id, opts \\ [])

@spec get!(module(), module(), term(), keyword()) :: Ecto.Schema.t()

Gets a single record by its primary key, raising if not found.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • schema_module (module()) - Schema module to query
  • id (term()) - Primary key value to look up
  • opts (keyword()) - Query options supported by Aurora.Ctx.QueryBuilder

Returns

Ecto.Schema.t() - Found record

Raises

Ecto.NoResultsError - No record found with the given primary key

Examples

product = Core.get!(Repo, Product, 123)
#=> %Product{id: 123, name: "Widget"}

Core.get!(Repo, Product, 999)
#=> ** (Ecto.NoResultsError)

get_by(repo_module, schema_module, clauses, opts \\ [])

@spec get_by(module(), module(), keyword(), keyword()) :: Ecto.Schema.t() | nil

Gets a single record by filtering clauses.

Finds the first record matching the given clauses. If multiple records match, only the first one is returned. Use get_by!/4 for strict single-result enforcement.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • schema_module (module()) - Schema module to query
  • clauses (keyword()) - Filter conditions as key-value pairs
  • opts (keyword()) - Query options supported by Aurora.Ctx.QueryBuilder

Returns

  • Ecto.Schema.t() - First matching record
  • nil - No record matches the given clauses

Examples

# Get by single field
Core.get_by(Repo, Product, name: "Widget")
#=> %Product{name: "Widget"}

# Get by multiple fields
Core.get_by(Repo, Product, [name: "Widget", status: :active])
#=> %Product{name: "Widget", status: :active}

# With preloading
Core.get_by(Repo, Product, [code: "WDG001"], preload: [:category])
#=> %Product{code: "WDG001", category: %Category{}}

get_by!(repo_module, schema_module, clauses, opts \\ [])

@spec get_by!(module(), module(), keyword(), keyword()) :: Ecto.Schema.t()

Gets a single record by filtering clauses, raising if not found or multiple found.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • schema_module (module()) - Schema module to query
  • clauses (keyword()) - Filter conditions as key-value pairs
  • opts (keyword()) - Query options supported by Aurora.Ctx.QueryBuilder

Returns

Ecto.Schema.t() - The unique matching record

Raises

Examples

product = Core.get_by!(Repo, Product, code: "WDG001")
#=> %Product{code: "WDG001"}

Core.get_by!(Repo, Product, name: "NonExistent")
#=> ** (Ecto.NoResultsError)

list(repo_module, schema_module, opts \\ [])

@spec list(module(), module(), keyword()) :: [Ecto.Schema.t()]

Lists all records for a schema with optional filtering and sorting.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • schema_module (module()) - Schema module to query
  • opts (keyword()) - Query options processed by Aurora.Ctx.QueryBuilder:
    • where: keyword() - Filter conditions
    • order_by: keyword() - Sorting specifications
    • preload: atom() | list() - Associations to preload

    • select: list() - List of fields to load

Returns

list(Ecto.Schema.t()) - List of records as schema structs matching the query conditions

Examples

alias MyApp.{Repo, Product}

# List all products
Core.list(Repo, Product)
#=> [%Product{}, ...]

# List with filtering, sorting, and preloading
Core.list(Repo, Product,
  where: [status: :active],
  order_by: [desc: :inserted_at],
  preload: [:category]
)
#=> [%Product{category: %Category{}}, ...]

list_paginated(repo_module, schema_module, opts \\ [])

@spec list_paginated(module(), module(), keyword()) :: Aurora.Ctx.Pagination.t()

Lists records with pagination metadata.

Returns a Pagination struct containing the current page entries along with metadata for browsing. Page boundaries are safely handled - attempting to navigate beyond valid pages returns the current page with its entries refreshed.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • schema_module (module()) - Schema module to query
  • opts (keyword()) - Query and pagination options:
    • paginate: map() - Pagination configuration with page and per_page keys
    • Plus all options supported by Aurora.Ctx.QueryBuilder

Returns

Aurora.Ctx.Pagination.t() - Pagination struct, see Aurora.Ctx.Pagination

Examples

alias MyApp.{Repo, Product}

# Basic pagination
page1 = Core.list_paginated(Repo, Product,
  paginate: %{page: 1, per_page: 20}
)
#=> %Aurora.Ctx.Pagination{
#=>   entries: [%Product{}],
#=>   page: 1,
#=>   per_page: 20,
#=>   entries_count: 150,
#=>   pages_count: 8
#=> }

# With filtering and sorting
Core.list_paginated(Repo, Product,
  paginate: %{page: 2, per_page: 10},
  where: [category_id: 1],
  order_by: [desc: :inserted_at]
)

new(repo_module, schema_module, attrs)

@spec new(module(), module(), map() | keyword()) :: Ecto.Schema.t()

Initializes a new schema struct with optional attributes.

Convenience function that calls new/4 with empty options when provided with a map of attributes, or calls new/4 with empty attributes when provided with a keyword list of options.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for potential preloading
  • schema_module (module()) - Schema module to initialize
  • attrs_or_opts - Either:
    • map() - Initial attributes (will use empty options)
    • keyword() - Options including preload specifications (will use empty attributes)

Returns

Ecto.Schema.t() - Initialized schema struct

Examples

# Initialize with attributes
product = Core.new(Repo, Product, %{name: "Widget", price: 99.99})
#=> %Product{name: "Widget", price: 99.99}

# Initialize with preload options
product = Core.new(Repo, Product, preload: [:category])
#=> %Product{category: %Category{}}

new(repo_module, schema_module, attrs \\ %{}, opts \\ [])

@spec new(module(), module(), map(), keyword()) :: Ecto.Schema.t()

Initializes a new schema struct with attributes and optional preloads.

Creates a new struct instance and optionally preloads specified associations. Useful for preparing new records with related data for forms or other operations.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for preloading operations
  • schema_module (module()) - Schema module to initialize
  • attrs (map()) - Initial attributes to set on the struct
  • opts (keyword()) - Options:
    • preload: atom() | list() - Associations to preload

Returns

Ecto.Schema.t() - Initialized schema struct with preloaded associations

Examples

# Basic initialization
product = Core.new(Repo, Product, %{name: "Widget"}, [])
#=> %Product{name: "Widget"}

# With preloaded associations
product = Core.new(Repo, Product, %{name: "Widget"}, preload: [:category])
#=> %Product{name: "Widget", category: %Category{}}

next_page(paginate)

Moves to the next page in paginated results.

Safely advances to the next page if available. If already on the last page, returns the current pagination state with its entries refreshed.

Parameters

  • paginate (Aurora.Ctx.Pagination.t()) - Current pagination state

Returns

Aurora.Ctx.Pagination.t() - Pagination struct for the next page or current page if already at the end

Examples

page1 = Core.list_paginated(Repo, Product, paginate: %{page: 1, per_page: 20})
page2 = Core.next_page(page1)
#=> %Aurora.Ctx.Pagination{page: 2, entries: [...]}

previous_page(paginate)

@spec previous_page(Aurora.Ctx.Pagination.t()) :: Aurora.Ctx.Pagination.t()

Moves to the previous page in paginated results.

Safely moves back to the previous page if available. If already on the first page, returns the current pagination state with its entries refreshed.

Parameters

  • paginate (Aurora.Ctx.Pagination.t()) - Current pagination state

Returns

Aurora.Ctx.Pagination.t() - Pagination struct for the previous page or current page refreshed if already at the beginning

Examples

page3 = Core.list_paginated(Repo, Product, paginate: %{page: 3, per_page: 20})
page2 = Core.previous_page(page3)
#=> %Aurora.Ctx.Pagination{page: 2, entries: [...]}

to_page(paginate, page)

Navigates to a specific page in paginated results.

Provides safe navigation by validating the target page is within valid bounds. If the requested page is out of range (< 1 or > pages_count), returns the current pagination state with its entries updated.

Parameters

  • paginate (Aurora.Ctx.Pagination.t()) - Current pagination state containing repo module, schema, and query options
  • page (integer()) - Target page number to navigate to

Returns

Aurora.Ctx.Pagination.t() - Updated pagination struct with entries for the target page

Examples

paginate = Core.list_paginated(Repo, Product, paginate: %{per_page: 20})

# Navigate to page 5
page5 = Core.to_page(paginate, 5)
#=> %Aurora.Ctx.Pagination{page: 5, entries: [...]}

# Out of range navigation returns current page with its entries updated
same_page = Core.to_page(paginate, 999)
#=> %Aurora.Ctx.Pagination{page: 1, entries: [...]} (unchanged)

update(repo_module, entity_or_changeset, attrs)

@spec update(module(), Ecto.Schema.t() | Ecto.Changeset.t(), map()) ::
  {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}

Updates an entity with given attributes using the default changeset function.

Convenience function that calls update/4 with the default :changeset function and provided attributes.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • entity_or_changeset (Ecto.Schema.t() | Ecto.Changeset.t()) - Existing record to update or changeset to apply

  • attrs (map()) - Update attributes

Returns

  • {:ok, Ecto.Schema.t()} - Successfully updated record
  • {:error, Ecto.Changeset.t()} - Validation or constraint errors

Examples

product = Core.get!(Repo, Product, 123)
{:ok, updated} = Core.update(Repo, product, %{name: "New Name", price: 89.99})
#=> {:ok, %Product{name: "New Name", price: 89.99}}

update(repo_module, entity_or_changeset, changeset_function \\ :changeset, attrs \\ %{})

@spec update(
  module(),
  Ecto.Schema.t() | Ecto.Changeset.t(),
  atom() | function(),
  map()
) ::
  {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}

Updates an entity using a specific changeset function.

Provides flexibility to use custom changeset functions for specialized validation or transformation logic during record updates.

Parameters

  • repo_module (module()) - Ecto.Repo module to use for database operations
  • entity_or_changeset (Ecto.Schema.t() | Ecto.Changeset.t()) - Existing record to update or changeset to apply

  • changeset_function (atom() | function()) - Changeset function to apply:

    • atom() - Function name assumed to exist in the schema module
    • function() - Direct function reference with arity 2
  • attrs (map()) - Update attributes

Returns

  • {:ok, Ecto.Schema.t()} - Successfully updated record
  • {:error, Ecto.Changeset.t()} - Validation or constraint errors

Examples

product = Core.get!(Repo, Product, 123)
{:ok, updated} = Core.update(Repo, product, :update_changeset, %{
  price: 79.99,
  updated_reason: "Price adjustment"
})
#=> {:ok, %Product{price: 79.99, updated_reason: "Price adjustment"}}