View Source Sorcery.SpecDb (Sorcery v0.2.4)

introduction

Introduction

In Sorcery, we keep data sync'd across nodes. So it naturally follows that we should keep the metadata sync'd across codes.

schema-module

Schema module

In a typical phoenix app, you might have a Player schema for a game. You can replace part, or all of that module with Sorcery. Observe:

defmodule MyApp.Player do
  require Sorcery.SpecDb

  @spec_table %{
    user_id: %{t: :id, require_update: false},
    name: %{t: :string, default: "Player", min: 3, max: 45},
    age: %{t: :integer, min: 0, max: 200, bump: true}
  }

  Sorcery.SpecDb.build_schema_module("player")

  # Alternately:
  #
  #  def spec_table, do: @spec_table
  #  Sorcery.SpecDb.build_ecto_schema(name)
  #  Sorcery.SpecDb.build_norm_schema()
  #  Sorcery.SpecDb.build_streamdata_generator()
  #  Sorcery.SpecDb.build_changesets()


end

exploring-the-results

Exploring the results

What does this achieve? Well, I think the test module actually speaks for itself. Read the comments carefully

defmodule MyApp.PlayerTest do
  use ExUnit.Case
  use ExUnitProperties
  use Norm
  alias MyApp.Player


  test "build_schema macro" do
    # Builds the struct via Ecto.Schema
    assert Map.get(%Player{}, :name) == "Player"

    # Builds the Norm schema
    assert !valid?(%{name: "hello"}, Player.t())
    assert valid?(%Player{user_id: 1, age: 23, name: "hello"}, Player.t())
    assert valid?(%{user_id: 1, age: 23, name: "hello"}, Player.t())
  end


  property "Generates Players" do
    # First, we generate a player with random data that matches the spec_table
    # The map you pass into gen/1 will override any random values.
    check all player <- Player.gen(%{user_id: 24}) do
      assert valid?(player, Player.t())

      # We get a valid changeset
      cs = Player.sorcery_update(%Player{id: player.id}, player)
      assert cs.valid?


      # If we use numbers outside the min/max, using `bump: true` will set it the same min/max value.
      # Remember our spec_table included:
      # age: %{t: :integer, min: 0, max: 200, bump: true}

      player = Map.put(player, :age, 9999999)
      cs = Player.sorcery_update(%Player{id: player.id}, player)
      assert cs.changes.age == 200
      assert cs.valid?

      # Instead of causing an invalid changeset, it just 'bumped' it to the :max value.
    end
  end


end

the-table_spec

The table_spec

To see more about the options to pass into each field, see the types section.

Link to this section Summary

Types

Whether the value should be coerced to its min/max if beyond that range.

Whether this field should be cast inside sorcery_insert/2

Whether this field should be cast inside sorcery_update/2

If t: :list, then every item must be of this type

The default value if nothing else set.

Sets the length of a list type.

The highest possible number, or string length

The lowest possible number, or string length

A list of possible options

Whether this field should be in validate_required inside sorcery_insert/2

Whether this field should be in validate_required inside sorcery_update/2

Default: true. Whether Norm should treat this as a required field. Does nothing if :default field is set.

t()

Required. The type of this field

Functions

Adds sorcery_insert/2 and sorcery_update/2, based on the @spec_table

# Injects this
use Ecto.Schema

schema "name" do
  field :fieldname, :type
end

# While passing in all the data from @spec_table

Adds the MODULE.t() function, for usage with Norm.

# Macro which automatically adds to your module:
def spec_table, do: @spec_table
Sorcery.SpecDb.build_ecto_schema(name)
Sorcery.SpecDb.build_norm_schema()
Sorcery.SpecDb.build_streamdata_generator()
Sorcery.SpecDb.build_changesets()

Adds the MODULE.gen() function, for generating data in property based testing with StreamData

Link to this section Types

Specs

bump() :: boolean()

Whether the value should be coerced to its min/max if beyond that range.

Specs

cast_insert() :: boolean()

Whether this field should be cast inside sorcery_insert/2

Specs

cast_update() :: boolean()

Whether this field should be cast inside sorcery_update/2

Specs

coll_of() :: atom()

If t: :list, then every item must be of this type

Specs

default() :: any()

The default value if nothing else set.

Specs

length() :: integer()

Sets the length of a list type.

Specs

max() :: integer() | float()

The highest possible number, or string length

Specs

min() :: integer() | float()

The lowest possible number, or string length

Specs

one_of() :: [any()]

A list of possible options

Specs

require_insert() :: boolean()

Whether this field should be in validate_required inside sorcery_insert/2

Specs

require_update() :: boolean()

Whether this field should be in validate_required inside sorcery_update/2

Specs

required() :: boolean()

Default: true. Whether Norm should treat this as a required field. Does nothing if :default field is set.

Specs

t() :: :integer | :float | :boolean | :string

Required. The type of this field

Link to this section Functions

Link to this macro

build_changesets()

View Source (macro)

Adds sorcery_insert/2 and sorcery_update/2, based on the @spec_table

Link to this macro

build_ecto_schema(name)

View Source (macro)
# Injects this
use Ecto.Schema

schema "name" do
  field :fieldname, :type
end

# While passing in all the data from @spec_table
Link to this macro

build_norm_schema()

View Source (macro)

Adds the MODULE.t() function, for usage with Norm.

Link to this macro

build_schema_module(name)

View Source (macro)
# Macro which automatically adds to your module:
def spec_table, do: @spec_table
Sorcery.SpecDb.build_ecto_schema(name)
Sorcery.SpecDb.build_norm_schema()
Sorcery.SpecDb.build_streamdata_generator()
Sorcery.SpecDb.build_changesets()
Link to this macro

build_streamdata_generator()

View Source (macro)

Adds the MODULE.gen() function, for generating data in property based testing with StreamData