Coder Social home page Coder Social logo

formex_ecto's Introduction

Formex Ecto

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.

Instalation

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

Optional Ecto.Changeset validator

config/config.exs

config :formex,
  validator: Formex.Ecto.ChangesetValidator

More info about this validator

Usage

Model

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

Form Type

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

Controller

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

Template

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:

Collections of forms

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.

Automation

This library does few things automatically.

Nested forms and collections

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.

Method

<%= 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.

Changeset modification

There is a callback modify_changeset. Examples:

Add something to an user during registration

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

Assign current logged user to a data which he creates

Controller

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

Form type

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

Tests

Test database

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.

Creating a new migration

MIX_ENV=test mix ecto.gen.migration migration_name -r Formex.Ecto.TestRepo

Troubleshooting

Repo is nil

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

Docs

Custom fields

Guides

formex_ecto's People

Contributors

jakub-zawislak avatar stareintothebeard avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.