6
votes

I have a simple form with a textarea, where the user will type in (or copy'n'paste) a json string into it. The form will be sent to my controller action and I will validate the json string and if it will be ok, I will create some records and all is fine.

To send the form to my controller action I use Phoenix.HTML.Form "With connection data" at the moment and therefore I have no model/changeset.

<%= form_for @conn, @action, [as: :match], fn f -> %>
  <%= textarea f, :json, rows: 20 %>
  <%#= error_tag @changeset, f, :json %>
<% end %>

If the json is invalid for some reason, I like to render the form again and display the error message(s). error_tag is a view helper method which will display an error at a field if there's a changeset. Because of this it's commented out now.

def error_tag(form, field) do
  if error = form.errors[field] do
    content_tag :span, (humanize(field) <> " " <> translate_error(error)), class: "help-block"
  end
end

def error_tag(changeset, form, field) do
  if changeset.action do
    error_tag(form, field)
  end
end

What is a proper way to add an error so that I can display them at a form field. Do I have to add the error to @conn or f (form) or Is there another way to got?

3
I think you could assign error(s) to your connection in the controller before rerendering the form and access those in the form since you pass connection to the template anyway.NoDisplayName

3 Answers

11
votes

In your controller module

create a keyword list with the errors inside the action

errors = [field: "error message"]

then pass it when calling the render method

render(conn, "template.html", errors: errors)

In your view module

add a helper function to extract and format the message from the keyword list

def error_tag(errors, field) when is_list(errors) and is_atom(field) do
  case Keyword.fetch(errors, field) do
    {:ok, message} -> content_tag :span, (humanize(field) <> " " <> translate_error(message)), class: "help-block"
    :error -> html_escape("")
  end
end

In your template file

display the error message

<%= form_for @conn, @action, [as: :match], fn f -> %>
  <%= textarea f, :json, rows: 20 %>
  <%= error_tag @errors, :json %>
<% end %>
2
votes

Another approach is to create an embeded_schema in which you can define all the data model including validations without persisting into the DB, and rely on the validation/error management shipped in Phoenix. José Valim explains how to use this approach.

You just create the schema this way:

data  = %{}
types = %{first_name: :string, last_name: :string, email: :string}

changeset =
  {data, types} # The data+types tuple is equivalent to %Registration{}
  |> Ecto.Changeset.cast(params["sign_up"], Map.keys(types))
  |> validate_required(...)
  |> validate_length(...)

and then show the errors as usual:

<%= error_tag f, :first_name %>
-1
votes

I decided to implement a module that validates the json string. The module method changeset returns an Ecto.Changeset to which I added my custom errors. The private validate_json method checks the json schema and content. After all I set the changes and params to the given params from the form and I set valid? wether I have errors or not.

def changeset(required_fields, json_field, params) do
  changeset =
    %Ecto.Changeset{model: %MyApp.ModelName{}}
      |> Ecto.Changeset.cast(params, required_fields, ~w())
      |> validate_json(json_field, params)
  %Ecto.Changeset{changeset | changes: params, params: params, valid?: Enum.count(changeset.errors) == 0}
end

This way I keep my views and partials as they are and the form and all helper methods work with my changeset as they did before.