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.
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
The highest possible number, or string length
Specs
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
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