View Source Spike.Form behaviour (spike v0.2.5)
Use this module to define your Spike forms.
simple-use-cases
Simple use cases
Simple Spike form, with no validation and no nested structs will look like this:
defmodule MyApp.RegistrationForm do
use Spike.Form do
field(:first_name, :string)
field(:last_name, :string)
field(:age, :integer)
field(:email, :string)
field(:accepts_conditions, :boolean)
end
end
form = MyApp.RegistrationForm.new(%{first_name: "Spike"})
form = Spike.update(form, form.ref, %{last_name: "Spiegel"})
form.first_name
=> "Spike"
form.last_name
=> "Spiegel"
adding-validations
Adding validations
Spike uses Vex as a validation library, so you can add form validations easily:
defmodule MyApp.RegistrationForm do
use Spike.Form do
field(:first_name, :string)
field(:last_name, :string)
field(:age, :integer)
field(:email, :string)
field(:accepts_conditions, :boolean)
end
validates(:first_name, presence: true)
validates(:accepts_conditions, acceptance: true)
end
form = MyApp.RegistrationForm.new(%{})
Spike.valid?(form)
=> false
Spike.errors(form)[form.ref]
=> %{accepts_conditions: [acceptance: "must be accepted"], first_name: [presence: "must be present"]}
nested-forms-with-contextual-validations
Nested forms with contextual validations
You can have nested forms, supproting nested validations as well, where child item can
access parent or sibling items by fetching validation context using Spike.validation_context/1
.
defmodule MyApp.BudgetPlanner do
defmodule LineItem do
use Spike.Form do
field(:price, :integer)
field(:name, :string)
validates(:name, presence: true)
validates(:price, presence: true, by: &__MODULE__.validate_price_within_budget/2)
end
def validate_price_within_budget(_price, this_line_item) do
[parent, :line_items] = Spike.validation_context(this_line_item)
sum =
parent.line_items
|> Enum.reduce_while(0, fn line_item, acc ->
if line_item.ref == this_line_item.ref do
{:halt, acc + line_item.price}
else
{:cont, acc + line_item.price}
end
end)
if parent.max_budget && sum > parent.max_budget do
{:error, "exceeds max budget of #{parent.max_budget}"}
else
:ok
end
end
end
use Spike.Form do
field(:max_budget, :integer)
embeds_many(:line_items, __MODULE__.LineItem)
end
end
For functions useful to manipulate forms, see [Spike]. For schema definition look
into Spike.Form.Schema
.
To initialize a Spike form, by casting a map to it's fields (recursively), you can use
new/1
callback.
form = MyApp.BudgetPlanner.new(%{max_budget: 12, line_items: [%{name: "Cheap one", price: 1}]})
Spike.valid?(form)
=> true
form = Spike.append(form, form.ref, :line_items, %{name: "Expensive one", price: 9000})
Spike.valid?(form)
=> false
Spike.human_readable_errors(form)
=> %{"line_items.1.price" => ["exceeds max budget of 12"]}
In case you need to cast fields marked as private, use new/2
where second parameter
is [cast_private: true]
.