View Source EctoDiff (ecto_diff v0.5.0)

Generates a data structure describing the difference between two ecto structs.

For details on how to generate an EctoDiff.t/0 struct, see: diff/2.

For details on what the generated struct looks like, see: EctoDiff.t/0.

Link to this section Summary

Types

Configurable options for diff/3.

The type of change for a given struct.

A keyword list or a map which specifies an override from an Ecto schema to the desired primary key, for use in comparing structs.

A struct field or list of fields used to define a simple or composite primary key.

A field defined on a struct.

t()

Describes all changes made during an insert or update operation.

Functions

Returns an EctoDiff.t/0 describing the difference between two given ecto structs.

An alternate form of diff/2 which allows options to be specified.

Link to this section Types

@type diff_opts() :: [{:overrides, overrides()}]

Configurable options for diff/3.

options

Options

  • :overrides - A keyword list or map which provides a reference from a struct to a key (or list of keys) on that struct which will be used as the primary key (simple or composite) for diffing.
@type effect() :: :added | :deleted | :changed | :replaced

The type of change for a given struct.

Each EctoDiff.t/0 struct will have a field describing what happened to the given Ecto schema struct. The values can be one of the following:

  • :added - This struct is new and was not present previously. This happens when the primary struct, or an associated
           struct, was added during an update or insert.
  • :deleted - This struct was previously present, but no longer is.
  • :changed - This struct existed previously and still does, but some of its fields and/or associations have changed.
  • :replaced - This struct was replaced with a completely new one. This happens with belongs_to or embeds_one
              associations with the `on_replace: :nilify` option set.
@type overrides() ::
  [{module(), primary_key()}] | %{required(module()) => primary_key()}

A keyword list or a map which specifies an override from an Ecto schema to the desired primary key, for use in comparing structs.

Structs that are not specified will be compared using their default primary key.

examples

Examples:

[{Pet, :refid}, {Skill, :id}]

or

%{Skill => :refid, Owner => [:id, :refid]}
@type primary_key() :: struct_field() | [struct_field()]

A struct field or list of fields used to define a simple or composite primary key.

@type struct_field() :: atom()

A field defined on a struct.

@type t() :: %EctoDiff{
  changes: %{required(struct_field()) => any()},
  current: Ecto.Schema.t(),
  effect: effect(),
  previous: Ecto.Schema.t(),
  primary_key: %{required(struct_field()) => any()},
  struct: atom()
}

Describes all changes made during an insert or update operation.

The following fields should be considered public:

  • struct - The module atom of the ecto schema being diffed.
  • primary_key - The primary key(s) of the ecto struct. This is a map of all primary keys in case of composite keys. For most common use-cases this will just be the map %{id: id}.
  • changes - A map representing all changes made. The keys will be fields and associations defined in the ecto schema, but only fields and associations with changes will be present. For changed fields, the value will be a tuple representing the previous and new values (i.e. {previous, new}). For associations, the value will be another EctoDiff.t/0 struct for cardinality "one" associations, or a list of EctoDiff.t/0 structs for cardinality "many" associations.
  • effect - The type of change for this ecto struct. See effect/0 for details.
  • previous - The previous struct itself.
  • current - The current (new) struct itself.

Link to this section Functions

@spec diff(Ecto.Schema.t() | nil, Ecto.Schema.t() | nil) ::
  {:ok, t()} | {:ok, :unchanged}

Returns an EctoDiff.t/0 describing the difference between two given ecto structs.

The "previous" struct can be nil, to represent an insert operation.

examples

Examples

A new struct being inserted:

  iex> {:ok, pet} = %{name: "Spot"} |> Pet.new() |> Repo.insert()
  iex> {:ok, diff} = EctoDiff.diff(nil, pet)
  iex> diff
  #EctoDiff<
    struct: Pet,
    primary_key: %{id: 1},
    effect: :added,
    previous: #Pet<>,
    current: #Pet<>,
    changes: %{
      id: {nil, 1},
      name: {nil, "Spot"}
    }
  >

A struct being updated:

  iex> {:ok, pet} = %{name: "Spot"} |> Pet.new() |> Repo.insert()
  iex> {:ok, updated_pet} = pet |> Pet.update(%{name: "McFluffFace"}) |> Repo.update()
  iex> {:ok, diff} = EctoDiff.diff(pet, updated_pet)
  iex> diff
  #EctoDiff<
    struct: Pet,
    primary_key: %{id: 1},
    effect: :changed,
    previous: #Pet<>,
    current: #Pet<>,
    changes: %{
      name: {"Spot", "McFluffFace"}
    }
  >

A nested has_many association being updated:

  iex> {:ok, initial_pet} =
  ...>   %{name: "Spot", skills: [%{name: "Eating"}, %{name: "Sleeping"}, %{name: "Scratching"}]}
  ...>   |> Pet.new()
  ...>   |> Repo.insert()
  iex> [eating_id, sleeping_id, _scratching_id] = Enum.map(initial_pet.skills, & &1.id)
  iex> {:ok, updated_pet} =
  ...>   initial_pet
  ...>   |> Pet.update(%{skills: [%{id: eating_id}, %{id: sleeping_id, level: 2}, %{name: "Meowing"}]})
  ...>   |> Repo.update()
  iex> {:ok, diff} = EctoDiff.diff(initial_pet, updated_pet)
  iex> diff
  #EctoDiff<
    struct: Pet,
    primary_key: %{id: 1},
    effect: :changed,
    previous: #Pet<>,
    current: #Pet<>,
    changes: %{
      skills: [
        #EctoDiff<
          struct: Skill,
          primary_key: %{id: 2},
          effect: :changed,
          previous: #Skill<>,
          current: #Skill<>,
          changes: %{
            level: {1, 2}
          }
        >,
        #EctoDiff<
          struct: Skill,
          primary_key: %{id: 3},
          effect: :deleted,
          previous: #Skill<>,
          current: #Skill<>,
          changes: %{}
        >,
        #EctoDiff<
          struct: Skill,
          primary_key: %{id: 4},
          effect: :added,
          previous: #Skill<>,
          current: #Skill<>,
          changes: %{
            id: {nil, 4},
            pet_id: {nil, 1},
            name: {nil, "Meowing"}
          }
        >
      ]
    }
  >

Using an override to specify a composite primary key:

  iex> {:ok, pet} = %{name: "Spot", type: "Calico"} |> Pet.new() |> Repo.insert()
  iex> {:ok, diff} = EctoDiff.diff(nil, pet, overrides: %{Pet => [:name, :type]})
  iex> diff
  #EctoDiff<
    struct: Pet,
    primary_key: %{name: "Spot", type: "Calico"},
    effect: :added,
    previous: #Pet<>,
    current: #Pet<>,
    changes: %{
      id: {nil, 1},
      name: {nil, "Spot"},
      type: {"Cat", "Calico"}
    }
  >
Link to this function

diff(previous, current, opts)

View Source
@spec diff(Ecto.Schema.t() | nil, Ecto.Schema.t() | nil, diff_opts()) ::
  {:ok, t()} | {:ok, :unchanged}

An alternate form of diff/2 which allows options to be specified.

See diff_opts/0 for available options.