View Source SeedFactory.Schema (SeedFactory v0.4.0)

A schema describes how commands modify context

This module provides a DSL for defining schemas that describe how entities should be created, updated, or deleted within a context using the SeedFactory library.

In order to use the DSL, add the following line to your module:

use SeedFactory.Schema

Command Definition

Command is the first thing that should be defined in the schema. To define a command, use the command macro followed by the command name. Inside the command block, you can define various parameters, a resolution, and produce, update, and delete directives.

command :create_user do
  # Parameters, resolution, produce, update, and delete directives
end

Parameters

Parameters define the inputs required for the command's resolver function and how default values should be generated. Parameter can be defined using the param macro and can have an arbitrary level of nesting.

Options

  • :value - a static default value. Applied by default as value: nil.
  • :generate - an anonymous function that generates data.
  • :entity - refers to an entity within the context.
  • :with_traits - a list of atoms with trait names. Can be applied only if :entity option is present.
  • :map - an anonymous function that allows mapping an entity to another value. Can be applied only if :entity option is present.

One of these options should always be specified: :value, :generate, :entity.

param :address do
  param :city, value: "Lemberg"
  param :street, generate: &random_street/0
end

param :paid_by, entity: :user, with_traits: [:active]
param :office_id, entity: :office, map: & &1.id

Resolution

The resolution defines the logic to be executed when the command is invoked. It is implemented as a resolver function inside the resolve macro. The resolver function is an anonymous function that takes args as its parameter.

It should return {:ok, map}, where map keys are atoms and values represent entities. The atom keys will be used by the :from option in produce and update directives.

{:error, reason} will raise an exception.

resolve(fn args ->
  user = MyApp.insert_user!(args)
  {:ok, %{user: user}}
end)

Producing Entities

The produce directive specifies that the command will put a new entity to the context. It takes two arguments: the name of the entity being produced and options.

Options

  • :from - an atom that specifies the key of the map returned by the resolver. Defaults to specified entity name (first argument)
produce :entity_name, from: :source_key
resolve(fn args ->
  {user, profile} = MyApp.register_user!(args)
  {:ok, %{user: user, profile: profile}}
end)

produce :user
produce :user_profile, from: :profile

Updating Entities

The update directive modifies an existing entity within the context. It takes two arguments: the name of the entity being updated and options.

Options

  • :from - an atom that specifies the key of the map returned by the resolver. Defaults to specified entity name (first argument)
update :entity_name, from: :source_key
resolve(fn args ->
  {user, profile} = MyApp.update_user!(args)
  {:ok, %{user: user, profile: profile}}
end)

update :user
update :user_profile, from: :profile

Deleting Entities

The delete directive removes an entity from the context.

delete :entity_name

It is used to delete an entity from the context.

Traits

The trait directive declares a trait for an entity. The first argument is trait name, the second is entity name.

A trait must contain an exec directive with the name of the command. Execution of the specified command marks entity with the trait.

Options

  • :from - an atom that points to the trait that should be replaced with the new one. This is useful for different kinds of status transitions.
trait :pending, :user do
  exec :create_user
end
trait :active, :user do
  from :pending
  exec :activate_user
end

Exec step

The exec directive is required when declaring a trait and is used for specifying what should be executed in order to mark entity with the trait

Options

  • :args_match - a function which accepts command args and must return a boolean. Must be used with :generate_args option. If the function returns true, then the entity will be marked with the trait.
  • :generate_args - a function which generates a map with args. Must be used with :args_match option. The function generates args which satisfy validation in :args_match option and is used when the entity is requested with the trait.
  • :args_pattern - a map with args. This option option is less verbose (and limited in functionality) alternative to the combination of :args_match and :generate_args options. If specified, then entity will be marked with the trait only when command args match the pattern. Also, the pattern will be used as a replacement to :generate_args invocation.
# all three instuction below are equal
exec :create_user
exec :create_user, args_pattern: %{}
exec :create_user, generate_args: fn -> %{} end, args_match: fn _args -> true end
trait :admin, :user do
  exec :create_user, args_pattern: %{role: :admin}
end

trait :normal, :user do
  exec :create_user, args_pattern: %{role: :normal}
end

# the same using the combination of `:args_match` and `:generate_args`
trait :admin, :user do
  exec :create_user do
    generate_args(fn -> %{role: :admin} end)
    args_match(&match?(%{role: :admin}, &1)
  end
end

trait :normal, :user do
  exec :create_user do
    generate_args(fn -> %{role: :normal} end)
    args_match(&match?(%{role: :normal}, &1))
  end
end
# an example which shows what is possible with `:args_match` + `:generate_args`
# but not with `:args_pattern`

trait :not_expired, :project do
  exec :publish_project do
    args_match(fn args -> Date.compare(Date.utc_today(), args.expiry_date) in [:lt, :eq] end)

    generate_args(fn ->
      today = Date.utc_today()
      %{start_date: today, expiry_date: Date.add(today, 21)}
    end)
  end
end

trait :expired, :project do
  exec :publish_project do
    args_match(fn args -> Date.compare(Date.utc_today(), args.expiry_date) == :gt end)

    generate_args(fn ->
      today = Date.utc_today()
      %{start_date: Date.add(today, -22), expiry_date: Date.add(today, -1)}
    end)
  end
end

Include schemas

It is possible to include multiple schemas into a new schema in order to reuse everything that is declared in specified modules.

include_schema MyApp.SeedFactorySchema