View Source EctoMorph (EctoMorph v0.1.29)

Utility functions for Ecto related stuff and things. Check out the functions docs to see what is available.

Link to this section Summary

Functions

Takes some data and tries to convert it to a struct in the shape of the given schema. Casts values to the types defined by the schema dynamically using ecto changesets.

Takes some data and tries to convert it to a struct in the shape of the given schema. Casts values to the types defined by the schema dynamically using ecto changesets.

Same as cast_to_struct/2, but raises if the data fails casting.

Same as cast_to_struct/3, but raises if the data fails casting.

Deep filters the data to only include those fields that appear in the given schema and in any of the given schema's relations.

Returns a map of all of the given schema's fields contained within data. This is not recursive so look at deep_filter_by_schema_fields if you want a recursive version.

Casts the given data into a changeset according to the types defined by the given schema. It ignores any fields in data that are not defined in the schema, and recursively casts any embedded fields to a changeset also. Accepts a different struct as the first argument, calling Map.to_struct on it first. Also allows the schema to be an existing struct, in which case it will infer the schema from the struct, and effectively update that struct with the changes supplied in data.

Takes in a map of data and creates a changeset out of it by casting the data recursively, according to the whitelist of fields in fields. The map of data may be a struct, and the fields whitelist can whitelist fields of nested relations by providing a list for them as well.

Take a changeset and returns a struct if there are no errors on the changeset. Returns an error tuple with the invalid changeset otherwise.

Essentially a wrapper around Ecto.Changeset.apply_action! where the action is create. It will create a struct out of a valid changeset and raise in the case of an invalid one.

Creates a map out of the Ecto struct, removing the internal ecto fields. Optionally you can remove the inserted_at and updated_at timestamp fields also by passing in :exclude_timestamps as an option

Attempts to update the given Ecto Schema struct with the given data by casting data and merging it into the struct. Uses cast and changesets to recursively update any nested relations also.

Allows us to specify validations for nested changesets.

Validates whether a changeset has a the given fields. You can pass in relations and they will be required, and you can pass in nested keys which will also be validated.

Link to this section Functions

Link to this function

cast_to_struct(data, schema)

View Source
@spec cast_to_struct(map() | ecto_struct(), schema_module() | struct()) ::
  okay_struct() | error_changeset()

Takes some data and tries to convert it to a struct in the shape of the given schema. Casts values to the types defined by the schema dynamically using ecto changesets.

Consider this:

iex> Jason.encode!(%{a: :b, c: Decimal.new("10")}) |> Jason.decode!
%{"a" => "b", "c" => "10"}

When we decode some JSON (e.g. from a jsonb column in the db or from a network request), the JSON gets decoded by our Jason lib, but not all of the information is preserved; any atom keys become strings, and if the value is a type that is not part of the JSON spec, it is casted to a string.

This means we cannot pass that JSON data directly into a struct/2 function and expect a shiny Ecto struct back (struct!/2 will just raise, and struct/2 will silently return an empty struct)

UNTIL NOW!

Here we take care of casting the values in the json to the type that the given schema defines, as well as turning the string keys into (existing) atoms. (We know they will be existing atoms because they will exist in the schema definitions.)

We filter out any keys that are not defined in the schema, and if the first argument is a struct, we call Map.from_struct/1 on it first. This can be useful for converting data between structs.

Check out the tests for more full examples.

examples

Examples

iex> defmodule Test do
...>   use Ecto.Schema
...>
...>   embedded_schema do
...>     field(:pageviews, :integer)
...>   end
...> end
...> {:ok, test = %Test{}} = cast_to_struct(%{"pageviews" => "10"}, Test)
...> test.pageviews
10

iex> defmodule Test do
...>   use Ecto.Schema
...>
...>   embedded_schema do
...>     field(:pageviews, :integer)
...>   end
...> end
...> json = %{"pageviews" => "10", "ignored_field" => "ten"}
...> {:ok, test = %Test{}} = cast_to_struct(json, Test)
...> test.pageviews
10
Link to this function

cast_to_struct(data, schema, fields)

View Source
@spec cast_to_struct(map() | ecto_struct(), schema_module() | struct(), list()) ::
  okay_struct() | error_changeset()

Takes some data and tries to convert it to a struct in the shape of the given schema. Casts values to the types defined by the schema dynamically using ecto changesets.

Accepts a whitelist of fields that you allow updates / inserts on. This list of fields can define fields for inner schemas also like so:

EctoMorph.cast_to_struct(json, SchemaUnderTest, [
  :boolean,
  :name,
  :binary,
  :array_of_ints,
  steamed_hams: [:pickles, double_nested_schema: [:value]]
])

We filter out any keys that are not defined in the schema, and if the first argument is a struct, we call Map.from_struct/1 on it first. This can be useful for converting data between structs.

Link to this function

cast_to_struct!(data, schema)

View Source
@spec cast_to_struct!(map() | ecto_struct(), schema_module()) ::
  struct() | no_return()

Same as cast_to_struct/2, but raises if the data fails casting.

Link to this function

cast_to_struct!(data, schema, fields)

View Source
@spec cast_to_struct!(map() | ecto_struct(), schema_module(), list()) ::
  struct() | no_return()

Same as cast_to_struct/3, but raises if the data fails casting.

Link to this function

deep_filter_by_schema_fields(data, schema, opts \\ [])

View Source

Deep filters the data to only include those fields that appear in the given schema and in any of the given schema's relations.

If the schema has_one(:foo, Foo) and data has :foo as an key, then the value under :foo in data will be filtered by the fields in Foo. This will happen for all casts, embeds, and virtual fields. There is no way to determine via reflection which schema a through relation points to, so by default they are filtered by their own schema if they are a map.

This is useful for converting a struct into a map, eliminating internal Ecto fields in the process.

options

Options

When filtering you can optionally choose to nillify Ecto.Association.NotLoaded structs. By default they are passed through as is, but you can nillify them like this:

deep_filter_by_schema_fields(data, MySchema, filter_not_loaded: true)

examples

Examples

iex> deep_filter_by_schema_fields(%{a: "c", ignored: true, stuff: "nope"}, A)
%{a: "c"}

iex> data = %{relation: %Ecto.Association.NotLoaded{}}
...> deep_filter_by_schema_fields(data, A, filter_not_loaded: true)
%{relation: nil}
Link to this function

filter_by_schema_fields(data, schema, opts \\ [])

View Source
@spec filter_by_schema_fields(map(), schema_module(), list()) :: map()

Returns a map of all of the given schema's fields contained within data. This is not recursive so look at deep_filter_by_schema_fields if you want a recursive version.

options

Options

  • :filter_not_loaded - This will nillify any Ecto.Association.NotLoaded structs in the map, setting the value to be nil for any non loaded association.

  • :filter_assocs - This will remove any key from data that is the same as any of the associations (including embeds) in schema.

    iex> data = %{id: 1, other: %Ecto.Association.NotLoaded{}}
    ...> filter_by_schema_fields(data, MySchema, filter_not_loaded: true)
    %{id: 1, other: nil}
    
    iex> data = %{id: 1, other: %AnotherOne{}}
    ...> filter_by_schema_fields(data, MySchema)
    %{id: 1, other: %AnotherOne{}}
Link to this function

generate_changeset(data, schema)

View Source
@spec generate_changeset(map() | ecto_struct(), schema_module() | ecto_struct()) ::
  Ecto.Changeset.t()

Casts the given data into a changeset according to the types defined by the given schema. It ignores any fields in data that are not defined in the schema, and recursively casts any embedded fields to a changeset also. Accepts a different struct as the first argument, calling Map.to_struct on it first. Also allows the schema to be an existing struct, in which case it will infer the schema from the struct, and effectively update that struct with the changes supplied in data.

examples

Examples

...> data = %{
...>  "integer" => "77",
...>  "steamed_hams" => [%{
...>    "pickles" => 1,
...>    "sauce_ratio" => "0.7",
...>    "double_nested_schema" => %{"value" => "works!"}
...>  }],
...> }
...> EctoMorph.generate_changeset(data, %SchemaUnderTest{integer: 2})
...>
Link to this function

generate_changeset(data, schema_or_existing_struct, fields)

View Source
@spec generate_changeset(map(), schema_module() | ecto_struct(), list()) ::
  Ecto.Changeset.t()

Takes in a map of data and creates a changeset out of it by casting the data recursively, according to the whitelist of fields in fields. The map of data may be a struct, and the fields whitelist can whitelist fields of nested relations by providing a list for them as well.

examples

Examples

If we provide a whitelist of fields, we will be passed a changeset for the changes on those fields only:

...> data = %{
...>  "integer" => "77",
...>  "steamed_hams" => [%{
...>    "pickles" => 1,
...>    "sauce_ratio" => "0.7",
...>    "double_nested_schema" => %{"value" => "works!"}
...>  }],
...> }
...> EctoMorph.generate_changeset(data, SchemaUnderTest, [:integer])
...>

We can also define whitelists for any arbitrarily deep relation like so:

...> data = %{
...>  "integer" => "77",
...>  "steamed_hams" => [%{
...>    "pickles" => 1,
...>    "sauce_ratio" => "0.7",
...>    "double_nested_schema" => %{"value" => "works!"}
...>  }],
...> }
...> EctoMorph.generate_changeset(data, SchemaUnderTest, [
...>   :integer,
...>   steamed_hams: [:pickles, double_nested_schema: [:value]]
...> ])
@spec into_struct(Ecto.Changeset.t()) :: okay_struct() | error_changeset()

Take a changeset and returns a struct if there are no errors on the changeset. Returns an error tuple with the invalid changeset otherwise.

@spec into_struct!(Ecto.Changeset.t()) :: struct() | no_return()

Essentially a wrapper around Ecto.Changeset.apply_action! where the action is create. It will create a struct out of a valid changeset and raise in the case of an invalid one.

Link to this function

map_from_struct(struct, options \\ [])

View Source
@spec map_from_struct(ecto_struct(), list()) :: map()

Creates a map out of the Ecto struct, removing the internal ecto fields. Optionally you can remove the inserted_at and updated_at timestamp fields also by passing in :exclude_timestamps as an option

This function is not deep. Prefer deep_filter_by_schema_fields or filter_by_schema_fields

examples

Examples

iex> map_from_struct(%Test{}, [:exclude_timestamps])
%Test{foo: "bar", id: 10}

iex> map_from_struct(%Test{})
%Test{foo: "bar", updated_at: ~N[2000-01-01 23:00:07], inserted_at: ~N[2000-01-01 23:00:07], id: 10}

iex> map_from_struct(%Test{}, [:exclude_timestamps, :exclude_id])
%Test{foo: "bar"}
Link to this function

update_struct(struct_to_update, data)

View Source
@spec update_struct(ecto_struct(), map()) :: okay_struct() | error_changeset()

Attempts to update the given Ecto Schema struct with the given data by casting data and merging it into the struct. Uses cast and changesets to recursively update any nested relations also.

Accepts a whitelist of fields for which updates can take place on. The whitelist can be arbitrarily nested, and Data may be a map, or another struct of any kind. See examples below.

examples

Examples

iex> MyApp.Repo.get(Thing, 10) |> EctoMorph.update

As with cast_to_struct, the data you are updating struct you are updating can be a

Link to this function

update_struct(struct_to_update, data, field_whitelist)

View Source
@spec update_struct(ecto_struct(), map(), list()) :: okay_struct() | error_changeset()
Link to this function

validate_nested_changeset(changeset, path, validation)

View Source

Allows us to specify validations for nested changesets.

Accepts a path to a nested changeset, and a validation function. The validation fun will be passed the changeset at the end of the path, and the result of the validation function will be merged back into the parent changeset.

If a changeset is invalid, the parent will also be marked as valid?: false (as well as any changeset between the root changeset and the nested one), but the error messages will remain on the changeset they are relevant for. This is in line with how Ecto works elsewhere like in cast_embed etc. To get the nested error messages you can use Ecto.Changeset.traverse_errors

This works with *_many relations by validating the list of changesets. If you are validating their nested relations, each changeset in the list must have the nested relation in their changes.

If you provide a path to changeset that does not exist in changes then no validation will run for that changeset. This is to allow partial updates - essentially only validating changes that exist.

If you want to validate that a set of fields are present (either on the changeset.changes or in changeset.data), then use EctoMorph.validate_required/2

examples

Examples

EctoMorph.generate_changeset(%{nested: %{foo: 3}})
|> EctoMorph.validate_nested_changeset([:nested], fn changeset ->
  Ecto.Changeset.validate_number(changeset, :foo, greater_than: 5)
end)

changeset = EctoMorph.generate_changeset(%{nested: %{double_nested: %{field: 6}}})
EctoMorph.validate_nested_changeset(changeset, [:nested, :double_nested], &MySchema.validate/1)
Link to this function

validate_required(changeset, path)

View Source

Validates whether a changeset has a the given fields. You can pass in relations and they will be required, and you can pass in nested keys which will also be validated.

For the relations, this follows the semantics of Ecto.Changeset.validate_required and will check changes for a non null relation, then check data. If either are non null the validation will pass allowing the possibility for partial updates.

examples

Examples

EctoMorph.generate_changeset(%{my: :data, relation: %{}}, MyModule)
|> EctoMorph.validate_required([:relation])

EctoMorph.generate_changeset(%{my: :data, relation: %{nested_thing: %{}}}, MyModule)
|> EctoMorph.validate_required([relation: :nested_thing])

data = %{my: :data, relation: %{nested_thing: %{thing: 1}}}
EctoMorph.validate_required(data, [relation: [nested_thing: :thing]])