DataSchema (data_schema v0.1.0) View Source

DataSchemas are declarative specifications of how to create structs from some kind of data source. For example you can define a schema that knows how to turn an elixir map into a struct, casting all of the values as it goes. Alternatively you can set up a schema to ingest XML data and create structs from the values inside the XML.

Below is an example of a simple schema:

defmodule Blog do
  import DataSchema, only: [data_schema: 1]

  data_schema([
    field: {:name, "name", &to_string/1}
  ])
end

This says we will create a struct with a :name key and will get the value for that key from under the "name" key in the source data. That value will be passed to to_string/1 and the result of that function will end up as the value under :name in the resulting struct.

In general this is the format for a field:

field {:content, "text", &cast_string/1}
#  ^         ^      ^              ^
# field type |      |              |
# struct key name   |              |
#    path to data in the source    |
#                           casting function

Field Types

There are 4 kinds of struct fields we can have:

  1. field - The value will be a casted value from the source data.
  2. list_of - The value will be a list of casted values created from the source data.
  3. has_one - The value will be created from a nested data schema (so will be a struct)
  4. aggregate - The value will a casted value formed from multiple bits of data in the source.

Examples

See the guides for more in depth examples but below you can see how we create a schema that will take a map of data and create a struct out of it. Given the following schema:

defmodule Sandwich do
  require DataSchema

  DataSchema.data_schema([
    field: {:type, "the_type", &String.upcase/1},
    list_of: {:fillings, "the_fillings", &(String.downcase(&1["name"]))}
  ])
end

input_data = %{
  "the_type" => "fake steak",
  "the_fillings" => [
    %{"name" => "fake stake", "good?" => true},
    %{"name" => "SAUCE"},
    %{"name" => "sweetcorn"},
  ]
}

DataSchema.to_struct!(input_data, Sandwich)
# outputs the following:
%Sandwich{
  type: "FAKE STEAK",
  fillings: ["fake stake", "sauce", "sweetcorn"],
}

Link to this section Summary

Functions

Defines a data schema with the provided fields. Uses the default DataSchema.MapAccessor as the accessor, meaning it will expect the source data to be an elixir map and will use Map.get/2 to access the required values in the source data.

A macro that creates a data schema. By default all struct fields are required but you can specify that a field be optional by passing the correct option in. See the Options section below for more.

Accepts an data schema module and some source data and attempts to create the struct defined in the schema from the source data recursively.

Link to this section Functions

Link to this macro

data_schema(fields)

View Source (macro)

Defines a data schema with the provided fields. Uses the default DataSchema.MapAccessor as the accessor, meaning it will expect the source data to be an elixir map and will use Map.get/2 to access the required values in the source data.

See DataSchema.data_schema/2 for more details on what fields should look like.

Link to this macro

data_schema(fields, data_accessor)

View Source (macro)

A macro that creates a data schema. By default all struct fields are required but you can specify that a field be optional by passing the correct option in. See the Options section below for more.

Field Types

There are 4 kinds of struct fields we can have:

  1. field - The value will be a casted value from the source data.
  2. list_of - The value will be a list of casted values created from the source data.
  3. has_one - The value will be created from a nested data schema (so will be a struct)
  4. aggregate - The value will a casted value formed from multiple bits of data in the source.

Options

Available options are:

  • :optional? - specifies whether or not the field in the struct should be included in the @enforce_keys for the struct. By default all fields are required but you can mark them as optional by setting this to true.

Examples

See the guides for more in depth examples but below you can see how we create a schema that will take a map of data and create a struct out of it. Given the following schema:

defmodule Sandwich do
  require DataSchema

  DataSchema.data_schema([
    field: {:type, "the_type", &String.upcase/1},
    list_of: {:fillings, "the_fillings", &(String.downcase(&1["name"]))}
  ])
end

input_data = %{
  "the_type" => "fake steak",
  "the_fillings" => [
    %{"name" => "fake stake", "good?" => true},
    %{"name" => "SAUCE"},
    %{"name" => "sweetcorn"},
  ]
}

DataSchema.to_struct!(input_data, Sandwich)
# outputs the following:
%Sandwich{
  type: "FAKE STEAK",
  fillings: ["fake stake", "sauce", "sweetcorn"],
}
Link to this function

to_struct!(data, schema)

View Source

Accepts an data schema module and some source data and attempts to create the struct defined in the schema from the source data recursively.

We essentially visit each field in the schema and extract the data the field points to from the sauce data, passing it to the field's casting function before setting the result of that as the value on the struct.

This function takes a simple approach to creating the struct - whatever you return from a casting function will be set as the value of the struct field. You should raise if you want casting to fail.

Examples

data = %{ "spice" => "enables space travel" }

defmodule Foo do
  require DataSchema

  DataSchema.data_schema(
    field: {:a_rocket, "spice", & &1}
  )
end

DataSchema.to_struct!(data, Foo)
# => Outputs the following:
%Foo{a_rocket: "enables space travel"}