Skip to content

Latest commit

 

History

55 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TypedEctoSchema

Build Status Coverage Status

TypedEctoSchema provides a DSL on top of Ecto.Schema to define schemas with typespecs without all the boilerplate code.

For example, if you want to add type information about your Ecto.Schema, you normally do something like this:

defmodule Person do
  use Ecto.Schema

  @enforce_keys [:name]

  schema "people" do
    field(:name, :string)
    field(:age, :integer)
    field(:happy, :boolean, default: true)
    field(:phone, :string)
    belongs_to(:company, Company)
    timestamps(type: :naive_datetime_usec)
  end

  @type t() :: %__MODULE__{
          __meta__: Ecto.Schema.Metadata.t(),
          id: integer() | nil,
          name: String.t(),
          age: non_neg_integer() | nil,
          happy: boolean(),
          phone: String.t() | nil,
          company_id: integer() | nil,
          company: Company.t() | Ecto.Association.NotLoaded.t() | nil,
          inserted_at: NaiveDateTime.t() | nil,
          updated_at: NaiveDateTime.t() | nil
        }
end

With typed_ecto_schema you can just do:

defmodule Person do
  use TypedEctoSchema

  typed_schema "people" do
    field(:name, :string, enforce: true, null: false)
    field(:age, :integer) :: non_neg_integer() | nil
    field(:happy, :boolean, default: true, null: false)
    field(:phone, :string)
    belongs_to(:company, Company)
    timestamps(type: :naive_datetime_usec)
  end
end

Note that the timestamps are nullable by default (a struct that was not inserted yet has nil timestamps). You can opt out with timestamps(null: false).

Usage

Install it, add to your deps:

{:typed_ecto_schema, "~> 0.4.4", runtime: false}

And change your use Ecto.Schema for use TypedEctoSchema and change the calls to schema for typed_schema and embedded_schema to typed_embedded_schema.

The extra :null and :enforce options also work on the @primary_key attribute, so you can have a non-nullable (and/or enforced) primary key type:

@primary_key {:id, :binary_id, autogenerate: true, null: false}

Check the online documentation for further details.

PolymorphicEmbed support (experimental)

polymorphic_embed's polymorphic_embeds_one/2 and polymorphic_embeds_many/2 are supported inside typed_schema blocks behind a compile-time flag, disabled by default:

# config/config.exs
config :typed_ecto_schema, polymorphic_embed: true

The calls are recognized purely by name (so polymorphic_embed never becomes a dependency of this library) — the flag exists because another library could define same-named macros with different behavior, so only enable it if you use polymorphic_embed. With the flag disabled the calls behave exactly as before. The flag must live in compile-time config (config.exs, not runtime.exs) and requires Elixir 1.14+. This integration is experimental and may change in a future release.

Once enabled, the typespec is inferred as the union of the modules listed in the :types option:

defmodule Reminder do
  use TypedEctoSchema

  import PolymorphicEmbed

  typed_schema "reminders" do
    polymorphic_embeds_one(:channel,
      types: [sms: SMS, email: Email],
      on_replace: :update
    )
  end
end

This generates channel: (SMS.t() | Email.t()) | nil, while polymorphic_embeds_many generates a list of the union instead. The :: type override and the :null and :enforce options work just like they do for field/3 (and are stripped before the real polymorphic_embed macro runs):

# SMS.t() | Email.t() (without `| nil`), and :channel is added to @enforce_keys
polymorphic_embeds_one(:channel,
  types: [sms: SMS, email: Email],
  on_replace: :update,
  null: false,
  enforce: true
)

As with embeds_many, :null has no effect on polymorphic_embeds_many, since it is always initialized to an empty list. When the :types option cannot be resolved at compile time (for example, when it is a module attribute), the type falls back to any().

Since polymorphic_embed is not a dependency of this library, you still need to add it to your own deps and import it in your schema modules yourself.

Credits

This project started as a fork of the awesome typed_struct.

That being said, I'd like to give some special thanks to

About

A library to define Ecto schemas with typespecs without all the boilerplate code.

Resources

Stars

300 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages