Library that integrates Ecto with Formex.
It also has an Ecto.Changeset validator adapter for those who wants to use validation functions from Ecto.Changeset.
def deps do
[{:formex_ecto, "~> 0.2.0"}]
end
config/config.exs
config :formex,
repo: App.Repo
web/web.ex
def model do
quote do
use Formex.Ecto.Schema
end
end
def controller do
quote do
use Formex.Ecto.Controller
end
end
In every form type that uses Ecto:
defmodule App.ArticleType do
use Formex.Type
use Formex.Ecto.Type # <- add this
config/config.exs
config :formex,
validator: Formex.Ecto.ChangesetValidator
More info about this validator
We have models Article, Category and Tag:
schema "articles" do
field :title, :string
field :content, :string
field :hidden, :boolean
belongs_to :category, App.Category
many_to_many :tags, App.Tag, join_through: "articles_tags" #...
end
schema "categories" do
field :name, :string
end
schema "tags" do
field :name, :string
end
Let's create a form for Article using Formex. For validation we will use Ecto.Changeset validator
# /web/form/article_type.ex
defmodule App.ArticleType do
use Formex.Type
alias Formex.Ecto.CustomField.SelectAssoc
def build_form(form) do
form
|> add(:title, :text_input, label: "Title", validation: [:required])
|> add(:content, :textarea, label: "Content", phoenix_opts: [
rows: 4
], validation: [:required])
|> add(:category_id, SelectAssoc, label: "Category", phoenix_opts: [
prompt: "Choose a category"
], validation: [:required])
|> add(:tags, SelectAssoc, label: "Tags", validation: [:required])
|> add(:hidden, :checkbox, label: "Is hidden?", required: false)
|> add(:save, :submit, label: "Submit", phoenix_opts: [
class: "btn-primary"
])
end
end
def new(conn, _params) do
form = create_form(App.ArticleType, %Article{})
render(conn, "new.html", form: form)
end
def create(conn, %{"article" => article_params}) do
App.ArticleType
|> create_form(%Article{}, article_params)
|> insert_form_data
|> case do
{:ok, _article} ->
conn
|> put_flash(:info, "Article created successfully.")
|> redirect(to: article_path(conn, :index))
{:error, form} ->
render(conn, "new.html", form: form)
end
end
def edit(conn, %{"id" => id}) do
article = Repo.get!(Article, id)
form = create_form(App.ArticleType, article)
render(conn, "edit.html", article: article, form: form)
end
def update(conn, %{"id" => id, "article" => article_params}) do
article = Repo.get!(Article, id)
App.ArticleType
|> create_form(article, article_params)
|> update_form_data
|> case do
{:ok, article} ->
conn
|> put_flash(:info, "Article updated successfully.")
|> redirect(to: article_path(conn, :show, article))
{:error, form} ->
render(conn, "edit.html", article: article, form: form)
end
end
form.html.eex
<%= formex_form_for @form, @action, fn f -> %>
<%= if @form.submitted? do %>Oops, something went wrong!<% end %>
<%= formex_row f, :name %>
<%= formex_row f, :content %>
<%= formex_row f, :category_id %>
<%= formex_row f, :tags %>
<%= formex_row f, :hidden %>
<%= formex_row f, :save %>
<%# or generate all fields at once: formex_rows f %>
<% end %>
Also replace changeset: @changeset
with form: @form
in new.html.eex
and edit.html.eex
The final effect after submit:
Every schema used in collections of forms should call formex_collection_child
:
schema "user_addresses" do
field :street, :string
field :postal_code, :string
field :city, :string
belongs_to :user, App.User
formex_collection_child() # <- add this
end
This macro adds :formex_id
and :formex_delete
virtual fields.
This library does few things automatically.
def build_form(form) do
form
|> add(:user_info, App.UserInfoType, struct_module: App.UserInfo)
end
You don't need to pass :struct_module
option, it is taken from schema information.
<%= formex_form_for @form, article_path(@conn, :create), [method: :post], fn f -> %>
You don't need to pass :method
option, it's set basing on struct.id
value.
There is a callback modify_changeset
. Examples:
You can add additional changes while user creation, such as hash of a password.
def build_form(form) do
form
|> add(:email, :text_input)
|> add(:password, :password_input)
|> add(:save, :submit, label: "Register")
end
# Put additional changes that will be saved to database.
def modify_changeset(changeset, _form) do
changeset
|> User.put_pass_hash
end
Get the current user and pass it to a form
user = Guardian.Plug.current_resource(conn) # or similar
ArticleType
|> create_form(%Article{}, article_params, author: user) # store current logged user in opts
|> insert_form_data
|> case do
{:ok, _user_employee} ->
#
{:error, form} ->
#
end
Assign user to a new article (and don't do it if it's an update action)
def build_form(form) do
#
end
def modify_changeset(changeset, form) do
# check if it's a create action
if !form.struct.id do
changeset
|> Ecto.Changeset.put_assoc(:author, form.opts[:author]) # access author via form.opts[:author]
else
changeset
end
end
Use config/test.secret.example.exs
to create config/test.secret.exs
Run this command to migrate:
MIX_ENV=test mix ecto.migrate -r Formex.Ecto.TestRepo
Now you can use tests via mix test
.
MIX_ENV=test mix ecto.gen.migration migration_name -r Formex.Ecto.TestRepo
Do you have some weird "nil.insert/1 is undefined or private
" error?
It happens when you forgot about the repo
option in the configuration or you set it after package compilation.
To recompile the whole package use: mix deps.compile formex_ecto --force
- Controller - controller helpers
- Form Type - add your custom code to a changeset
- Changeset validator adapter - validation using Ecto.Changeset, adding errors to a changeset