I use the following:
Schema:
defmodule App.Form.ContactForm do
use Ecto.Schema
import Ecto.Changeset
schema "" do
field :name, :string, virtual: true
field :email, :string, virtual: true
field :phone, :string, virtual: true
field :body, :binary, virtual: true
end
def changeset(model, params) do
model
|> cast(params, [:name, :email, :phone, :body])
|> validate_required([:name, :phone])
end
end
Context:
defmodule App.Form do
alias App.Form.ContactForm
def change_contact(%ContactForm{} = contact \\ %ContactForm{}) do
ContactForm.changeset(contact, %{})
end
def create_contact(attrs \\ %{}) do
contact = ContactForm.changeset(%ContactForm{}, attrs)
contact = Map.merge(contact, %{action: :create}) # because we don't have a Repo call, we need to manually set the action.
if contact.valid? do
# send email or whatever here.
end
contact
end
end
In the html:
<%= form_for @contact, Routes.contact_path(@conn, :contact), [as: "contact"], fn f -> %>
# the form. I leave styling up to you. Errors should be working because we set the action.
In the router:
post "/contact", PageController, :contact, as: :contact
And the two necessary functions in the controller:
def index(conn, _params) do
render(conn, "index.html", contact: App.Form.change_contact())
end
def contact(conn, %{"contact" => contact_params}) do
with changeset <- App.Form.create_contact(contact_params),
true <- changeset.valid?
do
conn
|> put_flash(:success, gettext("We will get back to you shortly."))
|> render("index.html", contact: changeset)
else
_ ->
conn
|> put_flash(:error, gettext("Please check the errors in the form."))
|> render("index.html", contact: App.Form.create_contact(contact_params))
end
end
That's a lot of code for a contact form, that's why I wanted to post this so that you won't have to rewrite this. I hope it helps.