3
votes

I'm just getting started with Phoenix and I'm going through the Sending Email & also viewing the Phoenix.HTML.Form docs. I've been able to set up everything correctly based on the guide and have sent a test email via iex but I've yet to figure out how to send an email without using the @changset in the form. I was under the impression that using @changest is only needed when I'm using model data. For my scenario I'm simply trying to capture a name, email and message that gets sent to me when the user clicks send.

Help greatly appreciated!

2
Thanks for the edit, but why did you edit the question with a different user? I approved it, but a few other users will need to approve it as well before it will be visible... If you edit with the same user it will be visible immediately. - Patrick Oscity
Please also add the controller to your question. - Patrick Oscity

2 Answers

10
votes

You can use a changeset without it being backed by the database by using Ecto.Schema and virtual fields:

defmodule ContactForm do      
  use Ecto.Schema

  schema "" do
    field :email, :string, virtual: true
    field :name, :string, virtual: true
    field :body, :binary, virtual: true
  end

  def changeset(model, params \\ :empty) do
    model
    |> cast(params, ["email", "name", "binary"], [])
    #|> validate_length(:body, min: 5) - any validations, etc. 
  end   
end

With a module like this you can simply treat it as you would a model and your form will be validated, etc. You can then pass the whole %ContactForm{} struct to your mailer function for sending the email.

1
votes

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.