#
View Source Spike.Form behaviour (spike v0.4.0-rc.0)

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.

    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, validation_context) do
          [parent, :line_items] = validation_context

          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 `[allow_private_fields: true]`.

# Link to this section Summary

## Callbacks

after_update(struct_before, struct_after, changed_fields)
new(params)
new(params, options)

# 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()

Link to this callback

# new(params)

View Source (optional)

    @callback new(params :: map()) :: map()

Link to this callback

# new(params, options)

View Source (optional)

    @callback new(params :: map(), options :: keyword()) :: map()