Introduction

View Source

Drops.Relation is a high-level relation abstraction with extensible architecture built on top of Ecto.SQL, created to accelerate development with Ecto in both early stages and as applications grow and become more complex.

Faster development

When using Drops.Relation you make relations, aka data, a first-class citizen in your application. You are equiped with tools to define data structures that are specific to your application and work with it effectively, as all the boilerplate code is not needed, while preserving full control for when the time is right.

To improve and speed up development of Elixir applications, Drops.Relation provides the following benefits:

  • Schema inference - not only does Drops.Relation infer schemas and define underlying Ecto schemas automatically, but it also provides various meta-data about the fields that can be leveraged to build additional solutions
  • Streamlined Query API - because relations are 1st-class, top-level concepts, they hide details like the underlying Repos and database adapters.
  • Query composition - you can compose relations and their underlying Ecto queries with ease
  • Relation views - define custom views derived from base relations whenever the canonical representation is no longer a good fit in a given context
  • Batteries included - Drops.Relation provides a set of built-in plugins that cover most common use cases, you can also easily extend it with custom plugins to fit your specific needs

Architecture Overview

graph TD
    %% Main Relation Module - Larger and more prominent
    relationModule["<b>MyApp.Users</b>"]

    %% Schema Subgraph
    subgraph Schema ["Schema Management"]
        direction TB
        schema["<b>Drops.Relation.Schema</b><br/>id: :integer<br/>name: :string<br/>email: :string<br/>active: :boolean"]
        dbTable[("users table<br/>PostgreSQL/SQLite")]

        %% Cache subgraph with LR direction
        subgraph CacheSystem ["Cache System"]
            direction LR
            cache@{ shape: lin-cyl, label: "Schema Cache<br/>In-Memory" }
            cache-comment@{ shape: braces, label: "priv/repo/schemas<br/>Persistent Storage" }
            cache --> cache-comment
        end

        schema -.-> dbTable
        schema --> CacheSystem
    end

    %% Ecto Integration Subgraph
    subgraph EctoIntegration ["Ecto Integration"]
        direction TB
        ectoSchema["<b>MyApp.Users.User</b><br/>(Generated Ecto Schema)<br/>Struct definition"]
        queryable{{"<b>Ecto.Queryable</b><br/>MyApp.Users<br/>Query composition"}}
    end

    %% Protocols Subgraph
    subgraph Protocols ["Protocol Implementations"]
        direction TB
        enumerable{{"<b>Enumerable</b><br/>MyApp.Users<br/>Collection operations"}}
        loadable{{"<b>Drops.Relation.Loadable</b><br/>MyApp.Users<br/>Lazy loading support"}}
    end

    %% Connections
    relationModule --> Schema
    relationModule --> EctoIntegration
    relationModule --> Protocols

    %% Comment nodes for each subgraph
    schemaComment@{ shape: braces, label: "Handles database introspection<br/>and schema caching" }
    ectoComment@{ shape: braces, label: "Provides Ecto compatibility<br/>and query interface" }
    protocolComment@{ shape: braces, label: "Implements <br/>data loading protocols" }

    %% Comment connections
    Schema --- schemaComment
    EctoIntegration --- ectoComment
    Protocols --- protocolComment

    %% Enhanced Styling with larger fonts
    classDef relationModuleStyle fill:#e1f5fe,stroke:#01579b,stroke-width:4px,color:#000,font-size:46px,font-weight:bold
    classDef schemaStyle fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#000
    classDef ectoStyle fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px,color:#000
    classDef protocolStyle fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000
    classDef commentStyle fill:#f5f5f5,stroke:#757575,stroke-width:1px,font-size:24px,font-style:italic
    classDef cacheStyle fill:#f1f8e9,stroke:#33691e,stroke-width:2px,color:#000

    class relationModule relationModuleStyle
    class schema,dbTable schemaStyle
    class cache,cache-comment cacheStyle
    class ectoSchema,queryable ectoStyle
    class enumerable,loadable protocolStyle
    class schemaComment,ectoComment,protocolComment commentStyle

    %% Subgraph styling with better contrast
    style Schema fill:#f8f4ff,stroke:#673ab7,stroke-width:3px,color:#000
    style EctoIntegration fill:#f1f8e9,stroke:#4caf50,stroke-width:3px,color:#000
    style Protocols fill:#fff8e1,stroke:#ff9800,stroke-width:3px,color:#000
    style CacheSystem fill:#f9fff9,stroke:#2e7d32,stroke-width:2px,color:#000

Comparison with Ecto

Drops.Relation mimics some of the functions from Ecto.Repo to make things consistent and familiar. The goal is to provide a more convenient and composable API for working with databases while properly leveraging and encapsulating Ecto.Repo and Ecto.Query under the hood.

Schema Definition

Ecto

defmodule MyApp.User do
  use Ecto.Schema

  schema "users" do
    field :name, :string
    field :email, :string
    field :active, :boolean
    timestamps()
  end
end

Drops.Relation

defmodule MyApp.Users do
  use Drops.Relation, otp_app: :my_app

  schema("users", infer: true)
end

Schema with Custom Fields

Ecto

defmodule MyApp.User do
  use Ecto.Schema

  schema "users" do
    field :name, :string
    field :email, :string
    field :active, :boolean, default: true
    field :role, :string, default: "member"
    timestamps()
  end
end

Drops.Relation

defmodule MyApp.Users do
  use Drops.Relation, otp_app: :my_app

  schema("users", infer: true) do
    field(:role, :string, default: "member")
  end
end

Query Functions

Ecto

users = Repo.all(User)

user = Repo.get(User, 1)

active_users = Repo.all_by(User, active: true)

Drops.Relation

users = MyApp.Users.all()

user = MyApp.Users.get(1)

active_users = MyApp.Users.all_by(active: true)

Custom Query Definition

Ecto

defmodule MyApp.Users do
  import Ecto.Query

  def active(query) do
    from u in query, where: u.active == true
  end

  def by_role(query, role) do
    from u in query, where: u.role == ^role
  end

  def order(query, field) do
    from u in query, order_by: ^field
  end
end

User
  |> MyApp.Users.active()
  |> MyApp.Users.by_role("member")
  |> MyApp.Users.order_by(:name)
  |> Repo.all()

Drops.Relation

defmodule MyApp.Users do
  use Drops.Relation, otp_app: :my_app

  schema("users", infer: true)

  defquery active() do
    from(u in relation(), where: u.active == true)
  end

  defquery by_role(role) do
    from(u in relation(), where: u.role == ^role)
  end
end

MyApp.Users.active()
  |> MyApp.Users.by_role("member")
  |> MyApp.Users.order(:name)
  |> Repo.all()