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].

Link to this section Summary

Link to this section Callbacks

Link to this callback

after_update(struct_before, struct_after, changed_fields)

View Source (optional)
@callback after_update(
  struct_before :: term(),
  struct_after :: term(),
  changed_fields :: list()
) :: term()
@callback new(params :: map()) :: map()
Link to this callback

new(params, options)

View Source (optional)
@callback new(params :: map(), options :: keyword()) :: map()