A small project that allows you to normalize Ecto schemas. This version supports the release candidate of Ecto 2.
The package can be installed as follows:
-
Add Normalixr to your list of dependencies in
mix.exs
:def deps do [{:normalixr, "~> 0.4.0"}] end
-
Add Normalixr to your list of applications in
mix.exs
:def application do [applications: [:logger, :normalixr]] end
-
Replace every instance of
use Ecto.Schema
withuse Normalixr.Schema
The major function in this application is Normalixr.normalize/2
. The first
argument is an Ecto
schema or a list thereof. The second argument is optional
and can be ignored in most use cases, it is the result of a previous call to
this function, and serves as an accumulator.
This means you can simply normalize the result of a query as follows:
normalized = query
|> Repo.all
|> Normalixr.normalize
This function removes all nesting from the result, and transforms it to a flat representation.
In this flat representation, every schema is added to a field whose name is
derived from the schema name. By default, it uses the underscored
version of the final block of the schema name. For example,
schemas belonging to MyApp.Weather
will be added to the field :weather
,
and MyApp.CityName
to :city_name
.
(N.B. This means that MyApp.API
will be converted to :a_p_i
, so
you can override the default behaviour by defining
def underscored_name, do: :api
in MyApp.API
.)
These fields are maps, and the key of a particular schema is equal to its
primary key. For example, a schema with the primary key field equal to 1
belonging to MyApp.Weather
is normalized to:
%{weather: %{1 => %MyApp.Weather{id: 1}}
Any nesting due to preloading schemas is removed by this function as well.
If the field :cities
has been preloaded and this schema is called
MyApp.City
, the field will be replaced with a map:
%MyApp.Weather{id: 1, cities: [%MyApp.City{id: 1}, %MyApp.City{id: 2}]}
is normalized to
%{weather:
%{1 => %MyApp.Weather{id: 1,
cities: %{field: :city,
ids: [1, 2]}}},
%{city:
%{1 => %MyApp.City{id: 1},
2 => %MyApp.City{id: 2}}
}
This behaviour is cardinality-independent. If the schema is on the one-end of the relationship, the ids-field will contain a single-element list.
In order to facilitate integration with Phoenix, Normalixr offers the
Normalixr.PhoenixView
-module. In a Phoenix controller (or any other module
which imports the render
-functions from Phoenix.Controller
), you can call:
data = query
|> Repo.all
|> Normalixr.normalize
assigns = %{data: data,
fields_to_render: [city: [view: MyApp.CityView],
[weather: [view: MyApp.WeatherView]]]}
render(conn, Normalixr.PhoenixView, "normalized.json", assigns)
This will render the "city.json"
-template in MyApp.CityView
and the
"weather.json"
-template in MyApp.WeatherView
. These templates will receive
a two-parameter map as data, specifically, it will always have the key
:normalized_data
, which points to the full normalized representation, the
second points to the schema being rendered, and its key is its field name in
the normalized representation. For example, if you render MyApp.Weather
,
the render function will receive
%{weather: normalized_schema, normalized_data: normalized_data}
as data.
More documentation can be found on Hexdocs.